Repository: airyland/vux Branch: v2 Commit: d66ec01d2a76 Files: 971 Total size: 3.1 MB Directory structure: gitextract_8ntehtvf/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── build/ │ ├── build-components.js │ ├── build-docs.js │ ├── build-main.js │ ├── build-styles.js │ ├── build.js │ ├── check-versions.js │ ├── dev-doc.js │ ├── document.js │ ├── fetch-github.js │ ├── less.js │ ├── pre-publish.js │ ├── simulate.sh │ ├── umd-helper.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── vux-config.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ ├── webpack.prod.conf.js │ └── webpack.test.conf.js ├── circle.yml ├── config/ │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── docs/ │ ├── 404.md │ ├── about/ │ │ └── version.md │ ├── compile.js │ ├── component-contributors.js │ ├── en/ │ │ ├── .gitkeep │ │ ├── README.md │ │ ├── about/ │ │ │ ├── before-using-vux.md │ │ │ ├── contributors.vue │ │ │ ├── showcase.md │ │ │ └── thanks.md │ │ ├── contribution/ │ │ │ ├── donate.md │ │ │ ├── issue.md │ │ │ ├── pr.md │ │ │ └── vux-development.md │ │ ├── css/ │ │ │ ├── 1px.md │ │ │ └── close.md │ │ ├── css.md │ │ ├── development/ │ │ │ ├── i18n.md │ │ │ ├── remove-click-delay-fastclick.md │ │ │ ├── router.md │ │ │ ├── theme.md │ │ │ ├── typescript.md │ │ │ ├── vue-ajax.md │ │ │ ├── vue-global-method.md │ │ │ ├── vue-google-analytics.md │ │ │ ├── vue-loader-autoprefix.md │ │ │ ├── vue-loader-disable-eslint.md │ │ │ ├── vue-router-page-transition.md │ │ │ ├── vue-show-loading.md │ │ │ ├── vue-webpack-code-splitting-async.md │ │ │ ├── vue-webpack-env.md │ │ │ └── vux-nuxt-ssr.md │ │ ├── directives/ │ │ │ └── v-transfer-dom.md │ │ ├── faq/ │ │ │ ├── $t-is-not-defined.md │ │ │ ├── Uncaught-SyntaxError-Unexpected-token-export.md │ │ │ ├── can-vux-used-in-weapp.md │ │ │ ├── cannot-resolve-inline-desc.md │ │ │ ├── difference-between-vux-template-and-webpack-template.md │ │ │ ├── document-framework.md │ │ │ ├── does-vux-support-weex.md │ │ │ ├── dupicate-style.md │ │ │ ├── is-vux-wechat-official-project.md │ │ │ ├── vux-build-issue.md │ │ │ ├── vux-server-render-support.md │ │ │ ├── what-is-vux-virtual-component.md │ │ │ ├── why-cannot-i-import-all-components.md │ │ │ ├── why-cannot-i-use-vux-with-cdn.md │ │ │ ├── why-close-my-issue.md │ │ │ ├── why-delete-my-issue-comment.md │ │ │ ├── why-use-vue-file-for-distribution.md │ │ │ ├── why-use-x-prefix-for-components.md │ │ │ ├── why-using-vux-loader.md │ │ │ └── will-vux-stop-maintaining.md │ │ ├── install/ │ │ │ ├── biolerplate.md │ │ │ ├── manual-usage.md │ │ │ ├── npm.md │ │ │ ├── umd.md │ │ │ ├── upgrade-to-vux2.md │ │ │ └── usage.md │ │ ├── lab/ │ │ │ └── index.md │ │ ├── production/ │ │ │ └── inline-manifest.md │ │ ├── tools/ │ │ │ ├── base64.md │ │ │ ├── cookie.md │ │ │ ├── date.md │ │ │ ├── debounce-throttle.md │ │ │ ├── md5.md │ │ │ ├── number.md │ │ │ ├── querystring.md │ │ │ └── string.md │ │ ├── umd.md │ │ ├── upgrade-to-2.md │ │ ├── vux-loader/ │ │ │ ├── about.md │ │ │ ├── how-vux-loader-works.md │ │ │ ├── install.md │ │ │ └── plugins.md │ │ ├── vux-loader.md │ │ └── wechat/ │ │ ├── other.md │ │ ├── vue-wechat-jssdk.md │ │ └── wechat-document-title.md │ ├── examples/ │ │ ├── loading.html │ │ ├── umd_component_include_all.html │ │ ├── umd_component_include_single.html │ │ ├── umd_plugin_include_all.html │ │ └── umd_plugin_include_single.html │ ├── i18n.js │ ├── index.html │ ├── package.json │ ├── ream.config.js │ ├── server.js │ ├── src/ │ │ ├── App.vue │ │ ├── algolia-search.vue │ │ ├── app.css │ │ ├── component.vue │ │ ├── index.js │ │ ├── summary.js │ │ ├── tool-routes-en.json │ │ └── tool-routes-zh-CN.json │ ├── watch.js │ └── zh-CN/ │ ├── README.md │ ├── about/ │ │ ├── before-using-vux.md │ │ ├── contributors.vue │ │ ├── showcase.md │ │ └── thanks.md │ ├── contribution/ │ │ ├── donate.md │ │ ├── issue.md │ │ ├── pr.md │ │ └── vux-development.md │ ├── css/ │ │ ├── 1px.md │ │ └── close.md │ ├── css.md │ ├── development/ │ │ ├── i18n.md │ │ ├── remove-click-delay-fastclick.md │ │ ├── router.md │ │ ├── theme.md │ │ ├── typescript.md │ │ ├── vue-ajax.md │ │ ├── vue-global-method.md │ │ ├── vue-google-analytics.md │ │ ├── vue-loader-autoprefix.md │ │ ├── vue-loader-disable-eslint.md │ │ ├── vue-router-page-transition.md │ │ ├── vue-show-loading.md │ │ ├── vue-webpack-code-splitting-async.md │ │ ├── vue-webpack-env.md │ │ └── vux-nuxt-ssr.md │ ├── directives/ │ │ └── v-transfer-dom.md │ ├── faq/ │ │ ├── $t-is-not-defined.md │ │ ├── Uncaught-SyntaxError-Unexpected-token-export.md │ │ ├── can-vux-used-in-weapp.md │ │ ├── cannot-resolve-inline-desc.md │ │ ├── difference-between-vux-template-and-webpack-template.md │ │ ├── document-framework.md │ │ ├── does-vux-support-weex.md │ │ ├── dupicate-style.md │ │ ├── is-vux-wechat-official-project.md │ │ ├── vux-build-issue.md │ │ ├── vux-server-render-support.md │ │ ├── what-is-vux-virtual-component.md │ │ ├── why-cannot-i-import-all-components.md │ │ ├── why-cannot-i-use-vux-with-cdn.md │ │ ├── why-close-my-issue.md │ │ ├── why-delete-my-issue-comment.md │ │ ├── why-use-vue-file-for-distribution.md │ │ ├── why-use-x-prefix-for-components.md │ │ ├── why-using-vux-loader.md │ │ └── will-vux-stop-maintaining.md │ ├── install/ │ │ ├── biolerplate.md │ │ ├── manual-usage.md │ │ ├── npm.md │ │ ├── umd.md │ │ ├── upgrade-to-vux2.md │ │ └── usage.md │ ├── lab/ │ │ └── index.md │ ├── production/ │ │ └── inline-manifest.md │ ├── tools/ │ │ ├── base64.md │ │ ├── cookie.md │ │ ├── date.md │ │ ├── debounce-throttle.md │ │ ├── md5.md │ │ ├── number.md │ │ ├── querystring.md │ │ └── string.md │ ├── umd.md │ ├── upgrade-to-2.md │ ├── vux-loader/ │ │ ├── about.md │ │ ├── how-vux-loader-works.md │ │ ├── install.md │ │ └── plugins.md │ ├── vux-loader.md │ └── wechat/ │ ├── other.md │ ├── vue-wechat-jssdk.md │ └── wechat-document-title.md ├── index.html ├── index.js ├── package.json ├── packages/ │ ├── loader/ │ │ ├── README.md │ │ ├── libs/ │ │ │ ├── get-i18n-block.js │ │ │ ├── parse-i18n-function.js │ │ │ └── replace-i18n-for-script.js │ │ ├── package.json │ │ ├── parse.js │ │ ├── plugins/ │ │ │ ├── duplicate-style/ │ │ │ │ └── index.js │ │ │ └── html-build-callback/ │ │ │ └── index.js │ │ ├── src/ │ │ │ ├── after-less-loader.js │ │ │ ├── before-template-compiler-loader.js │ │ │ ├── i18n-loader.js │ │ │ ├── index.js │ │ │ ├── js-loader.js │ │ │ ├── libs/ │ │ │ │ ├── babel-transform-imports/ │ │ │ │ │ ├── .babelrc │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ ├── index.js │ │ │ │ │ ├── package.json │ │ │ │ │ └── test/ │ │ │ │ │ ├── invalidTransform.js │ │ │ │ │ ├── tests.js │ │ │ │ │ └── transform.js │ │ │ │ ├── get-less-variables.js │ │ │ │ ├── getTag.js │ │ │ │ ├── import-parser-v2.js │ │ │ │ ├── import-parser.js │ │ │ │ ├── parse-demo-code.js │ │ │ │ ├── parse-virtual-component.js │ │ │ │ ├── parse-x-icon.js │ │ │ │ ├── report.js │ │ │ │ └── stripe-attributes.js │ │ │ ├── noop-loader.js │ │ │ ├── script-loader.js │ │ │ ├── style-loader.js │ │ │ └── template-loader.js │ │ └── test/ │ │ ├── fixtures/ │ │ │ ├── basic.vue │ │ │ ├── css-modules.vue │ │ │ ├── es2015.vue │ │ │ ├── extend.vue │ │ │ ├── extract-css.vue │ │ │ ├── inject.js │ │ │ ├── inject.vue │ │ │ ├── media-query.vue │ │ │ ├── postcss.vue │ │ │ ├── pre.vue │ │ │ ├── resolve.vue │ │ │ ├── scoped-css.vue │ │ │ ├── script-import.js │ │ │ ├── script-import.vue │ │ │ ├── service.js │ │ │ ├── style-import-scoped.css │ │ │ ├── style-import.css │ │ │ ├── style-import.vue │ │ │ ├── template-import.pug │ │ │ └── template-import.vue │ │ ├── test.js │ │ └── vux-fixtures/ │ │ ├── less-theme-001.less │ │ ├── less-theme-002.less │ │ ├── less-theme-basic.less │ │ ├── less-theme-basic.vue │ │ ├── less-theme-import.less │ │ ├── option-vux-dir.vue │ │ ├── script-parser-fn.vue │ │ ├── style-parser-basic.vue │ │ ├── template-feature-switch-basic.vue │ │ └── template-parser-fn.vue │ └── vue-cli-3-example/ │ ├── .browserslistrc │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ └── index.html │ ├── src/ │ │ ├── App.vue │ │ ├── components/ │ │ │ ├── HelloWorld.js │ │ │ └── HelloWorld.vue │ │ ├── main.js │ │ ├── router.js │ │ ├── store.js │ │ ├── theme.less │ │ └── views/ │ │ ├── About.vue │ │ └── Home.vue │ └── vue.config.js ├── src/ │ ├── App.vue │ ├── components/ │ │ ├── actionsheet/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── agree/ │ │ │ ├── index.vue │ │ │ └── test.js │ │ ├── alert/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── badge/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── blur/ │ │ │ ├── blur.js │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── box/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── button-tab/ │ │ │ ├── button-tab-item.vue │ │ │ ├── button-tab.vue │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── calendar/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── card/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── cell/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── props.js │ │ │ ├── test/ │ │ │ │ └── wrapper.vue │ │ │ └── test.js │ │ ├── cell-box/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── cell-form-preview/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── check-icon/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── checker/ │ │ │ ├── checker-item.vue │ │ │ ├── checker.vue │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── checklist/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── object-filter.js │ │ │ └── test.js │ │ ├── clocker/ │ │ │ ├── clocker.js │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── color-picker/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── confirm/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── countdown/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── countup/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── datetime/ │ │ │ ├── datetimepicker.js │ │ │ ├── format.js │ │ │ ├── index.vue │ │ │ ├── makeData.js │ │ │ ├── metas.yml │ │ │ ├── style.less │ │ │ ├── test.js │ │ │ └── util.js │ │ ├── datetime-range/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── datetime-view/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── dev-tip/ │ │ │ └── index.vue │ │ ├── divider/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── drawer/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── flexbox/ │ │ │ ├── flexbox-item.vue │ │ │ ├── flexbox.vue │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── flow/ │ │ │ ├── flow-line.vue │ │ │ ├── flow-state.vue │ │ │ ├── flow.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── form-preview/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── fullpage/ │ │ │ ├── DemoBasic.vue │ │ │ ├── index.vue │ │ │ └── lib.js │ │ ├── grid/ │ │ │ ├── grid-item.vue │ │ │ ├── grid.vue │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── group/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── group-title/ │ │ │ ├── index.vue │ │ │ └── test.js │ │ ├── icon/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── inline-calendar/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── props.js │ │ │ ├── test.js │ │ │ └── util.js │ │ ├── inline-desc/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── inline-loading/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── inline-x-number/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── inline-x-switch/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── style.less │ │ ├── load-more/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── loading/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── map.json │ │ ├── marquee/ │ │ │ ├── marquee-item.vue │ │ │ ├── marquee.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── masker/ │ │ │ ├── converter.js │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── metas.yml │ │ ├── msg/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── number-roller/ │ │ │ ├── index.vue │ │ │ └── lib.js │ │ ├── orientation/ │ │ │ ├── index.js │ │ │ └── orientation.js │ │ ├── panel/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── picker/ │ │ │ ├── animate.js │ │ │ ├── chain.js │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── scroller.css │ │ │ ├── scroller.js │ │ │ ├── test.js │ │ │ └── util.js │ │ ├── popover/ │ │ │ ├── DemoIndex.vue │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── popup/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── popup.js │ │ │ └── test.js │ │ ├── popup-header/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── popup-picker/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── popup-radio/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── previewer/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── qrcode/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── radio/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── props.js │ │ │ └── test.js │ │ ├── range/ │ │ │ ├── index.vue │ │ │ ├── lib/ │ │ │ │ ├── classes.js │ │ │ │ ├── closest.js │ │ │ │ ├── delegate.js │ │ │ │ ├── emitter.js │ │ │ │ ├── event.js │ │ │ │ ├── events.js │ │ │ │ ├── matches-selector.js │ │ │ │ ├── mouse.js │ │ │ │ └── query.js │ │ │ ├── metas.yml │ │ │ ├── powerange.js │ │ │ ├── powerange.less │ │ │ └── utils.js │ │ ├── rater/ │ │ │ ├── index.vue │ │ │ └── metas.yml │ │ ├── scroller/ │ │ │ ├── index.vue │ │ │ └── metas.yml │ │ ├── search/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── selector/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── shake/ │ │ │ └── index.vue │ │ ├── spinner/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ ├── requestAnimationFrame.js │ │ │ └── spinner.js │ │ ├── step/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── step-item.vue │ │ │ └── step.vue │ │ ├── sticky/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── sticky.js │ │ ├── swipeout/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── swipeout-button.vue │ │ │ ├── swipeout-item.vue │ │ │ └── swipeout.vue │ │ ├── swiper/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── swiper-item.vue │ │ │ ├── swiper.js │ │ │ └── swiper.vue │ │ ├── tab/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── tab-item.vue │ │ │ └── tab.vue │ │ ├── tabbar/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── tabbar-item.vue │ │ │ └── tabbar.vue │ │ ├── timeline/ │ │ │ ├── index.js │ │ │ ├── metas.yml │ │ │ ├── timeline-item.vue │ │ │ └── timeline.vue │ │ ├── tip/ │ │ │ └── index.vue │ │ ├── toast/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── uploader/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── utils.js │ │ ├── v-chart/ │ │ │ ├── metas.yml │ │ │ ├── mixin.js │ │ │ ├── util.js │ │ │ ├── v-area.vue │ │ │ ├── v-axis.vue │ │ │ ├── v-bar.vue │ │ │ ├── v-chart.vue │ │ │ ├── v-guide.vue │ │ │ ├── v-legend.vue │ │ │ ├── v-line.vue │ │ │ ├── v-pie.vue │ │ │ ├── v-point.vue │ │ │ ├── v-scale.vue │ │ │ └── v-tooltip.vue │ │ ├── video/ │ │ │ ├── index.vue │ │ │ ├── zy.media.css │ │ │ └── zy.media.js │ │ ├── view-box/ │ │ │ ├── index.vue │ │ │ └── metas.yml │ │ ├── wechat-emotion/ │ │ │ ├── index.vue │ │ │ └── metas.yml │ │ ├── week-calendar/ │ │ │ └── index.vue │ │ ├── wepay-input/ │ │ │ └── index.vue │ │ ├── x-address/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-button/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-circle/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-dialog/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-form/ │ │ │ ├── field.vue │ │ │ ├── form.vue │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── x-header/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-hr/ │ │ │ └── index.vue │ │ ├── x-icon/ │ │ │ └── metas.yml │ │ ├── x-img/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-input/ │ │ │ ├── fixtures/ │ │ │ │ └── Mask.vue │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-number/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-progress/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-switch/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ ├── x-table/ │ │ │ ├── index.vue │ │ │ ├── metas.yml │ │ │ └── test.js │ │ └── x-textarea/ │ │ ├── index.vue │ │ ├── metas.yml │ │ └── test.js │ ├── datas/ │ │ ├── china_address.json │ │ ├── china_address_v1.json │ │ ├── china_address_v2.json │ │ ├── china_address_v3.json │ │ ├── china_address_v4.json │ │ └── vux_component_list.json │ ├── demo_list.json │ ├── demos/ │ │ ├── Actionsheet.vue │ │ ├── Agree.vue │ │ ├── Alert.vue │ │ ├── AppTest.vue │ │ ├── Badge.vue │ │ ├── Blur.vue │ │ ├── ButtonTab.vue │ │ ├── Calendar.vue │ │ ├── Card.vue │ │ ├── Cell.vue │ │ ├── CellBox.vue │ │ ├── CellFormPreview.vue │ │ ├── Center.vue │ │ ├── CheckIcon.vue │ │ ├── Checker.vue │ │ ├── Checklist.vue │ │ ├── Clocker.vue │ │ ├── Close.vue │ │ ├── ColorPicker.vue │ │ ├── Comment.vue │ │ ├── Confirm.vue │ │ ├── Countdown.vue │ │ ├── Countup.vue │ │ ├── Datetime.vue │ │ ├── DatetimeRange.vue │ │ ├── DatetimeView.vue │ │ ├── Demo.vue │ │ ├── DemoList.vue │ │ ├── Device.vue │ │ ├── Divider.vue │ │ ├── Donate.vue │ │ ├── Drawer.vue │ │ ├── Flexbox.vue │ │ ├── Flow.vue │ │ ├── FormPreview.vue │ │ ├── FormatTime.vue │ │ ├── Grid.vue │ │ ├── GridComponent.vue │ │ ├── GridNumber.vue │ │ ├── Group.vue │ │ ├── Home.vue │ │ ├── Icon.vue │ │ ├── IconLoading.vue │ │ ├── Inline-calendar-start-date.vue │ │ ├── InlineCalendar.vue │ │ ├── InlineCalendarEachDaySlot.vue │ │ ├── InlineCalendarMulti.vue │ │ ├── InlineLoading.vue │ │ ├── InlineXNumber.vue │ │ ├── InlineXSwitch.vue │ │ ├── Issue1211.vue │ │ ├── Issue189.vue │ │ ├── Issue2365.vue │ │ ├── Issue2960.vue │ │ ├── Issue461.vue │ │ ├── Issue471.vue │ │ ├── Issue598.vue │ │ ├── Issue624.vue │ │ ├── Issue649.vue │ │ ├── Issue702.vue │ │ ├── Issue833.vue │ │ ├── Issue865.vue │ │ ├── Line.vue │ │ ├── LoadMore.vue │ │ ├── Loading.vue │ │ ├── Marquee.vue │ │ ├── Masker.vue │ │ ├── Msg.vue │ │ ├── NotFoundComponent.vue │ │ ├── NumberRoller.vue │ │ ├── OnePx.vue │ │ ├── Panel.vue │ │ ├── Picker.vue │ │ ├── Popover.vue │ │ ├── Popup.vue │ │ ├── PopupHeader.vue │ │ ├── PopupPicker.vue │ │ ├── PopupRadio.vue │ │ ├── Previewer.vue │ │ ├── Pulldown.vue │ │ ├── PulldownPullup.vue │ │ ├── Pullup.vue │ │ ├── Qrcode.vue │ │ ├── Querystring.vue │ │ ├── Radio.vue │ │ ├── Range.vue │ │ ├── Rater.vue │ │ ├── Reddot.vue │ │ ├── Scroller.vue │ │ ├── ScrollerFull.vue │ │ ├── ScrollerHeader.vue │ │ ├── ScrollerSwiper.vue │ │ ├── Search.vue │ │ ├── SearchStatic.vue │ │ ├── Selector.vue │ │ ├── Shake.vue │ │ ├── Spinner.vue │ │ ├── Step.vue │ │ ├── Sticky.vue │ │ ├── Swipeout.vue │ │ ├── Swiper.vue │ │ ├── Tab.vue │ │ ├── Tabbar.vue │ │ ├── TabbarIcon.vue │ │ ├── TabbarLink.vue │ │ ├── TabbarSimple.vue │ │ ├── Test.vue │ │ ├── Thanks.vue │ │ ├── Timeline.vue │ │ ├── Toast.vue │ │ ├── ToggleText.vue │ │ ├── Uploader.vue │ │ ├── ViewBox.vue │ │ ├── Wechat.vue │ │ ├── WechatEmotion.vue │ │ ├── WeekCalendar.vue │ │ ├── WepayInput.vue │ │ ├── XAddress.vue │ │ ├── XButton.vue │ │ ├── XCircle.vue │ │ ├── XDialog.vue │ │ ├── XForm.vue │ │ ├── XHeader.vue │ │ ├── XHr.vue │ │ ├── XIcon.vue │ │ ├── XImg.vue │ │ ├── XImgPopup.vue │ │ ├── XImgScroller.vue │ │ ├── XInput.vue │ │ ├── XNumber.vue │ │ ├── XProgress.vue │ │ ├── XSwitch.vue │ │ ├── XTable.vue │ │ ├── XTextarea.vue │ │ ├── countdown/ │ │ │ ├── basic.vue │ │ │ └── manually.vue │ │ ├── style.css │ │ ├── v-chart/ │ │ │ ├── area_basic.vue │ │ │ ├── area_compare.json │ │ │ ├── area_compare.vue │ │ │ ├── area_empty.json │ │ │ ├── area_empty.vue │ │ │ ├── area_gradient.vue │ │ │ ├── area_negative.vue │ │ │ ├── area_stack.vue │ │ │ ├── bar_basic.vue │ │ │ ├── bar_percent.vue │ │ │ ├── bar_series.vue │ │ │ ├── bar_stack.vue │ │ │ ├── line_basic.vue │ │ │ ├── line_color.json │ │ │ ├── line_color.vue │ │ │ ├── line_gradient.vue │ │ │ ├── line_realtime.vue │ │ │ ├── line_smooth_with_dot.vue │ │ │ ├── line_with_dot.vue │ │ │ ├── pie_annular.vue │ │ │ ├── pie_basic.vue │ │ │ ├── point.json │ │ │ ├── point.vue │ │ │ └── prevent_render.vue │ │ └── x-circle/ │ │ ├── basic.vue │ │ └── gradient.vue │ ├── directives/ │ │ ├── click-outside/ │ │ │ └── index.js │ │ ├── inview/ │ │ │ ├── index.js │ │ │ └── inview.js │ │ └── transfer-dom/ │ │ ├── index.js │ │ └── metas.yml │ ├── faq-routes.json │ ├── filters/ │ │ ├── array2String.js │ │ ├── format-time.js │ │ ├── friendly-time.js │ │ ├── name2value.js │ │ └── value2name.js │ ├── libs/ │ │ ├── base.js │ │ ├── clean-style.js │ │ ├── dom.js │ │ ├── eventor.js │ │ ├── get-parent-prop.js │ │ ├── is-array.js │ │ ├── mixin_uuid.js │ │ ├── passive_supported.js │ │ ├── plugin_helper.js │ │ ├── router.js │ │ ├── trim.js │ │ └── webp-support.js │ ├── lints/ │ │ └── shouleBeInGroup.js │ ├── locales/ │ │ ├── all.yml │ │ ├── en.yml │ │ └── zh-CN.yml │ ├── main.js │ ├── mixins/ │ │ ├── calendar-marks.js │ │ ├── multi-items.js │ │ ├── prevent-body-scroll.js │ │ ├── safari-fix.js │ │ └── uuid.js │ ├── plugins/ │ │ ├── ajax/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── alert/ │ │ │ ├── index.js │ │ │ ├── module.js │ │ │ └── util.js │ │ ├── app/ │ │ │ └── index.js │ │ ├── bus/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── close-dialogs/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── config/ │ │ │ └── index.js │ │ ├── confirm/ │ │ │ └── index.js │ │ ├── datetime/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── device/ │ │ │ ├── doc.md │ │ │ └── index.js │ │ ├── loading/ │ │ │ ├── index.js │ │ │ ├── metas.yaml │ │ │ ├── module.js │ │ │ └── util.js │ │ ├── locale/ │ │ │ └── index.js │ │ ├── toast/ │ │ │ ├── index.js │ │ │ └── metas.yaml │ │ └── wechat/ │ │ ├── 1.3.0.js │ │ ├── 1.3.2.js │ │ ├── index.js │ │ ├── metas.yml │ │ ├── origin.1.3.0.js │ │ └── origin.1.3.2.js │ ├── styles/ │ │ ├── 1px.less │ │ ├── 1px.yml │ │ ├── blank.less │ │ ├── center.less │ │ ├── close.less │ │ ├── close.yml │ │ ├── doc.md │ │ ├── index.less │ │ ├── index.vue │ │ ├── loading.less │ │ ├── reddot.less │ │ ├── reset.less │ │ ├── tap.less │ │ ├── transition.less │ │ ├── variable.less │ │ ├── vux-modal.css │ │ └── weui/ │ │ ├── base/ │ │ │ ├── fn.less │ │ │ ├── mixin/ │ │ │ │ ├── mobile.less │ │ │ │ ├── setArrow.less │ │ │ │ ├── setChecked.less │ │ │ │ ├── setOnepx.less │ │ │ │ └── text.less │ │ │ ├── reset.less │ │ │ └── variable/ │ │ │ ├── color.less │ │ │ ├── global.less │ │ │ ├── weui-cell.less │ │ │ ├── weui-dialog.less │ │ │ ├── weui_button.less │ │ │ ├── weui_grid.less │ │ │ ├── weui_msg.less │ │ │ └── weui_progress.less │ │ ├── icon/ │ │ │ ├── weui_font.less │ │ │ └── weui_icon_font.less │ │ ├── weui.less │ │ └── widget/ │ │ ├── weui-agree/ │ │ │ └── weui-agree.less │ │ ├── weui-animate/ │ │ │ └── weui-animate.less │ │ ├── weui-button/ │ │ │ ├── weui-btn_default.less │ │ │ ├── weui-btn_disabled.less │ │ │ ├── weui-btn_global.less │ │ │ ├── weui-btn_loading.less │ │ │ ├── weui-btn_plain.less │ │ │ ├── weui-btn_primary.less │ │ │ ├── weui-btn_warn.less │ │ │ └── weui-button.less │ │ ├── weui-flex/ │ │ │ └── weui-flex.less │ │ ├── weui-footer/ │ │ │ └── weui-footer.less │ │ ├── weui-grid/ │ │ │ └── weui-grid.less │ │ ├── weui-loading/ │ │ │ └── weui-loading.less │ │ ├── weui-picker/ │ │ │ └── weui-picker.less │ │ ├── weui-slider.less/ │ │ │ └── weui-slider.less │ │ ├── weui-uploader/ │ │ │ └── index.less │ │ ├── weui_cell/ │ │ │ ├── weui-gallery.less │ │ │ ├── weui_access.less │ │ │ ├── weui_cell_global.less │ │ │ ├── weui_check/ │ │ │ │ ├── weui_check_common.less │ │ │ │ ├── weui_checkbox.less │ │ │ │ └── weui_radio.less │ │ │ ├── weui_check.less │ │ │ ├── weui_form/ │ │ │ │ ├── weui-form-preview.less │ │ │ │ ├── weui_form_common.less │ │ │ │ ├── weui_select.less │ │ │ │ └── weui_vcode.less │ │ │ ├── weui_form.less │ │ │ ├── weui_switch.less │ │ │ └── weui_uploader.less │ │ ├── weui_media_box/ │ │ │ └── weui_media_box.less │ │ ├── weui_page/ │ │ │ ├── weui_article.less │ │ │ └── weui_msg.less │ │ ├── weui_panel/ │ │ │ └── weui_panel.less │ │ ├── weui_progress/ │ │ │ └── weui_progress.less │ │ ├── weui_searchbar/ │ │ │ └── weui_searchbar.less │ │ ├── weui_tab/ │ │ │ ├── navbar.less │ │ │ ├── vux-tabbar.less │ │ │ ├── weui-tabbar.less │ │ │ ├── weui_tab.less │ │ │ └── weui_tab_tabbar.less │ │ └── weui_tips/ │ │ ├── weui-badge.less │ │ ├── weui-loadmore.less │ │ ├── weui_actionsheet.less │ │ ├── weui_dialog.less │ │ ├── weui_mask.less │ │ └── weui_toast.less │ ├── theme.less │ ├── tools/ │ │ ├── base64/ │ │ │ └── index.js │ │ ├── cookie/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── date/ │ │ │ ├── format.js │ │ │ ├── metas.yml │ │ │ └── range.js │ │ ├── debounce/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── md5/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ ├── number/ │ │ │ ├── comma.js │ │ │ ├── metas.yml │ │ │ ├── pad.js │ │ │ ├── random.js │ │ │ └── range.js │ │ ├── querystring/ │ │ │ └── index.js │ │ ├── string/ │ │ │ ├── metas.yml │ │ │ └── trim.js │ │ ├── throttle/ │ │ │ ├── index.js │ │ │ └── metas.yml │ │ └── validator/ │ │ └── chinaMobile.js │ └── vuex/ │ └── store.js ├── ssr/ │ └── nuxt/ │ ├── .babelrc │ ├── layouts/ │ │ └── default.vue │ ├── nuxt.config.js │ ├── package.json │ ├── pages/ │ │ └── index.vue │ ├── plugins/ │ │ ├── vux-components.js │ │ └── vux-plugins.js │ └── styles/ │ └── theme.less ├── static/ │ └── .gitkeep └── test/ └── unit/ ├── dateFormat.spec.js ├── index.js └── karma.conf.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["es2015", { "modules": false }], "stage-2" ], "plugins": ["transform-runtime"], "comments": false, "env": { "test": { "plugins": [ "istanbul" ] } } } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ build/*.js src/assets/*/*.js src/libs/eventor.js config/*.js src/tools/*/*.js src/components/video/zy.media.js src/plugins/wechat/1.3.0.js src/plugins/wechat/1.3.2.js src/plugins/wechat/origin.1.3.0.js src/plugins/wechat/origin.1.3.2.js ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, parser: 'babel-eslint', parserOptions: { sourceType: 'module' }, // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style extends: 'standard', globals: {'expect': true, 'it': true, 'describe': true, 'ga': true, 'window': true, 'document': true, 'alert': true, 'api': true, 'apiready': true}, env: { browser: true }, // required to lint *.vue files plugins: [ 'html' ], // add your custom rules here 'rules': { 'eol-last': 0, // allow paren-less arrow functions 'arrow-parens': 0, // allow async-await 'generator-star-spacing': 0, // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 } } ================================================ FILE: .github/FUNDING.yml ================================================ github: [airyland] ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Please makes sure the items are checked before submitting your PR, thank you! * [ ] `Rebase` before creating a PR to keep commit history clear. * [ ] `Only One commit` * [ ] No `eslint` errors ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ bower_components/ npm-debug.log /demo build/milestone.json examples/webpack/ .idea dist/components-commonjs/ /site /docs/changes /docs/demos /docs/**/faq/index.md /docs/**/components /docs/**/demos /docs/**/changelog /docs/.ream/ *.vue.vux.* src/locales/components.yml /dist/**/* src/components/index.js test/unit/coverage/ docs/*.map v2/ changes.json .nuxt/ docs/releases.json docs/src/routes.json docs/src/faq-routes.json docs/src/tool-routes.json docs/src/_index.js docs/sitemap.txt docs/algolia.json /src/demos/**/_index.vue yarn-error.log .vscode ================================================ FILE: .npmignore ================================================ build/ demo/ dist/ docs/ node_modules/ packages/ examples/ test/ /src/*.vue /src/*.js /src/*.html /src/assets/ /src/demos/ favicon.ico logo.png CNAME circle.yml bower.json /assets/ .babelrc docs/ *.vue.vux.* nuxt/ yarn.lock ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 7 os: linux cache: yarn: true branches: only: - v2 install: - | yarn install script: - | npm run lint && npm run test after_success: - | cat ./test/unit/coverage/lcov.info | ./node_modules/.bin/codecov ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016-present, Airyland Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Be Cool with Vue@^2.3.0 and WeUI.


Live Demo >>







## Sponsors

## Requirements + vue@^2.3.0(for .sync modifier) + webpack@^2.0 + node@^7.6(development) ## Docs + [中文文档](https://vux.li/) + English(working) ## Quick Start > [vux2 template](https://github.com/airyland/vux2) is directly modified from Vue official [webpack template](https://github.com/vuejs-templates/webpack). ``` bash npm install vue-cli -g vue init airyland/vux2 projectPath cd projectPath npm install // or yarn npm run dev // or yarn dev ``` ## Maintainers + [airyland](https://github.com/airyland) + [lichunqiang](https://github.com/lichunqiang) + [graysheeep](https://github.com/graysheeep) + [unclay](https://github.com/unclay) + [wg5945](https://github.com/wg5945) + [Sapphire2k](https://github.com/Sapphire2k) ## Vux is Inspired or Powered By: + [Vue](https://github.com/vuejs/vue) + [WeUI](https://github.com/weui/weui) + [FrozenUI](https://github.com/frozenui/frozenui) + [Ant Design](https://github.com/ant-design/ant-design) + [Ant Design Mobile](http://github.com/ant-design/ant-design-mobile) + [XScroll](https://github.com/huxiaoqi567/xscroll) + [Ionic](https://github.com/driftyco/ionic) + [SUI Mobile](https://github.com/sdc-alibaba/SUI-Mobile) + [PhotoSwipe](https://github.com/dimsemenov/PhotoSwipe) + [WePayUI](https://github.com/wepayui/wepayui) + [autosize](https://github.com/jackmoore/autosize)(MIT) + [buefy](https://github.com/buefy/buefy) ## License [MIT](https://github.com/airyland/vux/blob/v2/LICENSE) ================================================ FILE: build/build-components.js ================================================ 'use strict' process.env.NODE_ENV = 'production' /** * --locale='zh-CN' * --namespace='vux' * --components=Group,Cell */ var argv = require('yargs').argv var namespace = argv.namespace || 'vux' const { build } = require('./umd-helper') var isBuildAll = !argv.components var buildComponents = argv.components ? argv.components.split(',') : [] let config = require('./webpack.prod.conf.js') const vuxConfig = require('./vux-config') vuxConfig.plugins.forEach(function (plugin) { if (plugin.name === 'i18n') { plugin.vuxStaticReplace = true plugin.vuxLocale = argv['locale'] || 'zh-CN' } }) const webpack = require('webpack') const mkdirp = require('mkdirp') const fs = require('fs') const touch = require('touch') const path = require('path') mkdirp.sync(path.resolve(__dirname, '../dist/plugins')) mkdirp.sync(path.resolve(__dirname, '../dist/styles')) let list = require(path.resolve(__dirname, '../src/datas/vux_component_list.json')) const maps = require(path.resolve(__dirname, '../src/components/map.json')) // 查找在maps里但不在list里的组件 let others = [] for (let i in maps) { let match = list.filter(function (one) { return _camelCase(one.name) === i }) if (match.length === 0 && !/Plugin|Data|Directive|Filter|Item|NOTICE|Demo|Dev|Tool|String|Number|number|format|md5|base64|cookie/.test(i)) { others.push({ name: toDash(i), importName: i, path: maps[i] }) } } others.push({ name: 'swiper-item', importName: 'SwiperItem', path: maps['SwiperItem'] }) var ExtractTextPlugin = require('extract-text-webpack-plugin') var co = require('co') var thunkify = require('thunkify') var pkg = require(path.resolve(__dirname, '../package.json')) var utils = require('./utils') config.devtool = false config.plugins = config.plugins.slice(0, -2) config.plugins.forEach((one, index) => { if (one.constructor.name === 'HtmlWebpackPlugin') { config.plugins.splice(index, 1) } if (one.constructor.name === 'ExtractTextPlugin') { config.plugins.splice(index, 1) } }) config.output.assetsPublicPath = '/' let isBuilding = false co(function* () { if (!buildComponents.length) { try { yield build(buildMainConfig(), 'vux') } catch (e) { console.log(e) } try { for (let n = 0; n < others.length; n++) { yield build(buildConfig(others[n]), others[n].name) } } catch (e) { console.log(e) } try { const pluginList = ['Confirm', 'Toast', 'Device', 'Alert', 'Loading', ' Wechat', 'Ajax'] for (let j = 0; j < pluginList.length; j++) { yield build(buildPlugin(pluginList[j]), `Plugin ${pluginList[j]}`) } } catch (e) { console.log(e) } } if (buildComponents.length) { list = list.filter(function (one) { return buildComponents.indexOf(_camelCase(one.name)) > -1 }) } try { for (let i = 0; i < list.length; i++) { let one = list[i] const name = list[i].name if (one.items) { // build a commonjs bundle yield build(buildConfig({ name: list[i].name + '-pack', importName: _camelCase(list[i].name), path: `src/components/${name}/index.js` }), `pack: ${list[i].name}`) for (let j = 0; j < one.items.length; j++) { one.name = one.items[j] one.path = maps[_camelCase(one.items[j])] one.importName = _camelCase(one.items[j]) yield build(buildConfig(one), one.items[j]) } } else { yield build(buildConfig(one), one.name) } } } catch (e) { console.log(e) } }) function camelCase(input) { let str = input.toLowerCase().replace(/-(.)/g, function (match, group1) { return group1.toUpperCase(); }) str = str.replace(/_(.)/g, function (match, group1) { return group1.toUpperCase() }); return str } function _camelCase(input) { let str = camelCase(input) return str.slice(0, 1).toUpperCase() + str.slice(1) } function buildMainConfig() { // list all components const list = require('../src/components/map.json') let code = `const _vux = {}\n const isBrowser = typeof window !== 'undefined'\n` code += `isBrowser && (window.vux = _vux)\n\n` code += `import Style from '../styles/index.vue' // eslint-disable-line\n` delete list['NOTICE'] delete list['ChinaAddressV1Data'] delete list['vuxTransferDom'] for (let i in list) { const name = `${namespace}${i}` code += `import ${name} from '${list[i]}'\n _vux['${name}'] = ${name}\n` } code += ` if (isBrowser) { for (let i in _vux) { window[i] = _vux[i] } }\n` fs.writeFileSync(path.resolve(__dirname, '../src/components/index.js'), code) delete config.entry config.plugins.forEach((one, index) => { if (one.constructor.name === 'ExtractTextPlugin') { config.plugins.splice(index, 1) } }) config.plugins.push(new ExtractTextPlugin({ filename: `vux.min.css` })) config.entry = config.entry || {} config.entry['vux'] = 'src/components/index.js' config.output = {} config.output.libraryTarget = 'window' config.output.filename = `vux.min.js` config.output.path = path.resolve(__dirname, `../dist/`) delete config.__vueOptions__ return config } // build plugins function buildPlugin(name) { delete config.entry config.plugins.forEach((one, index) => { if (one.constructor.name === 'ExtractTextPlugin') { config.plugins.splice(index, 1) } }) config.plugins.push(new ExtractTextPlugin({ filename: 'index.min.css' })) config.entry = config.entry || {} config.entry['plugin'] = `src/plugins/${name.toLowerCase()}/index.js` config.output = {} config.output.libraryTarget = 'umd' config.output.library = `${namespace}${name}Plugin` config.output.filename = `index.min.js` config.output.path = path.resolve(__dirname, `../dist/plugins/${name.toLowerCase()}`) delete config.__vueOptions__ return config } function buildConfig(one, opts) { opts = opts || {} one.importName = one.importName || _camelCase(one.name) one.path = path.resolve(__dirname, '../' + (one.path || maps[one.importName])) delete config.entry config.plugins.forEach((one, index) => { if (one.constructor.name === 'ExtractTextPlugin') { config.plugins.splice(index, 1) } }) config.plugins.push(new ExtractTextPlugin({ filename: 'index.min.css' })) config.entry = {} config.entry[one.name] = one.path config.output = {} config.output.libraryTarget = 'umd' config.output.library = `${namespace}${one.importName}` config.output.filename = `index.min.js` config.output.path = path.resolve(__dirname, `../dist/components/${one.name}/`) delete config.__vueOptions__ return config } function toDash(str) { return str.replace(/([A-Z])/g, function (m, w) { return '-' + w.toLowerCase(); }).replace('-', '') } ================================================ FILE: build/build-docs.js ================================================ 'use strict' const glob = require("glob") const fs = require('fs') const yaml = require('js-yaml') const path = require('path') const _ = require('lodash') const mkdirp = require('mkdirp') const rimraf = require('rimraf') const semver = require('semver') const sortObj = require('sort-object') mkdirp.sync(path.resolve(__dirname, '../docs/zh-CN/changelog')) mkdirp.sync(path.resolve(__dirname, '../docs/en/changelog')) mkdirp.sync(path.resolve(__dirname, '../docs/zh-CN/components')) mkdirp.sync(path.resolve(__dirname, '../docs/en/components')) const aliasMap = { Base64Tool: 'base64', Md5Tool: 'md5', CookieTool: 'cookie', CommaTool: 'numberComma', PadTool: 'numberPad', RandomTool: 'numberRandom', FormatTool: 'dateFormat', TrimTool: 'stringTrim', QuerystringTool: 'querystring', DebounceTool: 'debounce', ThrottleTool: 'throttle' } let tMaps = { '组件列表': 'Components', '该组件已经停止维护。': 'This Component is Deprecated. ', '名字': 'name', '类型': 'type', '参数': 'params', '默认': 'default', '说明': 'description', '该文件为自动生成,请不要修改': 'THIS FILE IS AUTOGENERATED, DONOT EDIT IT', '进入demo页面': 'Demo page', '编辑文档': 'Edit document', 'demo源码': 'Demo source', '版本': 'version' } function getPath(dir) { return path.join(__dirname, dir) } let gComponents = [] let maps = {} saveMaps('numberRange', 'src/tools/number/range.js') saveMaps('dateRange', 'src/tools/date/range.js') saveMaps('TransferDom', 'src/directives/transfer-dom/index.js') saveMaps('trim', 'src/tools/string/trim') // add AlertModule saveMaps('AlertModule', 'src/plugins/alert/module') function saveMaps(key, value) { if (key === 'RangeTool') { return } if (/test|fixture/.test(value)) { return } if (/vux/.test(value)) { let index = value.indexOf('src/components') if (/Filter$/.test(key)) { index = value.indexOf('src/filters') } if (/Data$/.test(key)) { index = value.indexOf('src/datas') } if (/Plugin$/.test(key)) { index = value.indexOf('src/plugins') } if (/Directive$/.test(key)) { index = value.indexOf('src/directives') } if (/Tool$/.test(key)) { index = value.indexOf('src/tools') } value = value.slice(index, value.length) } maps[key] = value.replace('../', '') if (aliasMap[key]) { maps[aliasMap[key]] = maps[key] delete maps[key] } // sort const list = [] for (let i in maps) { list.push([i, maps[i]]) } list.sort(function (a, b) { return a[0].toLowerCase() > b[0].toLowerCase() ? 1 : -1 }) list.unshift(['NOTICE', 'THIS FILE IS AUTOGENERATED BY npm run build-docs']) const _list = {} list.forEach(function (one) { _list[one[0]] = one[1] }) fs.writeFileSync(getPath('../src/components/map.json'), JSON.stringify(_list, null, 2)) } glob(getPath("../src/plugins/**/index.js"), {}, function (er, files) { files.forEach(function (file) { let name = getComponentName(file) name = _camelCase(name) saveMaps(name + 'Plugin', file) }) }) glob(getPath("../src/tools/**/*.js"), {}, function (er, files) { files.forEach(function (file) { let name = getComponentName(file) name = _camelCase(name) saveMaps(name + 'Tool', file) }) }) glob(getPath("../src/directives/**/index.js"), {}, function (er, files) { files.forEach(function (file) { let name = getComponentName(file) name = _camelCase(name) saveMaps(name + 'Directive', file) }) }) glob(getPath("../src/filters/*.js"), {}, function (er, files) { files.forEach(function (file) { let name = getComponentName(file) name = _camelCase(name) saveMaps(name + 'Filter', file) }) }) glob(getPath("../src/datas/*.json"), {}, function (er, files) { files.forEach(function (file) { let name = getComponentName(file) name = _camelCase(name) saveMaps(name + 'Data', file) }) }) glob(getPath("../src/components/**/*.vue"), {}, function (er, files) { // 生成组件路径映射 // 语言配置 let rs = {} let enRs = {} let zhCnRs = {} files.forEach(function (file) { let name = getComponentName(file) const importName = _camelCase(name) saveMaps(importName, file) let content = fs.readFileSync(file, 'utf-8') if (content.indexOf('') > -1) { const name = getComponentName(file) const results = content.match(/]*>([\s\S]*?)<\/i18n>/) const json = yaml.safeLoad(results[1]) rs = Object.assign(rs, setKey(json, name)) for (let i in json) { let enItem = {} enRs[i] = json[i].en } for (let i in json) { zhCnRs[i] = json[i]['zh-CN'] } } }) let dump = yaml.safeDump({ en: enRs, 'zh-CN': zhCnRs }) fs.writeFileSync(getPath('../src/locales/all.yml'), dump) fs.writeFileSync(getPath('../src/locales/en.yml'), yaml.safeDump(enRs)) fs.writeFileSync(getPath('../src/locales/zh-CN.yml'), yaml.safeDump(zhCnRs)) }) function setKey(object, name) { for (let i in object) { object[`vux.${name}.${i}`] = object[i] delete object[i] } return object } glob(getPath('../src/**/metas.yml'), {}, function (err, files) { render(files) }) glob(getPath('../src/tools/**/metas.yml'), {}, function (err, files) { let rs = [] files.forEach(function (file) { const name = file.split('tools/')[1].replace('/metas.yml', '') try { const json = yaml.safeLoad(fs.readFileSync(file, 'utf-8')) rs.push({ name: name, metas: json }) } catch (e) { console.log('yml 出错') } }) fs.writeFileSync(getPath('../src/tools/changes.json'), JSON.stringify(rs, null, 2)) }) glob(getPath('../src/plugins/**/metas.yml'), {}, function (err, files) { let rs = [] files.forEach(function (file) { const name = file.split('plugins/')[1].replace('/metas.yml', '') const json = yaml.safeLoad(fs.readFileSync(file, 'utf-8')) rs.push({ name: name, metas: json }) }) fs.writeFileSync(getPath('../src/plugins/changes.json'), JSON.stringify(rs, null, 2)) }) glob(getPath('../src/directives/**/metas.yml'), {}, function (err, files) { let rs = [] files.forEach(function (file) { const name = file.split('directives/')[1].replace('/metas.yml', '') const json = yaml.safeLoad(fs.readFileSync(file, 'utf-8')) rs.push({ name: name, metas: json }) }) fs.writeFileSync(getPath('../src/directives/changes.json'), JSON.stringify(rs, null, 2)) }) function getComponentName(path) { let list = path.split('/') if (list[list.length - 1] === 'index.vue' || list[list.length - 1] === 'index.js') { return list[list.length - 2] } else if (list[list.length - 1] === 'metas.yml') { return list[list.length - 2] } else if (/\.json/.test(path)) { return list[list.length - 1].replace('.json', '') } else if (/\.js/.test(path)) { return list[list.length - 1].replace('.js', '') } else { return list[list.length - 1].replace('.vue', '') } } function render(files, tag) { let components = [] let infos = [] files.forEach(function (file) { const name = getComponentName(file) const content = fs.readFileSync(file, 'utf-8') let json = {} try { json = yaml.safeLoad(content) } catch (e) { console.log('错误', e) } let rs = { name: name, importName: _camelCase(name), deprecatedInfo: json.deprecated_info || '', props: json.props, events: json.events, methods: json.methods, slots: json.slots, extra: json.extra, after_extra: json.after_extra, tags: json.tags, items: json.items, json: json } let item = { name: name, icon: json.icon, color: json.color, importName: json.importName, items: json.items, } if (json.category) { item = { ...item, category_en: json.category.en, 'category_zh-CN': json.category['zh-CN'], category_order: json.category_order || 998 } } if (!tag && item.icon && item.name) { const isDir = fs.existsSync(path.join(__dirname, `../src/demos/${item.name}/`)) item.hasHome = isDir gComponents.push(item) } if (json.icon) { infos.push({ name, icon: json.icon, importName: json.importName, metas: json }) } if (tag && json.tags && json.tags.en && json.tags.en.indexOf(tag) === -1) { return } if (tag && json.tags && !json.tags.en) { return } if (tag && !json.tags) { return } if (json.icon) { rs.icon = json.icon } if (json.color) { rs.color = json.color } if (json.import_code) { rs.import_code = json.import_code } rs.status = json.status || 'maintaining' if (rs.icon && rs.name) { components.push(rs) } else {} }) if (!tag) { gComponents = _.uniqBy(gComponents, 'name') fs.writeFileSync(getPath('../src/datas/vux_component_list.json'), JSON.stringify(gComponents, null, 2)) } buildChanges(infos) buildChanges(infos, 'en') let langs = ['zh-CN', 'en'] for (var i = 0; i < langs.length; i++) { let lang = langs[i] let docs = '' if (!tag) { // 生成docs docs += `--- nav: ${lang} --- \n## ${t('组件列表')}` } else { // 生成docs docs += `--- nav: ${lang} --- \n## ${tag}` } components.forEach(function (one) { docs += '\n\n---\n' docs += `\n### ${one.importName}_COM` docs += `\n${t('编辑文档', lang)}` docs += `\n  ${t("进入demo页面", lang)}` docs += `\n  ${t("demo 原始链接", lang)}` docs += `\n  ${t("demo源码", lang)}\n` if (one.status === 'deprecated') { docs += `\n

${t('该组件已经停止维护。')}${one.deprecatedInfo ? one.deprecatedInfo[lang] : ''}

\n` } if (one.import_code) { if (one.import_code !== ' ') { docs += '\n``` js' docs += `\n${one.import_code}` docs += '\n```\n' } } else { docs += '\n``` js' docs += `\nimport { ${one.importName} } from 'vux'` docs += '\n```\n' } if (one.extra && typeof one.extra === 'string' && lang === 'zh-CN') { docs += '\n' + one.extra + '\n' } if (one.extra && typeof one.extra === 'object' && one.extra[lang]) { docs += '\n' + one.extra[lang] + '\n' } if (one.props || one.slots) { docs = getComponentInfo(one, lang, docs) } if (one.items) { docs = getComponentInfo({ name: one.name, hideDemo: one.items.length > 1, props: one.json[one.items[0]].props, sub_extra: one.json[one.items[0]].sub_extra, slots: one.json[one.items[0]].slots, events: one.json[one.items[0]].events, methods: one.json[one.items[0]].methods }, lang, docs, one.items[0]) docs = getComponentInfo({ name: one.name, json: one.json, hideDemo: one.items.length > 2, props: one.json[one.items[1]].props, sub_extra: one.json[one.items[1]].sub_extra, slots: one.json[one.items[1]].slots, events: one.json[one.items[1]].events, methods: one.json[one.items[1]].methods }, lang, docs, one.items[1]) if (one.items.length === 3) { docs = getComponentInfo({ name: one.name, json: one.json, props: one.json[one.items[2]].props, sub_extra: one.json[one.items[2]].sub_extra, slots: one.json[one.items[2]].slots, events: one.json[one.items[2]].events, methods: one.json[one.items[2]].methods }, lang, docs, one.items[2]) } } // after extra if (one.after_extra) { docs += '\n' + (one.after_extra[lang] || one.after_extra) + '\n' } /** docs += `\n
Playground is coming soon
\n` */ docs += `\n\n\n` }) } } glob(getPath("../src/styles/*.yml"), {}, function (er, files) { let infos = [] files.forEach(function (file) { const content = fs.readFileSync(file, 'utf-8') const info = yaml.safeLoad(content) infos.push(info) }) buildCssDocs(infos) }) function buildCssDocs(infoList) { let docs = `--- nav: zh-CN ---\n` infoList.forEach(function (one) { docs += `\n## ${one.name.en}\n` docs += `${one.doc}\n\n` }) fs.writeFileSync(getPath(`../docs/zh-CN/css.md`), docs) } function getVersion(version) { let rs = '' if (!version) { rs = '' } else if (version === 'next') { rs = '下个版本' } else { rs = version } return `${rs}` } function getComponentInfo(one, lang, docs, name) { if (one.props || one.slots) { if (name) { docs += `\n${_camelCase(name)}\n` } } if (one.sub_extra) { docs += `\n${one.sub_extra}\n` } if (one.props) { // prop title docs += `\nProps\n` docs += `\n| ${t('名字')} | ${t('类型')} | ${t('默认')} | version | ${t('说明')} | |-------|-------|-------|-------|-------| ` for (let i in one.props) { let prop = one.props[i][lang] docs += `| ${getKeyHTML(i)} | ${getTypeHTML(one.props[i].type)} | ${getColorHTML(one.props[i])} | ${getVersion(one.props[i].version)} | ${prop} |\n` } } if (one.slots) { // slot title docs += `\nSlots\n` docs += `\n| ${t('名字')} | ${t('说明')} | ${t('版本')} | |-------|-------|-------| ` for (let i in one.slots) { let slot = one.slots[i][lang] docs += `| ${getKeyHTML(i)} | ${slot} | ${getVersion(one.slots[i].version)} |\n` } } if (one.events) { // slot title docs += `\nEvents\n` docs += `\n| ${t('名字')} | ${t('参数')} | ${t('说明')} | ${t('版本')} | |-------|-------|-------|-------| ` for (let i in one.events) { let intro = one.events[i][lang] let params = one.events[i]['params'] docs += `| ${getKeyHTML(i)} | ${params || ' '} | ${intro} | ${getVersion(one.events[i].version)} |\n` } } if (one.methods) { // slot title docs += `\nMethods\n` docs += `\n| ${t('名字')} | ${t('参数')} | ${t('说明')} | ${t('版本')} | |-------|-------|-------|-------| ` for (let i in one.methods) { let intro = one.methods[i][lang] let params = one.methods[i]['params'] docs += `| ${getKeyHTML(i)} | ${params || ' '} | ${intro} |${getVersion(one.methods[i].version)} | \n` } } // docs += `
` // docs += `\n` // docs += `\n\nDemo\n` // docs += `\n
进入demo页面
\n` docs += `\n` // docs += `\n\nDemo\n` if (one.name || name) { if (one.hideDemo !== true) { docs += `\n${t("进入demo页面")}\n` } } // do not show change log in component list if (one.json && one.json.changes && one.hideDemo !== true && false) { let lastestVersion = Object.keys(one.json.changes)[0] docs += `\n
Changes (${lastestVersion})\n` docs += `\n` } return docs } function getKeyHTML(key) { return `${key}` } function getTypeHTML(type) { type = type || 'String' if (/,/.test(type)) { const list = type.split(',').map(function (one) { return one.replace(/^\s+|\s+$/g, '') }).map(function (one) { return `${one}` }) return list.join('
') } else { return `${type}` } } function getChangeTagHTML(str, fontSize = '15px') { const _split = str.split(']') const type = _split[0].replace('[', '') const content = _split[1] return `
  • ${type} ${content}
  • ` } function getColorHTML(one) { one.default = typeof one.default === 'undefined' ? '' : one.default let value = one.default if (value === false) { return 'false' } if (!/#/.test(value)) { return value } else { return `${value}` } } function t(key, lang) { if (lang === 'zh-CN') { return key } return tMaps[key] || key } /** function transform (object, name) { let rs = { en: {}, 'zh-CN': {} } for(let i in object) { rs['en'][`vux.${name}.${i}`] = object[i]['en'] rs['zh-CN'][`vux.${name}.${i}`] = object[i]['zh-CN'] } return rs } **/ function camelCase(input) { let str = input.toLowerCase().replace(/-(.)/g, function (match, group1) { return group1.toUpperCase(); }); str = str.replace(/_(.)/g, function (match, group1) { return group1.toUpperCase(); }); return str } function _camelCase(input) { let str = camelCase(input) return str.slice(0, 1).toUpperCase() + str.slice(1) } function parseChange(str) { str = str.replace(/#(\d+)\s?/g, function (a, b) { return `#${b} ` }) str = str.replace(/@(\w+)\s?/g, function (a, b) { return `${a}` }) return str } const strMap = { en: 'not release yet', 'zh-CN': '未发布' } function parseTag(firstTag, tag, lang) { if (tag === 'next') { return `${tag} (${strMap[lang]})` } return tag } function buildChanges(infos, lang = 'zh-CN') { const toolInfos = require(getPath('../src/tools/changes.json')) const pluginInfos = require(getPath('../src/plugins/changes.json')) const directiveInfos = require(getPath('../src/directives/changes.json')) infos = infos.concat(toolInfos) infos = infos.concat(pluginInfos) infos = infos.concat(directiveInfos) let rs = {} infos.forEach(one => { let name = one.name let metas = one.metas if (metas && metas.changes) { for (let i in metas.changes) { if (!rs[i]) { rs[i] = {} } rs[i][name] = metas.changes[i][lang] } } }) const titleMapSum = { en: 'releases', 'zh-CN': '发布日志' } const suffixMap = { en: ' | VUX - Vue.js Mobile UI Component Framework', 'zh-CN': ' | VUX - 基于 WeUI 和 Vue 的移动端组件库' } let str = `--- title: VUX ${titleMapSum[lang]}${suffixMap[lang]} ---\n # VUX ${titleMapSum[lang]}` rs = sortObj(rs, { sort: function (a, b) { if (a === 'next') { return -1 } if (b === 'next') { return 1 } return semver.gt(a, b) ? -1 : 1 } }) let firstTag = Object.keys(rs)[0] let releases = {} let n = 0 for (let i in rs) { releases[i] = {} // releases += `\n # ${i}\n` if (/next/.test(parseTag(firstTag, i, lang))) { str += `\n

    ${parseTag(firstTag, i, lang)}

    \n` } else { str += `\n

    ${parseTag(firstTag, i)}

    \n` } for (let j in rs[i]) { // releases += `\n## ${_camelCase(j)}\n` releases[i][j] = [] rs[i][j] && rs[i][j].forEach(one => { releases[i][j].push(one) }) if (n <= 2) { str += `\n### ${_camelCase(j)}\n` str += `` str += `\n` } } n++ } const titleMap = { en: 'released', 'zh-CN': '发布日志' } for (let i in releases) { const release = releases[i] let file = getPath(`../docs/${lang}/changelog/${i}.md`) let htmlFile = getPath(`../docs/changes/${lang}/${i}.html`) let data = { lang: lang, version: i, title: `${i}发布`, components: [] } let content = `--- title: VUX ${_camelCase(i)} ${titleMap[lang]}${suffixMap[lang]} ---\n # VUX ${_camelCase(i)} ${titleMap[lang]}` for (let j in release) { content += `\n

    ${_camelCase(j)}

    \n` content += '' data.components.push({ name: j, list: release[j].map(one => { return { change: one } }) }) fs.writeFileSync(file, content) } } str += '\n' fs.writeFileSync(getPath(`../docs/${lang}/changelog/changelog.md`), str) } ================================================ FILE: build/build-main.js ================================================ 'use strict' const fs = require('fs') const path = require('path') const maps = require(path.resolve(__dirname, '../src/components/map.json')) delete maps.NOTICE const target = path.resolve(__dirname, '../index.js') let str = `// THIS FILE IS ONLY FOR IDE ENTRY CHECKING NOT FOR REAL USAGE\n\n` str += `console.warn('VUX: 如果你看到这一行,说明 vux-loader 配置有问题或者代码书写规范的原因导致无法解析成按需引入组件,会导致打包体积过大。请升级到最新版本 vux-loader,建议开启 eslint(standard)。')\n\n` for (let i in maps) { str += `import ${i} from './${maps[i]}'\n` } str += '\nexport {\n' for (let i in maps) { str += ` ${i}${i === 'XTextarea' ? '' : ','}\n` } str += '}' fs.writeFileSync(target, str) ================================================ FILE: build/build-styles.js ================================================ const postcss = require('postcss') const syntax = require('postcss-less') const path = require('path') const fs = require('fs') const less = require('less') const shell = require('shelljs') const distPath = path.resolve(__dirname, `../dist/styles/`) shell.rm('-rf', distPath) shell.mkdir('-p', distPath) var pkg = require(path.join(__dirname, '../package.json')) const p = path.resolve(__dirname, '../src/styles/') fs.readdir(p, function (err, files) { if (err) { throw err } files.filter(function (file) { return !fs.statSync(path.join(p, file)).isDirectory() && /less/.test(file) && !/index|variable|theme/.test(file) }).forEach(function (file) { parse(file) }) }) const getPath = function (name) { return path.resolve(__dirname, `../dist/styles/${name}`) } function parse (file) { var code = fs.readFileSync(path.resolve(__dirname, `../src/styles/${file}`), 'utf-8') less.render(code, { compress: true, paths: [path.resolve(__dirname, '../src/styles')] },function (e, output) { if (e) { throw e } else { postcss([require('autoprefixer')(['iOS >= 7', 'Android >= 4.1'])]) .process(output.css, { syntax: syntax }) .then(function (result) { const dist = getPath(file.replace('less', 'css')); fs.writeFileSync(dist, result.css); }) .catch(function(e){ console.log(e) }) } }) } ================================================ FILE: build/build.js ================================================ // https://github.com/shelljs/shelljs require('./check-versions')() process.env.NODE_ENV = 'production' var ora = require('ora') var path = require('path') var chalk = require('chalk') var shell = require('shelljs') var webpack = require('webpack') var config = require('../config') var webpackConfig = require('./webpack.prod.conf') var spinner = ora('building for production...') spinner.start() var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) shell.rm('-rf', assetsPath) shell.mkdir('-p', assetsPath) shell.config.silent = true shell.cp('-R', 'static/*', assetsPath) shell.config.silent = false webpack(webpackConfig, function (err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, chunks: false, chunkModules: false }) + '\n\n') console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) }) ================================================ FILE: build/check-versions.js ================================================ var chalk = require('chalk') var semver = require('semver') var packageConfig = require('../package.json') function exec (cmd) { return require('child_process').execSync(cmd).toString().trim() } var versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node }, { name: 'npm', currentVersion: exec('npm --version'), versionRequirement: packageConfig.engines.npm } ] module.exports = function () { var warnings = [] for (var i = 0; i < versionRequirements.length; i++) { var mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { warnings.push(mod.name + ': ' + chalk.red(mod.currentVersion) + ' should be ' + chalk.green(mod.versionRequirement) ) } } if (warnings.length) { console.log('') console.log(chalk.yellow('To use this template, you must update following to modules:')) console.log() for (var i = 0; i < warnings.length; i++) { var warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } } ================================================ FILE: build/dev-doc.js ================================================ const watch = require('node-watch') const path = require('path') const dir = path.resolve(__dirname, '../src/components') watch(dir, function (filename) { if (/metas\.yml/.test(filename)) { delete require.cache[require.resolve('./build-docs.js')] require('./build-docs.js') } }) ================================================ FILE: build/document.js ================================================ 'use strict' const glob = require("glob") const path = require('path') const yaml = require('js-yaml') const fs = require('fs') function getPath(dir) { return path.join(__dirname, dir) } glob(getPath('../src/**/metas.yml'), {}, function (err, files) { for(let file of files) { console.log(file) let content = fs.readFileSync(file, 'utf-8') let doc = yaml.safeLoad(content) for (let prop in doc.props) { console.log(prop) let info = doc.props[prop] if (!info['en'] || !info['zh-CN']) { throw('缺少多语言文档') } } } }) ================================================ FILE: build/fetch-github.js ================================================ 'use strict' const fetch = require('node-fetch') const path = require('path') const fs = require('fs') const relesesPath = path.resolve(__dirname, '../docs/releases.json') const get = async function () { const res =await fetch('https://api.github.com/repos/airyland/vux/releases') return await res.json() } get().then(res => { fs.writeFileSync(relesesPath, JSON.stringify(res, null, 2)) }) ================================================ FILE: build/less.js ================================================ const less = require('less') const path = require('path') const fs = require('fs') const file = path.resolve(__dirname, '../src/styles/reset.less') less.render(fs.readFileSync(file, 'utf8'), { paths: [path.resolve(__dirname, '../src/styles')] },function (e, output) { console.log(e, output) }) ================================================ FILE: build/pre-publish.js ================================================ const glob = require('glob') const path = require('path') const fs = require('fs') function getPath(dir) { return path.join(__dirname, dir) } const argv = require('yargs').argv console.log('argv', argv) const version = argv.version if (!version) { throw('no version specified') } const pkg = require(getPath('../package.json')) const pkgContent = fs.readFileSync(getPath('../package.json'), 'utf-8') fs.writeFileSync(getPath('../package.json'), pkgContent.replace(pkg.version, version.replace('v', ''))) glob(getPath('../src/**/**/metas.yml'), {}, function (err, files) { let rs = [] files.forEach(function (file) { console.log(file) let content = fs.readFileSync(file, 'utf-8') content = content.split('\n') content = content.map(line => { console.log(line) if (/next:/.test(line)) { return line.replace('next', `${version}`) } else if (/version: next/.test(line)) { return line.replace('next', `${version}`) } return line }) fs.writeFileSync(file, content.join('\n')) }) }) ================================================ FILE: build/simulate.sh ================================================ mkdir node_modules/vux mkdir node_modules/vux/src rm node_modules/vux/package.json cp package.json node_modules/vux/ cp -r src/ node_modules/vux/src/ ================================================ FILE: build/umd-helper.js ================================================ 'use strict' const webpack = require('webpack') const mkdirp = require('mkdirp') const fs = require('fs') const touch = require('touch') const path = require('path') var co = require('co') var thunkify = require('thunkify') var build = thunkify(function (config, name, cb) { let start = new Date().getTime() console.log(`start:${name}`) webpack(config, function (err, stats) { if (!config.entry.vux) { mkdirp.sync(path.resolve(config.output.path)) touch.sync(path.resolve(config.output.path, './index.min.css')) } var jsonStats = stats.toJson() var assets = jsonStats.assets[0] var size = assets.size / 1024 size = size.toFixed(2) + 'k' console.log('size', size) console.log('time', (new Date().getTime() - start) / 1000 + 's') console.log('----------------') cb && cb(err) }) }) var ExtractTextPlugin = require('extract-text-webpack-plugin') module.exports = { build, ExtractTextPlugin } ================================================ FILE: build/utils.js ================================================ 'use strict' const path = require('path') const config = require('../config') const ExtractTextPlugin = require('extract-text-webpack-plugin') const pkg = require('../package.json') exports.assetsPath = function (_path) { const assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } exports.cssLoaders = function (options) { options = options || {} const cssLoader = { loader: 'css-loader', options: { sourceMap: options.sourceMap } } var postcssLoader = { loader: 'postcss-loader', options: { sourceMap: options.sourceMap } } // generate loader string to be used with extract text plugin function generateLoaders (loader, loaderOptions) { const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified // (which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') } } // Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function (options) { const output = [] const loaders = exports.cssLoaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output } exports.createNotifierCallback = function () { const notifier = require('node-notifier') return (severity, errors) => { if (severity !== 'error') { return } const error = errors[0] const filename = error.file && error.file.split('!').pop() notifier.notify({ title: pkg.name, message: severity + ': ' + error.name, subtitle: filename || '', icon: path.join(__dirname, 'logo.png') }) } } ================================================ FILE: build/vue-loader.conf.js ================================================ var utils = require('./utils') var config = require('../config') var isProduction = process.env.NODE_ENV === 'production' module.exports = { loaders: utils.cssLoaders({ sourceMap: isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap, extract: isProduction }), postcss: [ require('autoprefixer')({ browsers: ['iOS >= 7', 'Android >= 4.1'] }) ] } ================================================ FILE: build/vux-config.js ================================================ 'use strict' const path = require('path') const fs = require('fs') const demoPath = path.resolve(__dirname, '../src/demo_list.json') const glob = require('glob') const match = path.join(__dirname, '../src/demos/*/*.vue') const dir = path.join(__dirname, '../src/demos/') const yaml = require('js-yaml') const reg = /]*>([\s\S]*?)<\/demo>/g const argv = require('yargs').argv argv.simulate = argv.simulate || false const isInclude = function (name, include) { let list = include.split(',') for (let i = 0; i < list.length; i++) { if (name.includes(list[i])) { return true } } return false } module.exports = { options: { vuxDev: !argv.simulate, // true vuxSetBabel: argv.simulate, // false vuxWriteFile: false, env: 'dev' }, plugins: [ 'vux-ui', 'inline-manifest', { name: 'duplicate-style', options: { cssProcessorOptions : { safe: true, zindex: false, autoprefixer: { add: true, "browsers": [ "iOS >= 7", "Android >= 4.1" ] } } } }, { name: 'progress-bar', envs: ['development'] }, { name: 'js-parser', test: /main\.js/, fn: function (source) { this.addDependency(demoPath) let list = fs.readFileSync(demoPath, 'utf-8') list = JSON.parse(list) let groupList = [] let dirs = {} const listDir = glob.sync(match).filter(one => { return !one.includes('_index.vue') }).map(one => { one = path.normalize(one) const component = one.replace(dir, '').replace('.vue', '').replace(path.sep, '/') let title = { en: 'EXAMPLE', 'zh-CN': 'EXAMPLE' } let order = 999 const urlpath = '/components/' + component let name = one.replace(dir, '').split(path.sep)[0] if (!Array.isArray(dirs[name])) { dirs[name] = [] } const code = fs.readFileSync(path.join(__dirname, `../src/demos/${component}.vue`), 'utf-8') const rs = code.match(reg) if (rs) { let meta = yaml.safeLoad(rs[0].replace('', '').trim().replace('', '')) if (typeof meta.title === 'string') { title.en = meta.title title['zh-CN'] = meta.title } else if (typeof meta.title === 'object') { title.en = meta.title.en title['zh-CN'] = meta.title['zh-CN'] } if (meta.order) { order = meta.order * 1 } } dirs[name].push({ title, path: urlpath, order }) return component + '#' + urlpath }) list = list.concat(listDir) for (let dir in dirs) { dirs[dir].sort((a, b) => { return a.order > b.order ? 1 : -1 }) const file = ` ` fs.writeFileSync(path.join(__dirname, `../src/demos/${dir}/_index.vue`), file) groupList.push(`${dir}/_index#/components/${dir}/home`) } list = list.concat(groupList) if (argv.demo) { list = list.filter(item => { return item.indexOf(argv.demo) > -1 }) } let str = [] list.forEach(one => { let filename = one let path = `/component/${toDash(one)}` if (/#/.test(one)) { filename = one.split('#')[0] path = one.split('#')[1] } let shouldIncluded = true if (path !== '/demo' && argv.env && argv.env.include) { shouldIncluded = isInclude(path, argv.env.include) } if (shouldIncluded) { str.push(`{ path: '${path}', component: () => import('./demos/${filename}.vue').then(m => m.default) }`) } }) // 404 page str.push(`{ path: '*', component: () => import('./demos/NotFoundComponent.vue').then(m => m.default) }`) str = `[${str.join(',\n')}]` source = source.replace('const routes = []', 'const routes = ' + str) return source } }, { name: 'i18n', vuxStaticReplace: false, staticReplace: false, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] }, { name: 'less-theme', path: 'src/theme.less' } ] } function toDash(str) { return str.replace(/([A-Z])/g, function (m, w) { return '-' + w.toLowerCase(); }).replace('-', '') } ================================================ FILE: build/webpack.base.conf.js ================================================ var path = require('path') var config = require('../config') var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') var fs = require('fs') var vueLoaderConfig = require('./vue-loader.conf') var argv = require('yargs').argv argv.simulate = argv.simulate || false function resolve (dir) { return path.join(__dirname, '..', dir) } const webpackConfig = { entry: { app: './src/main.js' }, output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { extensions: ['.js', '.vue', '.json'], modules: [ resolve('src'), resolve('node_modules') ], alias: { 'vue$': 'vue/dist/vue.common.js', 'src': resolve('src'), 'assets': resolve('src/assets'), 'components': resolve('src/components'), '@': resolve('src') } }, module: { rules: [ { test: /\.(js|vue)$/, loader: 'eslint-loader', enforce: "pre", include: [resolve('src'), resolve('test')], exclude: /vue.vux.js$/, options: { formatter: require('eslint-friendly-formatter') } }, { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { test: /\.(yaml|yml)$/, loader: 'js-yaml-loader' }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', query: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', query: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] } } const vuxLoader = require('vux-loader') const vuxConfig = require('./vux-config') module.exports = vuxLoader.merge(webpackConfig, vuxConfig) ================================================ FILE: build/webpack.dev.conf.js ================================================ 'use strict' const utils = require('./utils') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const HtmlWebpackPlugin = require('html-webpack-plugin') const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') const portfinder = require('portfinder') const devWebpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, // cheap-module-eval-source-map is faster for development devtool: config.dev.devtool, // these devServer options should be customized in /config/index.js devServer: { clientLogLevel: 'warning', historyApiFallback: true, hot: true, compress: true, host: process.env.HOST || config.dev.host, port: process.env.PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? { warnings: false, errors: true, } : false, publicPath: config.dev.assetsPublicPath, proxy: config.dev.proxyTable, quiet: true, // necessary for FriendlyErrorsPlugin watchOptions: { poll: config.dev.poll, } }, plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env') }), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }), ] }) module.exports = new Promise((resolve, reject) => { portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => { if (err) { reject(err) } else { // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add FriendlyErrorsPlugin devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ compilationSuccessInfo: { messages: [`Your application is running here: http://${config.dev.host}:${port}`], }, onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined })) resolve(devWebpackConfig) } }) }) ================================================ FILE: build/webpack.prod.conf.js ================================================ var path = require('path') var config = require('../config') var utils = require('./utils') var webpack = require('webpack') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') var ExtractTextPlugin = require('extract-text-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : require('../config/prod.env') var argv = require('yargs').argv var webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, devtool: false, // config.build.productionSourceMap ? '#source-map' : false, output: { path: config.build.assetsRoot, publicPath: argv.cdn ? 'https://upcdn.vux.li/demos/v2/' : './', filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ 'process.env': env }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // extract css into its own file new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css') }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: process.env.NODE_ENV === 'testing' ? 'index.html' : config.build.index, template: 'index.html', inject: true, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true // more options: // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency' }), // split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function (module, count) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }) ] }) if (config.build.productionGzip) { var CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) } if (config.build.bundleAnalyzerReport) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } module.exports = webpackConfig ================================================ FILE: build/webpack.test.conf.js ================================================ // This is the webpack config used for unit tests. var utils = require('./utils') var webpack = require('webpack') var merge = require('webpack-merge') var baseConfig = require('./webpack.base.conf') var webpackConfig = merge(baseConfig, { // use inline sourcemap for karma-sourcemap-loader module: { rules: utils.styleLoaders() }, devtool: '#inline-source-map', resolveLoader: { alias: { // necessary to to make lang="scss" work in test when using vue-loader's ?inject option // see discussion at https://github.com/vuejs/vue-loader/issues/724 'scss-loader': 'sass-loader' } }, plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/test.env') }) ] }) // no need for app entry during tests delete webpackConfig.entry const vuxLoader = require('vux-loader') // set i18n dynamic to false module.exports = vuxLoader.merge(webpackConfig, { plugins: [{ name: 'i18n', vuxStaticReplace: true, staticReplace: true, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] }] }) ================================================ FILE: circle.yml ================================================ machine: node: version: 5.3.0 ================================================ FILE: config/dev.env.js ================================================ var merge = require('webpack-merge') var prodEnv = require('./prod.env') module.exports = merge(prodEnv, { NODE_ENV: '"development"' }) ================================================ FILE: config/index.js ================================================ 'use strict' // Template version: 1.2.3 // see http://vuejs-templates.github.io/webpack for documentation. const path = require('path') module.exports = { dev: { // Paths assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {}, // Various Dev Server settings host: 'localhost', // can be overwritten by process.env.HOST port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: false, errorOverlay: true, notifyOnErrors: true, poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- // Use Eslint Loader? // If true, your code will be linted during bundling and // linting errors and warnings will be shown in the console. useEslint: true, // If true, eslint errors and warnings will also be shown in the error overlay // in the browser. showEslintErrorsInOverlay: false, /** * Source Maps */ // https://webpack.js.org/configuration/devtool/#development devtool: 'eval-source-map', // If you have problems debugging vue-files in devtools, // set this to false - it *may* help // https://vue-loader.vuejs.org/en/options.html#cachebusting cacheBusting: true, // CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected, // just be aware of this issue when enabling this option. cssSourceMap: false, }, build: { // Template for index.html index: path.resolve(__dirname, '../docs/demos/temp/index.html'), // Paths assetsRoot: path.resolve(__dirname, '../docs/demos/temp'), assetsSubDirectory: 'static', assetsPublicPath: './', /** * Source Maps */ productionSourceMap: false, // https://webpack.js.org/configuration/devtool/#production devtool: '#source-map', // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report } } ================================================ FILE: config/prod.env.js ================================================ module.exports = { NODE_ENV: '"production"' } ================================================ FILE: config/test.env.js ================================================ var merge = require('webpack-merge') var devEnv = require('./dev.env') module.exports = merge(devEnv, { NODE_ENV: '"testing"' }) ================================================ FILE: docs/404.md ================================================ Page is being translated... ================================================ FILE: docs/about/version.md ================================================ ## 版本发布

    `0.x` 版本文档不完整,并且已经不再维护。请更新或者直接使用`2.x`。

    当前开发分支为 [v2](https://github.com/airyland/vux)。 `2.1.0` ~ `3.0.0` 之间版本不会有影响升级的 `break change`,请放心及时更新版本。 ================================================ FILE: docs/compile.js ================================================ 'use strict' const glob = require('glob') const path = require('path') const fs = require('fs') const yaml = require('js-yaml') const hljs = require('highlight.js') const MD = require('markdown-it') const argv = require('yargs').argv let langs = ['en', 'zh-CN'] const t = require('./i18n') const pkg = require('../package.json') const variables = {} const variableMap = {} const variablePath = path.join(__dirname, '../src/styles/variable.less') const variableContent = fs.readFileSync(variablePath, 'utf-8').split('\n') const isInclude = function (name, include) { let list = include.split(',') for (let i = 0; i < list.length; i++) { if (name.includes(list[i])) { return true } } return false } variableContent.forEach((line, index) => { if (/^@/.test(line)) { let temp = line.split(':') let component = temp[0].replace('@', '').split('-')[0] if (component === 'button' && /button-tab/.test(line)) { component = 'button-tab' } if (!variables[component]) { variables[component] = [] } const name = temp[0].replace('@', '') let value = temp[1].replace(';', '').trim().replace(/\/\*(.*?)\*\//, '') if (value.includes('//')) { value = value.split(' ')[0].trim() } let inherited_name = '' let is_inherited = false if (/@/.test(value)) { is_inherited = true inherited_name = value value = variableMap[value.replace('@', '')] } let t = {} if (variableContent[index - 1] === '*/' && /:/.test(variableContent[index - 2])) { let stop = false let i = 2 while (!stop) { const temp = variableContent[index - i] if (/:/.test(temp)) { let pair = temp.split(':') t[pair[0].replace('*', '').trim()] = pair[1].trim() } else { stop = true } i++ } } variables[component].push({ name, value, is_inherited, inherited_name, desc: t }) variableMap[name] = value } }) let contents = [] const getAlternate = function (lang, route) { return langs.filter(_lang => _lang !== lang).map(_lang => { return { rel: 'alternate', hreflang: _lang, href: route.replace(lang, _lang) } }) } const getPath = function (dir) { return path.join(__dirname, dir) } // for faster test with few languages if (argv.langs) { langs = argv.langs.split(',') } // filters routes let include if (argv.include) { include = argv.include } const customContainer = require('markdown-it-container') const md = new MD({ html: true, highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '
    ' +
                   hljs.highlight(lang, str, true).value +
                   '
    '; } catch (__) {} } return '
    ' + md.utils.escapeHtml(str) + '
    '; } }) .use(customContainer, 'tip', { validate: function(params) { return params.trim() === 'tip' } }) .use(customContainer, 'warning', { validate: function(params) { return params.trim() === 'warning' } }) .use(customContainer, 'danger', { validate: function(params) { return params.trim() === 'danger' } }) function getComponentName(path) { let list = path.split('/') if (list[list.length - 1] === 'index.vue' || list[list.length - 1] === 'index.js') { return list[list.length - 2] } else if (list[list.length - 1] === 'metas.yml') { return list[list.length - 2] } else if (/\.json/.test(path)) { return list[list.length - 1].replace('.json', '') } else if (/\.js/.test(path)) { return list[list.length - 1].replace('.js', '') } else { return list[list.length - 1].replace('.vue', '') } } let paths = [] langs.forEach(lang => { const faqs = glob.sync(getPath(`./${lang}/faq/*.md`)) const faqRoutes = [] const commonTitle = `${t('title', lang)}` let faqMd = ` --- title: ${t('faq', lang)} | ${commonTitle} --- # ${t('faq', lang)} ` faqs.forEach(one => { one = '.' + one.replace(__dirname, '') const content = fs.readFileSync(getPath(one), 'utf-8') const titleRs = content.match(/\n#(.*?)\n/) if (titleRs && titleRs[1] && one.indexOf('index.md') === -1) { faqRoutes.push({ title: titleRs[1].trim(), path: one.replace('./', '/').replace('.md', '.html') }) contents.push({ lang, category: '常见问题', title: titleRs[1].trim(), url: one.replace('./', '/').replace('.md', '.html'), content: md.render(content) }) } }) faqRoutes.forEach(one => { faqMd += ` * ${one.title} ` }) fs.writeFileSync(getPath('./src/faq-routes.json'), JSON.stringify(faqRoutes, null, 2)) fs.writeFileSync(getPath(`./${lang}/faq/index.md`), faqMd) }) /** * tools */ langs.forEach(lang => { let toolRoutes = [] const tools = glob.sync(getPath(`./${lang}/tools/*.md`)) tools.forEach(one => { one = '.' + one.replace(__dirname, '') const content = fs.readFileSync(getPath(one), 'utf-8') let titleRs = content.match(/\n#(.*?)\n/) if (titleRs && titleRs[1] && one.indexOf('index.md') === -1) { titleRs[1] = titleRs[1].replace(/#/g, '') toolRoutes.push({ title: titleRs[1].trim(), path: one.replace('./', '/').replace('.md', '.html') }) contents.push({ lang, category: t('Toolkit', lang), title: t(titleRs[1].trim(), lang), url: one.replace('./', '/').replace('.md', '.html'), content: md.render(content) }) } }) fs.writeFileSync(getPath(`./src/tool-routes-${lang}.json`), JSON.stringify(toolRoutes, null, 2)) }) let files = [] langs.forEach(lang => { files = files.concat(glob.sync(getPath(`./${lang}/**/*.md`))) }) files = files.map(file => { return '.' + file.replace(__dirname, '') }) if (include) { files = files.filter(file => { return isInclude(file, include) }) } let str = '' langs.forEach(lang => { str += ` routes.push({ path: '/${lang}/lab/index.html', component: () => import('../${lang}/lab/index.md') }) routes.push({ path: '/${lang}/faq', component: () => import('../${lang}/faq/index.md') }) routes.push({ path: '/${lang}/', component: () => import('../${lang}/README.md') }) routes.push({ path: '/${lang}/about/contributors.html', component: () => import('../${lang}/about/contributors.vue') })` paths.push('/${lang}/') paths.push('/${lang}/faq/') paths.push('/${lang}/about/contributors.html') }) files.forEach(file => { let currentPath = `${file.replace(/^.\//, '/').replace('.md', '.html')}` if (!include || isInclude(currentPath, include)) { paths.push(currentPath) str += ` routes.push({ path: '${currentPath}', component: () => import('.${file}') }) ` } }) // components const components = glob.sync(getPath('../src/components/**/metas.yml')) const colorCode = function (lang, code) { return '
    ' +
                   hljs.highlight(lang, code, true).value +
                   '
    ' } const routesList = require(getPath(`../src/demo_list.json`)) const map = {} routesList.forEach(route => { if (/#/.test(route)) { let pair = route.split('#') map[pair[0]] = pair[1] } }) components.forEach((file) => { const rawMetas = fs.readFileSync(file, 'utf-8') const metas = yaml.safeLoad(rawMetas) const root = getPath('../') const componentName = file.replace(root, '').replace('src/components/', '').replace('/metas.yml', '') const importName = _camelCase(componentName) let importList = [{ componentName: componentName, importName: importName }] if (/meta/.test(componentName)) { return } // 加入样式变量 metas.variables = variables[componentName.replace('x-', '')] // demo 源码 const demoPath = path.join(__dirname, `../src/demos/${importName}.vue`) let demoCode = '' try { demoCode = encodeURIComponent(colorCode('html', fs.readFileSync(demoPath, 'utf-8'))) } catch (e) {} if (metas.items) { importList = [] metas.items.forEach(one=> { importList.push({ componentName: one, importName: _camelCase(one) }) }) } const parseReg = '`(.*?)`' const _localImportCode = `import { ${importList.map(one => one.importName).join(', ')} } from 'vux' export default { components: { ${importList.map(one => one.importName).join(',\n ')} } }` const localImportCode = colorCode('js', _localImportCode) let exampleCode = '' if (metas.example) { exampleCode = colorCode('html', metas.example) } if (metas.tips) { langs.forEach(lang => { if (metas.tips[lang]) { for (let i = 0; i < metas.tips[lang].length; i++) { metas.tips[lang][i].a = metas.tips[lang][i].a .replace(/```\s+(css|js|bash|html)/g, '``` $1\n') .replace(/```/g, '\n```') metas.tips[lang][i].a = md.render(metas.tips[lang][i].a) } } }) // add tip on last component if (metas.items) { const keys = metas.items const last = keys[keys.length - 1] metas[last].tips = metas.tips } } // render child component description if (metas.items) { metas.items.forEach(item => { if (metas[item].description) { metas[item].description = md.render(metas[item].description) } }) } langs.forEach(lang => { // find demos let demos = [] const reg = /]*>([\s\S]*?)<\/demo>/g let demoHeight = '270px' let commonNoBackgroundColor = 'true' let demosDir = getPath(`../src/demos/${componentName}/`) if (fs.existsSync(demosDir)) { const list = glob.sync(demosDir + '/*.vue').filter(one => !one.includes('_index.vue')) .map(one => one.replace(getPath(`../src/demos/`), '')) .map(one => { const route = map[one.replace('.vue', '')] const code = fs.readFileSync(getPath(`../src/demos/`) + one, 'utf-8') let height = demoHeight let noBackgroundColor = commonNoBackgroundColor let order = 999 const rs = code.match(reg) let title = 'EXAMPLE' if (rs) { let meta = yaml.safeLoad(rs[0].replace('\n', '').replace('', '')) if (typeof meta.title === 'string') { title = meta.title } else if (typeof meta.title === 'object') { title = meta.title[lang] } if (meta.height) { height = meta.height + 'px' } if (meta.noBackgroundColor === false) { noBackgroundColor === 'false' } if (meta.order) { order = meta.order * 1 } } return { file: one.replace('.vue', ''), title, route: route, code: '
    ' +
                     hljs.highlight('html', code.replace(reg, ''), true).value +
                     '
    ', oriHeight: height, height: height, noBackgroundColor, order } }) demos = list.sort((a, b) => { return a.order > b.order ? 1 : -1 }) } let url = `https://vux.li/demos/v2/#/component/${componentName}` if (demos.length) { url = `https://vux.li/demos/v2/#/components/${componentName}/home` } // toc let toc = [] if (demos.length) { toc.push({ title: t('Intro', lang), anchor: 'intro' }) toc.push({ title: t('Install', lang), anchor: 'install' }) toc.push({ title: t('Examples', lang), anchor: 'examples', list: demos.map(one => { return { title: one.title, anchor: 'examples:' + one.title } }).slice(0, 5) }) if (metas.items) { toc.push({ title: 'API', anchor: 'api', list: metas.items.map(item => { return { title: `<${item}>`, anchor: 'components:' + item } }) }) } else { toc.push({ title: 'API', anchor: 'api' }) } toc.push({ title: t('Tips', lang), anchor: 'tips' }) toc.push({ title: t('Contributors', lang), anchor: 'contributors' }) toc.push({ title: t('Changelog', lang), anchor: 'changelog' }) } const needImport = metas.need_import === false ? false : true const gitMetas = require(`./${lang}/components/${componentName}_git_metas.json`) // ldjson const ldjson = { "@context": "http://schema.org/", "@type": "SoftwareApplication", "name": `Vue Component ${componentName}`, "screenshot": "", "description": "Vue.js component for VUX", "url": `/${lang}/components/${componentName}.html`, "applicationCategory": t('Component', lang), "author": { "@type": "Organization", "name": "VUX", "url": "https://vux.li" }, "downloadUrl": "https://vux.li", "softwareVersion": pkg.version, "aggregateRating": { "@type": "AggregateRating", "ratingValue": 5, "reviewCount": gitMetas.commitCount }, "operatingSystem": "iOS >= 7, Android >= 4.1", "offers": { "@type": "Offer", "price": 0, "priceCurrency": t('priceCurrency', lang), "availability": "http://schema.org/InStock" } } contents.push({ lang: lang, category: t('Components', lang), title: importName, title_alias: componentName, url: `/${lang}/components/${componentName}.html` }) const list = ['Props', 'Events', 'Slots', 'Functions'] list.forEach(block => { const key = block.toLowerCase() if (metas[key]) { for (let prop in metas[key]) { contents.push({ lang: lang, category: t('Component', lang) + ' ' + importName + ' ' + t(block.replace(/s$/, ''), lang), title: prop, url: `/${lang}/components/${componentName}.html#${key}-${prop}`, content: metas[key][prop][lang] }) } } }) let _globalImportCode = `// ${t('globally register', lang)}\n\nimport Vue from 'vue'\nimport { ${importList.map(one => one.importName).join(', ')} } from 'vux'\n\n` const urlWithNoTransition = `https://vux.li/demos/v2?locale=${lang}&transition=none/#/component/${componentName}` importList.forEach(one => { _globalImportCode += `Vue.component('${one.componentName}', ${one.importName})\n` }) const globalImportCode = colorCode('js', _globalImportCode) let str = ` ` fs.writeFileSync(getPath(`./${lang}/components/${componentName}.vue`), str) }) }) langs.forEach(lang => { glob.sync(getPath(`./${lang}/components/*.vue`)).forEach(component => { component = '..' + component.replace(__dirname, '') const name = component.replace(`../${lang}/components/`, '').replace('.vue', '') if (!include || isInclude(name, include)) { str += ` routes.push({ path: '/${lang}/components/${name}.html', component: () => import('${component}') }) ` paths.push(`/${lang}/components/${name}.html`) } }) }) if (include) { paths = paths.filter(path => { return isInclude(path, include) }) } str += ` routes.push({ path: '*', component: () => import('../404.md') }) ` const ori = fs.readFileSync(getPath('./src/index.js'), 'utf-8') fs.writeFileSync(getPath('./src/_index.js'), ori.replace('const routes = []', `const routes = []\n${str}`)) fs.writeFileSync(getPath('./src/routes.json'), JSON.stringify(paths, null, 2)) fs.writeFileSync(getPath('./sitemap.txt'), paths.map(path => `https://doc.vux.li${path}`).join('\n')) fs.writeFileSync(getPath('./algolia.json'), JSON.stringify(contents, null, 2)) function camelCase(input) { let str = input.toLowerCase().replace(/-(.)/g, function (match, group1) { return group1.toUpperCase(); }); str = str.replace(/_(.)/g, function (match, group1) { return group1.toUpperCase(); }); return str } function _camelCase(input) { let str = camelCase(input) return str.slice(0, 1).toUpperCase() + str.slice(1) } ================================================ FILE: docs/component-contributors.js ================================================ const shell = require('shelljs') const path = require('path') shell.cd(path.join(__dirname + '../../')) const glob = require('glob') const fs = require('fs') const _ = require('lodash') const componentsPath = path.join(__dirname, '../src/components/**/metas.yml') const components = glob.sync(componentsPath) const format = JSON.stringify({ hash: '%H', authorName: '%an', authorEmail: '%ae', date: '%aI', // subject: '%s' }) components.map(one => one.replace('/metas.yml', '')).forEach(one => { const name = one.split('components/')[1] const metaFile = path.join(__dirname, `./zh-CN/components/${name}_git_metas.json`) const rs = shell.exec(`git log --pretty='format:${format},' -- ${one}`, { silent: true }) let str = `[${rs.stdout.slice(0, -1).replace(/\n/g, ' ').replace(/"/g, '\"')}]` str = JSON.parse(str) const result = { commitCount: str.length, commitMembers: _.uniqBy(str, function (one) { return one.authorName.toLowerCase() }).map(one => { return { count: str.filter(_one => _one.authorName.toLowerCase() === one.authorName.toLowerCase()).length, authorName: one.authorName } }) } result.commitUniqueCount = result.commitMembers.length fs.writeFileSync(metaFile, JSON.stringify(result, null, 2)) fs.writeFileSync(metaFile.replace('zh-CN', 'en'), JSON.stringify(result, null, 2)) }) ================================================ FILE: docs/en/.gitkeep ================================================ ================================================ FILE: docs/en/README.md ================================================ --- title: 关于 VUX ---



    预览地址>>






    ::: warning **敲黑板**:VUX 必须配合 `vux-loader` 使用,如果不使用 vux2 模板请按照文档正确配置。
    **less@3.x** 有严重的兼容问题,请暂时使用 **less@^2.7.3**。 ::: ## 简介 `VUX`(读音 [v'ju:z],同 `views`)是基于`WeUI`和`Vue`(2.x)开发的移动端UI组件库,主要服务于微信页面。 基于`webpack + vue-loader + vux`可以快速开发移动端页面,配合`vux-loader`方便你在`WeUI`的基础上定制需要的样式。 `vux-loader`保证了组件按需使用,因此不用担心最终打包了整个vux的组件库代码。 `VUX`并不完全依赖于`WeUI`,`VUX` 在 `WeUI` 的基础上扩展了多个常用组件,但是尽量保持整体UI样式接近`WeUI`的设计规范。 ::: warning VUX 并不是一个能解决所有场景的完美解决方案(实际上也没有一个方案能解决所有问题),也会出现某些`bug`或者某些特性不支持,所以如果遇到问题麻烦及时**不带情绪正确反馈**,**我们乐于及时解决描述详细方便重现的问题**。
    即使你不直接使用 `VUX` 组件代码, 你依然可以参考 VUX 代码来实现自己的组件库。如果一定程度上帮助到了你,那么维护这个项目也就有所意义。 ::: ## 订阅版本发布通知 请使用微信扫描 ## 提示 ::: tip `VUX` 是`库`而非`框架`,虽然有专用的 `vux-loader`,但并不影响你自由地使用其他组件库或者工具库。

    `VUX` 使用的 CSS 预处理工具是 `less`(同 WeUI),但(利益于 .vue 单文件组件的灵活性)这并不影响你使用 `SASS` 等其他预处理器。

    用以表示该组件库时请使用大写名字 `VUX`,用在说明版本号时使用小写 `vux@2.x`。 ::: ================================================ FILE: docs/en/about/before-using-vux.md ================================================ --- title: 开始使用 VUX 之前 --- # 开始使用 VUX 之前 ::: warning 如果你刚从后端转到前端,可能会被目前前端(表面的)工程复杂度惊吓到,但是放心,使用 `vue-cli` 从模板创建项目可以快速开始编码、构建,仅仅是几行简单的命令不是么? ::: 在使用 VUX 之前需要你至少已经会使用 `Vue`,同时需要你大概了解 `Node.js`,`npm`,`cnpm`,`yarn` 这些东西。 ::: tip 建议 `Node.js` 版本在 `7.6.0` 以上。 ::: ## 相关工具 ### WeUI VUX 样式基于 [WeUI](https://github.com/weui/weui),但是你不必通过使用 VUX 来使用 `WeUI`。简单的页面你可以直接引入 `WeUI` 样式。详细请参考 [WeUI 文档](https://github.com/weui/weui)。 ### Vue VUX 基于 `Vue` 的组件库,意味着你需要有 `Vue` 的基础知识。 如果还没有了解过,建议先看看[Vue 官方文档](https://cn.vuejs.org)。 ### Webpack 如果你直接使用 `vux2` 模板,你可以暂时不用了解。当你需要自定义一些配置时自然就能很快了解了。 [Webpack 文档](https://webpack.js.org) ### vue-cli Vue 官方用于快速创建项目的工具。 ``` js bash npm install vue-cli -g ``` 或者使用 yarn ``` bash yarn global add vue-cli ``` [vue-cli 文档](https://github.com/vuejs/vue-cli) ### vue-loader webpack loader,用于编译 `.vue` 文件,官方模板已经帮你配置好。 [vue-loader 文档](https://vue-loader.vuejs.org) ### vux-loader VUX 组件库的 webpack loader,实现按需加载等功能。它不是替代 `vue-loader` 而是配合 `vue-loader` 使用。如果你使用 vux2 模板,暂不需要手动使用它。 ================================================ FILE: docs/en/about/contributors.vue ================================================ ================================================ FILE: docs/en/about/showcase.md ================================================ --- title: VUX 使用案例 --- # 使用案例 > 如果你的产品在使用`VUX`, 请直接发 PR 修改 `docs/zh-CN/about/showcase.md`(置于列表最后,统一格式:二维码必须无白边框)。 ================================================ FILE: docs/en/about/thanks.md ================================================ --- title: 感谢 --- # Thanks ## VUX 参考或者使用了以下开源项目的代码 + [Vue](https://github.com/vuejs/vue) + [WeUI](https://github.com/weui/weui) + [FrozenUI](https://github.com/frozenui/frozenui) + [Ant Design](https://github.com/ant-design/ant-design) + [Ant Design Mobile](http://github.com/ant-design/ant-design-mobile) + [XScroll](https://github.com/huxiaoqi567/xscroll) + [Ionic](https://github.com/driftyco/ionic) + [SUI Mobile](https://github.com/sdc-alibaba/SUI-Mobile) + [PhotoSwipe](https://github.com/dimsemenov/PhotoSwipe) ## 工具框架 - 世界上最好的语言 `JavaScript` - 啥都能做的前端构建工具 [Webpack](https://webpack.js.org/) - 简单好用的文档展示工具 [Docute](https://docute.js.org/#/) ================================================ FILE: docs/en/contribution/donate.md ================================================ --- title: 捐赠支持 VUX --- ## 捐赠 * [Patreon](https://www.patreon.com/airyland) * [PayPal](https://paypal.me/airyland) * 支付宝 airyland@qq.com ================================================ FILE: docs/en/contribution/issue.md ================================================ --- title: 如何提 issue --- # 如何提 issue ::: tip 使用本项目意味着你也有义务帮助其变得更好。 ::: 不要浪费维护者时间。 不要让维护者帮你学习`Vue`,帮你熟悉`vue-loader`,甚至帮你写代码。 不要认为随便一句话就能让维护者明白你的意思,我们没有你想象的那么厉害。 不要提没有任何意义的、代码中带有业务逻辑不方便重现的Issue。 直接关闭你的`issue`不是对你不满,是你提问题方式不对,没有必要再浪费时间说明为什么要关闭你的`issue`。 马虎的提问,缺少上下文和重现步骤是在浪费彼此时间。 ================================================ FILE: docs/en/contribution/pr.md ================================================ --- title: 如何贡献 --- # 如何贡献 ## 文档更新 如果修改了组件代码,需要在组件目录的`metas.yml`加上changes,直接使用`next`作为版本号(如果已经存在该版本号,则直接添加变更条目即可)。 中括号内为变更类型,可选值 `fix` `enhance` `feature` `change` 比如: ``` yml changes: next: en: - '[fix] fix *** bug #issueId' - '[feature] new feature' zh-CN: - '[fix] 修复 *** bug #issueId' ``` ::: tip 当文档相关的 `PR`被合并后,文档服务器会在`5分钟`内拉取最新代码并执行`npm run build-docs`及`npm run build`实现文档及`demo`更新。 ::: ## 为什么使用 next 为版本号 `next` 表示下个版本,未发布时在 `changelog` 里显示 `next`,提醒用户一些开发中的代码尚未发布。 当进行版本发布时,文档中的 `next` 会被脚本代替成真正的版本号,至此发布完毕。 这样的最大好处是更新代码时可以直接写 `changelog` 不用在意要写哪个版本号,在发布时翻 commit 记录写 `changelog` 是件比较浪费时间的事。 ================================================ FILE: docs/en/contribution/vux-development.md ================================================ --- title: VUX 源码本地运行 --- # VUX 源码本地运行 ::: tip 请更新 `NodeJS` 版本到 `v7.6.0` 以上,`build` 命令逐步使用 `async`。 ::: ``` bash yarn // 使用 yarn.lock 保证依赖版本一致 yarn dev ``` ## 本地查看文档 ``` bash npm run doc:build // 构建文档 cd docs yarn npm run dev // 耗时较长 ``` ## 文档相关命令 > docs 目录下的相关命令,它们是 npm run doc:build 的一部分,但是可以单独运行 ``` bash node compile // 生成文档路由 node component-contributions // 从 git 记录里获取每个组件提交历史 npm run doc:dev // 运行 ``` ## 构建部分文档 文档页面比较多等待 build 命令会长,因此可以只构建部分文档方便快速查看。 ``` bash cd docs node compile --include datetime // 只构建 datetime 相关文档 npm run dev ``` ================================================ FILE: docs/en/css/1px.md ================================================ --- title: 1px 解决方案 --- # 1px 解决方案 ::: tip 1px 方案在 VUX 组件内应用广泛,包括 `Grid`, `ButtonTab`, `XTable`, `XButton`, `Cell` 等等。 利用 `Flexbox` + `1px` 你可以实现复杂的宫格布局。 ::: ## 引入 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` ## 可用类名: - `vux-1px-l` 左边框 - `vux-1px-r` 右边框 - `vux-1px-t` 上边框 - `vux-1px-b` 下边框 - `vux-1px-tb` 上下边框 - `vux-1px` 全边框 ================================================ FILE: docs/en/css/close.md ================================================ --- title: 纯 css close 图标 --- # 纯 css close 图标 ::: tip 关闭图标主要应用于弹窗(dialog)关闭按钮以及弹窗(popup)头部的关闭按钮。 ::: ## 使用 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` ## 类名 - `vux-close` ``` html ``` 可以参考 `XDialog` 演示。 ## 颜色 当你想设置图标颜色时,直接设置 color 即可。 ``` html ``` ================================================ FILE: docs/en/css.md ================================================ --- nav: zh-CN --- ## 1px ::: tip 1px 方案在 VUX 组件内应用广泛,包括 Grid, ButtonTab, XTable, XButton, Cell 等等。

    利用 flexbox + 1px 你可以实现复杂的宫格布局。 ::: 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` 可用类名: - `vux-1px-l` 左边框 - `vux-1px-r` 右边框 - `vux-1px-t` 上边框 - `vux-1px-b` 下边框 - `vux-1px-tb` 上下边框 - `vux-1px` 全边框 ## close ::: tip 关闭图标主要应用于弹窗(popup)的左侧关闭按钮。 ::: 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` 可用类名: - vux-close ``` html ``` 可以参考 `XDialog` 演示。 当你想设置图标颜色时,直接设置 color 即可。 ``` html ``` ================================================ FILE: docs/en/development/i18n.md ================================================ --- title: 国际化 i18n --- # 国际化 ::: warning 暂时只支持配合`vux-loader`使用。 如果你只需要默认的中文组件,那么你可以略过下面说明,只要启用`vux-ui`插件即可。 ::: 默认不配置此插件时,vux源码会按照默认语言`zh-CN`进行静态编译,和原来的使用没有明显不同。 详细请参照 vux-loader的vux-i18n文档 ================================================ FILE: docs/en/development/remove-click-delay-fastclick.md ================================================ --- title: Vue 应用移除移动端页面点击延迟 --- # 移除移动端页面点击延迟 ::: tip 直接使用 `WeUI` 样式并引入 `fastclick` 会导致一些点击问题,VUX 组件内部已经做了相关处理。 ::: ## 引入 fastclick 在`main.js`里引用`fastclick` ``` js const FastClick = require('fastclick') FastClick.attach(document.body) // done ``` ================================================ FILE: docs/en/development/router.md ================================================ --- title: vue-router 路由 --- # 路由 推荐直接使用官方 [vue-router](https://github.com/vuejs/vue-router),`VUX`部分组件支持`link`属性直接支持`vue-router`的路由参数,`vux2`模板内置了`vue-router`。 ::: warning 如果使用了过渡(转场动画),在`iPhone`上使用`左划返回`时动画会再执行一遍,目前没有找到可行的处理方法,如果你有处理方案,欢迎`PR`。 [https://github.com/airyland/vux/pull/2259](https://github.com/airyland/vux/pull/2259) ::: ================================================ FILE: docs/en/development/theme.md ================================================ --- title: 主题颜色配置 --- # 主题颜色配置 ## 配置插件 ::: warning 暂时只支持配合`vux-loader`使用。 注意的是主题文件不能引入其他less文件,只能为简单变量列表。 ::: 请配置vux-loader的`less-theme`插件,指定用以覆盖的less文件路径: ``` js { name: 'less-theme', path: 'src/styles/theme.less' // 相对项目根目录路径 } ``` ## 可配置颜色 源码地址:https://github.com/airyland/vux/blob/v2/src/styles/variable.less ::: tip 更多配置需求请通过 issue 提出。 ::: ## demo站点的示例配置 源代码地址:[https://github.com/airyland/vux/blob/v2/src/theme.less](https://github.com/airyland/vux/blob/v2/src/theme.less) ## 内部如何实现的? `vux-loader` 在每个 `less` 文件的编译过程中重写了 `less-loader` 的变量参数,使其能直接覆盖原来变量。 ================================================ FILE: docs/en/development/typescript.md ================================================ --- title: TypeScript 支持 --- # TypeScript 支持 > 即将支持 ================================================ FILE: docs/en/development/vue-ajax.md ================================================ --- title: Vue 中使用 ajax --- # 发送 ajax 请求 :::tip `AjaxPlugin` 插件依赖于 [axios](https://github.com/mzabriskie/axios) 详细 API 文档请查看:[axios](https://github.com/mzabriskie/axios) ::: ## 版本要求 `AjaxPlugin`在`vux@^2.1.0-rc.20`开始支持 ## 引入 `main.js` 入口文件中引入: ``` js import { AjaxPlugin } from 'vux' Vue.use(AjaxPlugin) ``` ## 兼容性问题 需要注意的是`axios`是基于`Promise`的,因此如果你需要兼容低版本浏览器([caniuse](http://caniuse.com/#feat=promises)),需要引入`polyfill`。 `Polyfill` 推荐使用 [es6-promise](https://github.com/stefanpenner/es6-promise) ``` js require('es6-promise').polyfill() ``` ## 全局使用 ``` js Vue.http.post('/api').then() ``` ## 组件中使用 ``` js export default { created () { this.$http.post('/api').then(({data}) => { console.log(data) }) } } ``` ================================================ FILE: docs/en/development/vue-global-method.md ================================================ --- title: Vue 全局公用函数 --- # vue 全局公用函数 如果你需要让一个工具函数在每个组件可用,可以把方法挂载到 `Vue.prototype`上。 ## 注册 `main.js` 中 ``` js Vue.prototype.$method = function () {} ``` ::: tip 一般建议函数名使用 `$` 前缀。像 `vue-router` 的 `$route` 和 `$router`。 ::: ## 使用 那么组件代码里 ``` js export default { created () { this.$method() } } ``` ## 说明 挂载到 `prototype` 上是为了方便组件内直接用 `this.$method` 来使用,你也可以直接用 `Vue.method`,这样同样可以全局使用,不过在组件内就需要再 import 一次 `Vue` 了。 ================================================ FILE: docs/en/development/vue-google-analytics.md ================================================ --- title: Vue 中使用谷歌统计 --- # 使用谷歌统计 单页面应用切换时要手动发送页面统计,首先在 `index.html` 或者 `main.js` 里引入谷歌统计代码: ``` js (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-yourID', 'auto') ga('send', 'pageview') // 是否要统计着陆页面访问,取决于你的需求,这个不一定需要,会和`router`统计有重复 ``` ``` js // main.js 里,如果你使用了 vue-router router.afterEach(function (to) { if (window.ga) { window.ga('set', 'page', to.fullPath) // 你可能想根据请求参数添加其他参数,可以修改这里的 to.fullPath window.ga('send', 'pageview') } }) ``` ================================================ FILE: docs/en/development/vue-loader-autoprefix.md ================================================ --- title: vue-loader autoprefix 推荐配置 --- # autoprefix 推荐配置 `vue`官方模板的设置是`last 2 versions`,可能会导致你在某些`Android`机子上出现问题。 如果你使用 `last 7 versions` 会生成不必要的`-ms`前缀代码. 因此建议同`WeUI`一样,使用配置 `['iOS >= 7', 'Android >= 4.1']` ## 直接在 vux-loader 里配置 如果你没有在 postcss 里配置,你可以直接配置 vux-loader 的 `duplicate-style` 插件: ``` js { name: 'duplicate-style', options: { cssProcessorOptions : { safe: true, zindex: false, autoprefixer: { add: true, "browsers": [ "iOS >= 7", "Android >= 4.1" ] } } } } ``` ================================================ FILE: docs/en/development/vue-loader-disable-eslint.md ================================================ --- title: vue-loader 禁用 eslint --- # 禁用 eslint 并不推荐禁用`eslint`, 编码规范可以一定程序上保证代码质量。但是如果你确实想禁用,可以删除`build/webpack.base.conf.js`里的相关代码。 ``` js preLoaders: [ { test: /\.vue$/, loader: 'eslint', include: [ path.join(projectRoot, 'src') ], exclude: /node_modules/ }, { test: /\.js$/, loader: 'eslint', include: [ path.join(projectRoot, 'src') ], exclude: /node_modules/ } ] ``` 如果你只是想禁用对某一文件的检测,那么可以在`.eslintignore`里添加规则。 如果你是想禁止某一行的检测,那么可以使用`// eslint-disable-line`。 更加灵活的使用参考 [eslint文档](http://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)。 ================================================ FILE: docs/en/development/vue-router-page-transition.md ================================================ --- title: 页面切换转场动画 --- # 页面切换转场动画 你可以注意到 demo 站点的切换动画,本质上是使用 `Vue` 的 `transition` 组件实现的。 - [ ] todo ================================================ FILE: docs/en/development/vue-show-loading.md ================================================ --- title: 页面切换时显示loading --- # 页面切换显示loading 移动端如果使用异步组件加载那么还是需要一点等待时间的,在网络慢时等待时间会更长。显示`Loading`状态缓解一下用户等待情绪就十分重要。 如果你使用`vue-router`和`vuex`,那么可以很容易实现。 首先,注册一个`module`来保存状态 ``` js const store = new Vuex.Store({}) // 这里你可能已经有其他 module store.registerModule('vux', { // 名字自己定义 state: { isLoading: false }, mutations: { updateLoadingStatus (state, payload) { state.isLoading = payload.isLoading } } }) ``` 然后使用`vue-router`的`beforeEach`和`afterEach`来更改`loading`状态 ``` js router.beforeEach(function (to, from, next) { store.commit('updateLoadingStatus', {isLoading: true}) next() }) router.afterEach(function (to) { store.commit('updateLoadingStatus', {isLoading: false}) }) ``` 在`App.vue`里使用`loading`组件并从`vuex`获取`isLoading`状态 ``` html ``` ``` js import { Loading } from 'vux' import { mapState } from 'vuex' export default { components: { Loading }, computed: { ...mapState({ isLoading: state => state.vux.isLoading }) } } ``` done. 如果你觉得在加载比较快时`Loading`组件一闪而过体验也不大好,那么你可以延迟设置`loading=false`。 ================================================ FILE: docs/en/development/vue-webpack-code-splitting-async.md ================================================ --- title: Vue 异步加载组件 --- # 异步加载组件 将所有页面组件一次性加载是一个很浪费资源和考验用户耐心的做法,尤其在移动端。 ## 使用方法 `webpack` 提供了[code splitting](https://webpack.js.org/guides/code-splitting-require/),你可以按照下面写法实现当切换到特定路由时才加载代码。 需要注意的是 `vue-loader@13.0.0` 语法有所变更,具体参照发布说明 [v13.0.0](https://github.com/vuejs/vue-loader/releases/tag/v13.0.0) ``` js // vue-loader@13.0.0 之前 const Foo = () => import('./Foo.vue') // 在 Vue 2.4 + vue-router 2.7 版本下可以正确运行 // vue-loader@13.0.0 const Foo = () => import('./Foo.vue').then(m => m.default) ``` ## 组件打包问题 如果你在不同的进行了代码分割的 .vue 文件引入了相同的组件,在打包时两个路由的代码都会重复打包该组件。 你可以对重复使用的组件在 `main.js` 进行全局注册,以减少相应 chunk file 的大小并提高下载速度。 ================================================ FILE: docs/en/development/vue-webpack-env.md ================================================ --- title: Vue + webpack 区分测试环境和生产环境 --- # 区分测试环境和生产环境 如果你使用了 `vux2` 模板或者 `webpack` 模板,默认你可以直接通过判断 `process.env.NODE_ENV` 来区分 比如`统计代码`仅放在 `production` 环境,在不同环境里使用不同的 `API` 接口地址。 ``` js if (process.env.NODE_ENV === 'production') { // 干一些线上才要做的事情 } if (process.env.NODE_ENV === 'development') { // 干一些测试时不可告人的事情 } ``` 如果你是自己配置的环境,可以参考 [webpack DefinePlugin 文档](https://webpack.js.org/plugins/define-plugin/) ================================================ FILE: docs/en/development/vux-nuxt-ssr.md ================================================ --- title: vux 使用 nuxt 实现服务端渲染 --- # vux 使用 nuxt 实现服务端渲染 请直接参考源码目录 [/ssr/nuxt](https://github.com/airyland/vux/tree/v2/ssr/nuxt) ================================================ FILE: docs/en/directives/v-transfer-dom.md ================================================ --- title: v-transfer-dom 指令 --- # v-transfer-dom 指令 在移动端应用里,为了便于代码组织,通常我们要将组件放在各个路由的 `.vue` 文件里,但是因为此时组件并不在 `body` 下,加上定位,overflowscrolling 设置等原因,会出现遮罩在弹层之上,z-index 失效等问题。 因此我们推荐在纯弹窗类组件比如 `Alert` `Popup` `XDialog` 等组件上使用 `v-transfer-dom` 实现自动移动到 body 下,解决以上问题。 ::: warning 必须有一个 `div` 作为占位元素否则会出错 ::: ## 使用 ### 注册局部指令 ``` js import { TransferDom } from 'vux' export default { directives: { TransferDom } } ``` ### 注册全局指令 ``` js import { TransferDom } from 'vux' Vue.directive('transfer-dom', TransferDom) ``` ### 模板使用 ``` html
    ``` ================================================ FILE: docs/en/faq/$t-is-not-defined.md ================================================ --- title: $t is not defined --- # $t is not defined 一般是两个原因: ## cnpm 导致的问题 如果是直接使用模板后并且使用 `cnpm` 报错,请升级到 `vux-loader@1.0.58`。 该问题出现原因是 `cnpm` 安装后 `node_modules` 里的模块为快捷方式,而 `vux-loader` 之前版本没有考虑到这一特殊情况。 ## 未正确配置动态 i18n demo例子使用`$t`是因为demo启用了`i18n`,如果你没有使用`vuex-i18n`等相关`i18n`插件,请不要在代码中使用`$t`函数 ================================================ FILE: docs/en/faq/Uncaught-SyntaxError-Unexpected-token-export.md ================================================ --- title: Uncaught SyntaxError: Unexpected token export --- # Uncaught SyntaxError: Unexpected token export 原因是Vux的js源码没配置babel,你可以在webpack配置的loaders加上 ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者启用`vux-loader`的`vux-ui`插件。 ================================================ FILE: docs/en/faq/can-vux-used-in-weapp.md ================================================ --- title: VUX 能否在微信小程序里使用 --- # VUX 能否在微信小程序里使用 Sorry,不能。微信小程序是个相对封闭的平台,无法简单地适配也没有计划支持小程序版本。 目前企业主体用户可以使用 `web-view` 组件直接显示 web 页面,间接地使用 VUX。 ## mpvue 解决方案能否直接迁移 VUX 目前 [mpvue](http://mpvue.com/) 还不支持 `slot`、`slot-scope`,因此迁移有难度,并且小程序存在较多差异,暂未计划。 ================================================ FILE: docs/en/faq/cannot-resolve-inline-desc.md ================================================ --- title: Can't resolve '../inline-desc' in --- # Can't resolve '../inline-desc' in webpack resolve 配置 ``` js resolve: { extensions: ['', '.js', '.vue', '.json'] } ``` ================================================ FILE: docs/en/faq/difference-between-vux-template-and-webpack-template.md ================================================ --- title: vux2 和 Vue 官方 webpack 模板区别是什么? --- # vux2 和 Vue 官方 webpack 模板区别是什么? `vux2` 模板 fork 自 `webpack` 模板并进行了配置,和 `webpack` 模板基本同步,因此建议直接使用 vux2 模板。 ================================================ FILE: docs/en/faq/document-framework.md ================================================ --- title: 文档是用什么工具编写的? --- # 文档是用什么工具编写的? 早期版本使用的是 [Docute](https://docute.js.org) by [egoist](https://github.com/egoist)。在`Docute`基础上做了一点样式修改。 2.2.1 后面版本出于 `SEO` 考虑使用 `Vue` 的服务端渲染,使用的框架同样是 [egoist](https://github.com/egoist) 开发的 [ream](https://github.com/ream/ream)。 文档部分样式参照了 [eggjs.org](https://eggjs.org/),表示感谢。 因此目前无论是组件还是文档都是完全基于 `Vue`,cool right? ================================================ FILE: docs/en/faq/does-vux-support-weex.md ================================================ --- title: Does VUX support weex --- # Does VUX support weex No, and not plan to support it. ================================================ FILE: docs/en/faq/dupicate-style.md ================================================ --- title: 样式冗余 --- # 样式冗余 ## 调试时 调试时如果你审查元素样式发现有多个重复样式是正常的,因为不同组件可能引用了同样的less源码,而调试时是直接把不同组件样式用` ``` - 配置 `vue-loader`(通过配置vux-loader实现) ``` js // vux-loader module.exports = { configureWebpack: config => { require('@vux/loader').merge(config, { plugins: ['vux-ui', { name: 'less-theme', path: 'src/theme.less' }] }) } } ``` - 配置`babel-loader`以正确编译 VUX 的js源码(通过配置vux-loader实现) ``` js module.exports = { configureWebpack: config => { require('@vux/loader').merge(config, { plugins: ['vux-ui', { name: 'less-theme', path: 'src/theme.less' }] }) } } ``` - 安装`less-loader`以正确编译less源码 ``` bash npm install less less-loader --save-dev ``` - 安装 `yaml-loader` 以正确进行语言文件读取 ``` bash npm install yaml-loader --save-dev ``` - 添加 `viewport` meta ``` html ``` - 添加 `Fastclick` 移除移动端点击延迟 ``` js const FastClick = require('fastclick') FastClick.attach(document.body) ``` - 添加 `vue-router`(如果不需要前端路由,可忽略) ``` js import VueRouter from 'vue-router' Vue.use(VueRouter) ``` - 添加 webpack plugin, 在构建后去除重复css代码(通过配置vux-loader实现) ``` js plugins: [{ name: 'duplicate-style' }] ``` - 如果你使用 `webpack-simple` 模板或者 webpack 配置里缺少 .vue extension 配置,请记得配置: ``` js resolve: { extensions: ['.js', '.vue', '.json'] ``` ================================================ FILE: docs/en/install/npm.md ================================================ --- title: VUX 安装使用 --- # 安装使用(webpack) ::: warning 如果你从没使用过 `VUX`,请参考 快速入门
    不推荐使用 `umd` 方式引用组件,但是如果不得不使用,可以参考 umd 构建 ::: 直接安装或者更新: ``` js npm install vux --save ``` 或者使用 `yarn` ``` js yarn add vux // 安装 yarn upgrade vux // 更新 ``` 如果你想直接从 Github 安装,请指定 `v2` 分支 ``` bash npm install git://github.com/airyland/vux.git#v2 ``` 如果你是从`0.x`更新,请参考: 更新到`2.x` ::: warning vux2必须配合`vux-loader`使用, 请在`build/webpack.base.conf.js`里参照如下代码进行配置: ::: ``` js const vuxLoader = require('vux-loader') const webpackConfig = originalConfig // 原来的 module.exports 代码赋值给变量 webpackConfig module.exports = vuxLoader.merge(webpackConfig, { plugins: ['vux-ui'] }) ``` ::: warning vux@0.x 已经停止维护,请尽快迁移到 vue@2.x & vuex@2.x & vux@2.x,虽然要花点时间,但是完全值得。 ::: ================================================ FILE: docs/en/install/umd.md ================================================ --- title: umd 组件构建及使用 --- # umd 组件构建及使用 ::: tip 从`2.0`开始,推荐使用`webpack`来调用组件,因此不再在`repo`中保存`umd`文件,但提供了生成命令。 请更新 `NodeJS` 到版本 `7.6.0`及以上。 例子可查看:[https://github.com/airyland/vux/tree/v2/docs/examples](https://github.com/airyland/vux/tree/v2/docs/examples) ::: ::: danger 强烈建议使用 `webpack` 的方式开发,在 3.0 之后可能不再提供支持。 ::: ## 生成命令 ``` bash git clone https://github.com/airyland/vux.git --depth=1 // or just update: git pull cd vux npm install npm run build-components ``` 默认生成的语言是`zh-CN`,模块命名空间为`vux`,如`vuxGroup`,`vuxCell`,你可以在命令行中配置。 ``` npm run build-components -- --locale='en' --namespace='X' ``` ## 目录结构 生成的文件夹结构如: ::: tip 出于目录结构一致性考虑,即使是子组件也是一个文件夹,并且会有一个空的`index.min.css`样式文件。 ::: ``` |- dist/ |- vux.min.js ------------ 所有组件打包,仅用于测试,不推荐在生产环境使用 |- vux.min.css ----------- 所有组件样式打包,同样仅用于测试 |- components |- actionsheet |- index.min.js -------- 组件js代码 |- index.min.css ------- 组件css代码 ``` ::: tip vux.min.js 包括了所有的组件、插件及默认地址库,都挂载在全局变量vux上。当然为了使用方便同样直接挂载到了`window`上。 组件调用举例: `vuxCell` 插件调用举例:`vuxAlertPlugin` 默认地址库调用:`vuxChinaAddressData` ::: ## 组件使用 ``` html scripts
    ``` ## 插件使用 ``` html scripts
    ``` ## 生成css工具样式 包括`1px`解决方案,构建文件位于`dist/styles/*.css`,构建方式: ``` bash npm run build-styles ``` ::: tip 为了使用方便,可以使用`npm run xbuild`来执行`build-components` 及 `build-styles` ::: ================================================ FILE: docs/en/install/upgrade-to-vux2.md ================================================ --- title: 升级到 vux@2.x --- # 升级到 vux@2.x ### Vue@2.x 主要变更 参考[Vue官方文档](https://cn.vuejs.org/v2/guide/migration.html)进行迁移, 这也是Vux组件的代码更新部分,主要包括: - `:value.sync` 已经废弃 - `broadcast` 方法已经废弃 - `@click` 需要更改为 `@click.native` - `v-for`的`(index, item)` => `(item, index)` ### vue-router 更新 配合vue2, `vue-router`同样需要更新到2.0版本以上 原来的路由配置修改为: ``` js const routes = [{ path: '/vux/2.0', component: Vux2Demo }] const router = new VueRouter({ routes }) ``` 原来的路由挂载修改为: ``` js new Vue({ router, render: h => h(App) }).$mount('#app') ``` `go` 已经不是过去的 `go`了,要用`push`方法来跳转 ``` js this.$router.go('/somewhere') ``` `v-link`也废弃了,使用`router-link`组件来代替 其他请参考官方升级文档: [https://cn.vuejs.org/v2/guide/migration-vue-router.html](https://cn.vuejs.org/v2/guide/migration-vue-router.html) ### 不再生成`umd`文件 但是你可以使用`npm run build-components`来生成,请参考文档首页。 ### 双向绑定修改为 `v-model` 所有相关Vux调用的 `:value.sync`都需要更改成 `v-model` ``` js // 0.x // 2.x ``` ### 使用 vux-loader 原来你可能在webpack中做了这样的配置以正确编译vux的js源码: ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者你也可能使用了低版本`vux-loader`的`getBabelLoader`方法。 现在你可以直接删除这一句了,直接使用vux-loader。 在`webpack.base.conf.js`中这样配置: ``` js const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [ { name: 'vux-ui' } ] }) ``` ### 引入方式变更 原来你可能是单个组件引入,现在在`vux-loader`的支持下你可以直接这样写: ``` js import { Group, Cell } from 'vux' ``` ### 组件名字变更

    为什么不参照其他组件库全部加上`X`前缀,主要是因为觉得总要写个`X`相当不顺手。

    因Vue2.0限制组件名不能与原有的html5标签一样,因此部分组件进行了重命名,加上 `x-`前缀,所有需要加前缀的组件如下: - `Progress` => `XProgress` - `Switch` => `XSwitch` - `Dialog` => `XDialog` - `Address` => `XAddress` - `Circle` => `XCircle` - `XButton` - `XImg` - `XInput` - `XTextarea` - `XHeader` ### 各个组件变更: #### Swiper 引入路径变化 目录结构变化,与其他有子组件的统一,导致引入方式变化: ``` js // 0.x import Swiper from 'vux/src/components/swiper' import SwiperItem from 'vux/src/components/swiper-item' // 2.0 import Swiper from 'vux/src/components/swiper/swiper' import SwiperItem from 'vux/src/components/swiper/swiper-item' // 或者 import { Swiper, SwiperItem } from 'vux/src/components/swiper' // with vux-loader ``` #### ColorPicker 废弃

    `2.0.0`可用用,但是后面不再维护。

    这个组件可以由`cell`配合`slot`扩展出来,而且更灵活。没有做过统计,但是感觉使用人数应该挺少。 #### Countdown 废弃

    `2.0.0`可用,但是后面不再维护。

    功能薄弱,比较鸡肋。 #### Scroller reset方法更新 由于 Vue@2.x 的`broadcast`方法已经废弃,并且之前的设计也并不是很好,uuid的绑定也其实是没必要的。 - reset方法变成使用ref的`reset()`方法 - pullup reset 变成 ref 的 `donePullup()`方法 - pullup disable 变成 ref 的 `disablePullup()`方法 - pullup enable 变成 ref 的 `enablePullup()`方法 - pulldown reset 变成 ref 的 `donePulldown()`方法 - pullup和pulldown的status绑定变成`v-model="status"`绑定,示例 ``` js status: { pullupStatus: 'default', pulldownStatus: 'default' } ``` 详细参照Scroller文档进行更新 #### 表单默认required为true 保持和`html`规范一致, 影响的组件有 `XInput` `Checklist` #### Checklist 不显示错误提示 考虑到错误样式目前并不优雅,而用户有自定义错误样式的需要,因此处理成emit一个错误事件+底部slot, 用户可自行处理。 #### XInput 的valid获取 由于Vue2的$ref不再是响应的,因此不能直接在模板中通过ref调用组件的valid值(会报undefined),所以需要变成在提交时再进行ref来获取valid值。 #### XAddress 默认地址数据更新 目前引用方式: ``` js import { XAddress, ChinaAddressData } from 'vux' ``` 如果你想继续使用旧版本数据 ``` js import { XAddress, ChinaAddressV1Data } from 'vux' ``` 按照[最新统计局数据](http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201608/t20160809_1386477.html)进行更新,部分区域已经不存在,部分id做了更新。因此请*谨慎*更新,评估后端数据存储设计和前端交互再进行更新,避免错误更新用户数据或者导致数据丢失。 完整更新如下图:

    ================================================ FILE: docs/en/install/usage.md ================================================ --- title: VUX 调用例子 --- # 调用示例 ## .vue文件中调用组件 ``` html ``` ## main.js中调用plugin ``` js import { AlertPlugin, ToastPlugin } from 'vux' Vue.use(AlertPlugin) Vue.use(ToastPlugin) // 详细使用请参考对应组件文档 ``` ================================================ FILE: docs/en/lab/index.md ================================================ --- title: 实验室 --- # 实验室 > beta 测试组件将出现在这里。 - [VChart](/en/components/v-chart.html) ================================================ FILE: docs/en/production/inline-manifest.md ================================================ --- title: Vue 打包优化:使用 inline manifest --- # inline manifest ::: tip `manifest` 文件为路径配置和异步组件名字列表,该做法可以减少一个`http`请求。 确保 `vux-loader`更新到 `^1.0.35`。 ::: ## 修改 index.html 模板 首先在页面的``前加入代码: ``` html <%=htmlWebpackPlugin.files.webpackManifest%> ``` ## 配置 vux-loader 在`vux-loader`配置的 `plugins` 列表中加入`inline-manifest`插件 ``` js plugins:[{ name: 'inline-manifest' }] ``` 或者简化写法直接使用名字: ``` js plugins:['inline-manifest'] ``` ================================================ FILE: docs/en/tools/base64.md ================================================ --- title: base64 --- # base64 ``` js import { base64 } from 'vux' base64.encode('VUX') base64.decode('VlVY') ``` ================================================ FILE: docs/en/tools/cookie.md ================================================ --- title: cookie --- # cookie ``` js import { cookie } from 'vux' ``` `get *cookie.get(name, [options])*` 获取 cookie 值。`options` 参数可选,取值如下: 1. `converter` 转换函数。如果所获取的 cookie 有值,会在返回前传给 `converter` 函数进行转换。 1. 选项对象。对象中可以有两个属性:`converter` 和 `raw`. `raw` 是布尔值,为真时,不会对获取到的 cookie 值进行 URI 解码。 **注**:如果要获取的 cookie 键值不存在,则返回 `undefined`. 例子: ```js // setup document.cookie = 'foo=1' document.cookie = 'bar=2' cookie.get('foo') // returns '1' cookie.get('bar', function(s) { return parseInt(s); } ) // returns 2 ``` `set *cookie.set(name, value, [options])` 设置 cookie 值。参数 `options` 可选,可以有以下属性:`path`(字符串)、`domain`(字符串)、 `expires`(数值或日期对象)、`raw`(布尔值)。当 `raw` 为真值时,在设置 cookie 值时,不会进行 URI 编码。 例子: ```js cookie.set('foo', 3) cookie.set('bar', 4, { domain: 'example.com', path: '/', expires: 30 }) ```` `remove *cookie.remove(name, [options])` 移除指定的 cookie. 例子: ```js cookie.remove('foo') cookie.remove('bar', { domain: 'example.com', path: '/' }) ``` ================================================ FILE: docs/en/tools/date.md ================================================ --- title: vue date format --- # date format Built-in dateFormat is much smaller size than `moment`。 ## Usage ``` js import { dateFormat } from 'vux' dateFormat(new Date(), 'YYYY-MM-DD HH:mm:ss') ``` ## Used as Vue filter ``` js import { dateFormat } from 'vux' export default { filters: { dateFormat } } ``` ================================================ FILE: docs/en/tools/debounce-throttle.md ================================================ --- title: debounce --- ## debounce ::: warning 请注意了解 `debounce` 和 `throttle` 的区别。如果尚不清楚,请`google`之。 ::: ``` js import { debounce } from 'vux' debounce(func, [wait=0], [options={}]) ``` [详细文档](https://lodash.com/docs/#debounce) ## throttle ``` js import { throttle } from 'vux' throttle(func, [wait=0], [options={}]) ``` [详细文档](https://lodash.com/docs/#throttle) ================================================ FILE: docs/en/tools/md5.md ================================================ --- title: md5 --- # md5 ::: tip 该工具直接依赖于 [blueimp-md5](https://github.com/blueimp/JavaScript-MD5) 注意: `md5`是消息摘要算法并非加密算法,用于需要加密的场景会有安全问题。 ::: ``` js import { md5 } from 'vux' md5('VUX') ``` ================================================ FILE: docs/en/tools/number.md ================================================ --- title: vue number format --- # number format `numberComma`用于分割数字,默认为3位分割,一般用于格式化`金额`。 `numberPad`用于按照位数补0 `numberRandom`用于生成两个整数范围内的随机整数 ``` js import { numberComma, numberPad, numberRandom } from 'vux' numberComma(21342132) // 21,342,132 numberComma(21342132, 4) // 2134,2132 numberComma(21342132.234) // 21,342,132.234 numberPad(1) // 01 numberPad(234, 4) // 0234 numberRandom(1, 7) // 2 ``` ================================================ FILE: docs/en/tools/querystring.md ================================================ --- title: Vue querystring url --- # url querystring ```js import { querystring } from 'vux' querystring.parse('a=b&c=d') // {a:'b',c:'d'}, 默认参数为 location.search querystring.stringify({a:'b',c:'d'}) // 'a=b&c=d',注意不支持复杂嵌套的结构 ``` ================================================ FILE: docs/en/tools/string.md ================================================ --- title: Vue trim string --- # trim string ``` js import { trim } from 'vux' trim(' 1024 ') // 1024 ``` ================================================ FILE: docs/en/umd.md ================================================ --- nav: zh-CN --- ## 组件 umd 文件

    从`2.0`开始,推荐使用`webpack`来调用组件,因此不再在`repo`中保存`umd`文件,但提供了生成命令。
    请更新 `NodeJS` 到版本 `7.6.0`及以上。
    例子可查看:[https://github.com/airyland/vux/tree/v2/docs/examples](https://github.com/airyland/vux/tree/v2/docs/examples)

    ### 生成命令 ``` bash git clone https://github.com/airyland/vux.git --depth=1 // or just update: git pull cd vux npm install npm run build-components ``` 默认生成的语言是`zh-CN`,模块命名空间为`vux`,如`vuxGroup`,`vuxCell`,你可以在命令行中配置。 ``` npm run build-components -- --locale='en' --namespace='X' ``` ### 目录结构 生成的文件夹结构如:

    出于目录结构一致性考虑,即使是子组件也是一个文件夹,并且会有一个空的`index.min.css`样式文件。

    ``` |- dist/ |- vux.min.js ------------ 所有组件打包,仅用于测试,不推荐在生产环境使用 |- vux.min.css ----------- 所有组件样式打包,同样仅用于测试 |- components |- actionsheet |- index.min.js -------- 组件js代码 |- index.min.css ------- 组件css代码 ```

    vux.min.js 包括了所有的组件、插件及默认地址库,都挂载在全局变量vux上。当然为了使用方便同样直接挂载到了`window`上。
    组件调用举例: `vuxCell`
    插件调用举例:`vuxAlertPlugin`
    默认地址库调用:`vuxChinaAddressData`

    ### 组件使用 ``` html scripts
    ``` ### 插件使用 ``` html scripts
    ``` ### 生成css工具样式 包括`1px`解决方案,构建文件位于`dist/styles/*.css`,构建方式: ``` bash npm run build-styles ```

    为了使用方便,可以使用`npm run xbuild`来执行`build-components` 及 `build-styles`

    ================================================ FILE: docs/en/upgrade-to-2.md ================================================ --- nav: zh-CN --- ### Vue@2.x 主要变更 参考[Vue官方文档](https://cn.vuejs.org/v2/guide/migration.html)进行迁移, 这也是Vux组件的代码更新部分,主要包括: - `:value.sync` 已经废弃 - `broadcast` 方法已经废弃 - `@click` 需要更改为 `@click.native` - `v-for`的`(index, item)` => `(item, index)` ### vue-router 更新 配合vue2, `vue-router`同样需要更新到2.0版本以上 原来的路由配置修改为: ``` js const routes = [{ path: '/vux/2.0', component: Vux2Demo }] const router = new VueRouter({ routes }) ``` 原来的路由挂载修改为: ``` js new Vue({ router, render: h => h(App) }).$mount('#app') ``` `go` 已经不是过去的 `go`了,要用`push`方法来跳转 ``` js this.$router.go('/somewhere') ``` `v-link`也废弃了,使用`router-link`组件来代替 其他请参考官方升级文档: [https://cn.vuejs.org/v2/guide/migration-vue-router.html](https://cn.vuejs.org/v2/guide/migration-vue-router.html) ### 不再生成`umd`文件 但是你可以使用`npm run build-components`来生成,请参考文档首页。 ### 双向绑定修改为 `v-model` 所有相关Vux调用的 `:value.sync`都需要更改成 `v-model` ``` js // 0.x // 2.x ``` ### 使用 vux-loader 原来你可能在webpack中做了这样的配置以正确编译vux的js源码: ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者你也可能使用了低版本`vux-loader`的`getBabelLoader`方法。 现在你可以直接删除这一句了,直接使用vux-loader。 在`webpack.base.conf.js`中这样配置: ``` js const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [ { name: 'vux-ui' } ] }) ``` ### 引入方式变更 原来你可能是单个组件引入,现在在`vux-loader`的支持下你可以直接这样写: ``` js import { Group, Cell } from 'vux' ``` ### 组件名字变更

    为什么不参照其他组件库全部加上`X`前缀,主要是因为觉得总要写个`X`相当不顺手。

    因Vue2.0限制组件名不能与原有的html5标签一样,因此部分组件进行了重命名,加上 `x-`前缀,所有需要加前缀的组件如下: - `Progress` => `XProgress` - `Switch` => `XSwitch` - `Dialog` => `XDialog` - `Address` => `XAddress` - `Circle` => `XCircle` - `XButton` - `XImg` - `XInput` - `XTextarea` - `XHeader` ### 各个组件变更: #### Swiper 引入路径变化 目录结构变化,与其他有子组件的统一,导致引入方式变化: ``` js // 0.x import Swiper from 'vux/src/components/swiper' import SwiperItem from 'vux/src/components/swiper-item' // 2.0 import Swiper from 'vux/src/components/swiper/swiper' import SwiperItem from 'vux/src/components/swiper/swiper-item' // 或者 import { Swiper, SwiperItem } from 'vux/src/components/swiper' // with vux-loader ``` #### ColorPicker 废弃

    `2.0.0`可用用,但是后面不再维护。

    这个组件可以由`cell`配合`slot`扩展出来,而且更灵活。没有做过统计,但是感觉使用人数应该挺少。 #### Countdown 废弃

    `2.0.0`可用,但是后面不再维护。

    功能薄弱,比较鸡肋。 #### Scroller reset方法更新 由于 Vue@2.x 的`broadcast`方法已经废弃,并且之前的设计也并不是很好,uuid的绑定也其实是没必要的。 - reset方法变成使用ref的`reset()`方法 - pullup reset 变成 ref 的 `donePullup()`方法 - pullup disable 变成 ref 的 `disablePullup()`方法 - pullup enable 变成 ref 的 `enablePullup()`方法 - pulldown reset 变成 ref 的 `donePulldown()`方法 - pullup和pulldown的status绑定变成`v-model="status"`绑定,示例 ``` js status: { pullupStatus: 'default', pulldownStatus: 'default' } ``` 详细参照Scroller文档进行更新 #### 表单默认required为true 保持和`html`规范一致, 影响的组件有 `XInput` `Checklist` #### Checklist 不显示错误提示 考虑到错误样式目前并不优雅,而用户有自定义错误样式的需要,因此处理成emit一个错误事件+底部slot, 用户可自行处理。 #### XInput 的valid获取 由于Vue2的$ref不再是响应的,因此不能直接在模板中通过ref调用组件的valid值(会报undefined),所以需要变成在提交时再进行ref来获取valid值。 #### XAddress 默认地址数据更新 目前引用方式: ``` js import { XAddress, ChinaAddressData } from 'vux' ``` 如果你想继续使用旧版本数据 ``` js import { XAddress, ChinaAddressV1Data } from 'vux' ``` 按照[最新统计局数据](http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201608/t20160809_1386477.html)进行更新,部分区域已经不存在,部分id做了更新。因此请*谨慎*更新,评估后端数据存储设计和前端交互再进行更新,避免错误更新用户数据或者导致数据丢失。 完整更新如下图:

    ================================================ FILE: docs/en/vux-loader/about.md ================================================ --- title: vux-loader 介绍 --- # vux-loader 介绍

    `vux-loader`工具的作用是对`.vue`代码进行预处理,不限于 vux 组件库。 它是针对`webpack+vue-loader`项目的工程化工具,简化了webpack插件和loader的使用和编写,支持在vue-loader处理之前进行预处理,同时内置对vux组件专用的配置和优化插件。 当然除了.vue文件外,你还可以对js文件进行预处理。说好的处理.vue文件,为什么连`js`文件也不放过呢?因为只有处理js才能实现理想工程化。举个例子,如果用户需要在`main.js`中调用vux的plugin,他需要这样做: ``` js import AlertPlugin from 'vux/src/plugins/Alert' import ToastPlugin from 'vux/src/plugins/Toast' ``` 虽然路径不长,但是看着相当不和谐,为了简化这个操作vux提供了更简洁的写法: ``` js import { AlertPlugin, ToastPlugin } from 'vux' ``` 这个操作即是通过`js-parser`插件解析main.js里的`import`语法来实现的,最终进入`babel`处理的代码和上面单独引入一致。 这个工具也许会帮你进一步打开`Vue`项目工程化的思路。 ::: tip 作为通用工具,即使你没有使用 `VUX`,依然可以使用它来进行各种代码处理。 ::: ## 为什么不使用 babel-plugin-import * VUX 希望使用源码分发 * VUX 希望无侵入地解决一系列的工程问题,不需要用户手动配置 webpack * VUX 希望能统计(匿名)使用情况 而上面这些单纯使用 `babel-plugin-import` 无法做到,所以造了个轮子。 ================================================ FILE: docs/en/vux-loader/how-vux-loader-works.md ================================================ --- title: vux-loader 实现方式 --- # vux-loader 实现方式 说明一下`vux-loader`是如何和`vue-loader`搞基配合的。 如果你看过`webpack`loader的介绍,理论上说如果需要自己手动先处理代码再传入`vue-loader`,你只需要这样对loader做配置: `vue-loader!my-loader`。但是如果你试过,就知道这个目前行不通。`vue-loader`不会接收上一个loader的source进行处理,因为.vue文件的特殊性,它直接生成了loader string进入下一个处理,处理后的结果大概是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 那么vue-loader提供了自定义loader string的选项,直接配置可否呢,[#531](https://github.com/vuejs/vue-loader/issues/531),直接配置会破坏原来的loader参数,导致不能Live reload。 那么最终能想到的就是直接修改`vue-loader`生成的代码,这样既不会影响`vue-loader`原来的逻辑,也能自由控制。 最终实现方式是前置`vux-loader`即:`vux-loader!vue-loader`,vux-loader将vue-loader生成的代码二次处理,分割loader string并(强行)插入vux的 template-loader, style-loader, script-loader,那么你就能理解vux-loader的配置方式了,在3大基本loader里获取到vux-loader的插件配置列表逐个做处理就行了。 上面的代码强行处理后是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!./../../../../vux-loader/src/style-loader.js!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./../../../../vux-loader/src/script-loader.js!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!./../../../../vux-loader/src/template-loader.js!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 原理就是这么简单(但是处理时还是遇到一些问题),为了方便使用,编写了常用的一些插件。 至于`js-parser`就只是在`babel-loader`前加上了`vux-loader`的`js-loader`。 而所有的loader, plugins设置都是在`vux-loader`的`merge`方法里来完成。 ================================================ FILE: docs/en/vux-loader/install.md ================================================ --- title: vux-loader 使用 --- # vux-loader 使用 ## 安装 ``` bash npm install vux-loader --save-dev ``` ## 使用 为了降低使用成本及不侵入原来配置,只需要调用`merge`方法对原来`webpack`配置进行操作: ``` js const webpackConfig = {} // 原来的 webpack.base.js 配置 const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [{ name: 'vux-ui' }] }) ``` ::: warning 更新配置后需要重新运行 `npm run dev` 命令。 ::: ## Options - `env` 非必选,定义当前环境变量,只在 vux-loader 里使用,用来判断哪些插件需要被执行(如果plugin有定义envs的话),目的是实现一份配置多个环境使用。 ## Plugins 插件为一个数组列表,根据需要可以添加你需要的插件,插件名为必须,有些组件不需要额外配置选项。 公用参数为: - `name` `必须`,插件名字 - `envs` `非必须`,数组,当前插件在哪些环境变量里执行,不定义则默认执行 下面的插件配置代码将省略 `plugins:[]`的书写。 ::: tip 当使用默认配置时,可以使用简写 ``` js plugins: ['vux-ui'] ``` 等同于 ``` js plugins: [{ name: 'vux-ui' }] ``` ::: ================================================ FILE: docs/en/vux-loader/plugins.md ================================================ --- title: vux-loader 插件列表 --- # vux-loader 插件列表 ## script-parser > .vue 文件的 script 部分代码处理 ``` js [{ name: 'script-parser', fn: function (source) { return source.replace('VARIABLE', 'v2') } }] ``` ## style-parser > `` 代码处理 ``` js [{ name: 'style-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` ## template-parser > template代码替换处理 > 适用于对``模板代码做自定义处理 * name 插件名字, `template-parser` * replaceList 非必须,正则匹配列表,将直接调用 `replace` 方法进行替换 * fn 非必须,相比params更灵活的方法,可以对字符串进行处理后返回 插件配置: ``` js [ name: "template-parser", replaceList: [{ test: /DeathToPM/g, replaceString: '微博-随时随地发现新鲜事' }, { test: /呵呵我们压根没有底线/g, replaceString: '我是有底线的' }], fn: function (templateSource) { return templateSource.replace('智障', '机智') } ] ``` #### js-parser > 项目里js文件处理,在babel-loader前进行处理 ``` js [{ name: 'js-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` ## template-feature-switch 实现根据变量切换template代码 参数: - `features`,变量列表,值只能为true或者false ``` js { name: 'template-feature-switch', features: { feature1: true, feature2: false } } ``` ``` html ``` 那么当 feature1 为 true时,将输出 `AWESOME FEATURE 1 is ON`, 反之则输出 `AWESOME FEATURE 1 is OFF`。注意`on`标签内不限定内容,可以为任何标签代码块,但`避免在on off 里面再嵌套 on off` ## vux-ui

    vux组件的配套工具,如果没有使用vux, 不需要添加。

    如果配合`vux`使用,需要手动启用。默认不需要设置选项。 该插件做了以下处理: - 配置`babel`编译 vux 的js源码 - 修改vue-loader为 `vux-loader!vue-loader` - `import`组件调用解析为单组件引入 ``` { name: 'vux-ui' } ``` 那么你就可以很方便地引入组件了: ``` // 0.x import Group from 'vux/src/components/group' import Cell from 'vux/src/components/cell' // 2.x import { Group, Cell } from 'vux' ``` ## i18n 1. 如果你只是调用中文语言的vux组件,那么你不需要做任何配置。 2. 如果你需要调用英文语言的vux组件,需要配置语言 ``` js { name: 'i18n', vuxStaticReplace: true, vuxLocale: 'en' } ``` 3. 如果你想和demo站点一样可以写`i18n`block,并且需要动态切换语言,那么需要配置插件抽取i18n的内容,并设置非静态替换 ``` js { name: 'i18n', vuxStaticReplace: false, staticReplace: false, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] } ``` 然后你就可以引用`vuex-i18n`插件实现多语言切换了。 参考 [main.js](https://github.com/airyland/vux/blob/v2/src/main.js#L84-L96) 及 [vuex-i18n 文档](https://www.npmjs.com/package/vuex-i18n)(仅当参考,你也可以使用其他i18n插件)。 ## less-theme

    注意,path所在文件必须是简单的less变量对,不能import其他文件或者引入变量。

    > less 变量设置和替换 > 适用于全局变量替换, 方便切换主题 > 这意味着,你不再需要为每个页面引入全局的less文件了,你只需要设置lang为less就可以直接使用变量了 ``` ``` 插件配置: ``` js { name: 'less-theme', path: 'src/styles/theme.less' } ``` ## duplicate-style > css 重复代码清除

    建议使用vux组件的用户使用,因为vux直接引用less,最终构建的css文件确实会有冗余。

    > 在webpack 构建完成后对生成的css文件使用cssnano进行重复样式清除。建议只在production环境下开启,因为dev环境没有必要。 ``` js { name: 'duplicate-style', events: { done: function () { console.log('done!') } } } ``` ## html-build-callback >html文件处理事件回调 > 适用于在写入html(一般为index.html)文件前进行内容修改,比如替换特定内容 > 比如写入js配置变量,改变cdn域名,将manifest文件直接写入html以减少请求等 ``` js { name: 'html-build-callback', events: { 'after-html-processing': function (data, cb) { data.html += 'magic footer' cb(null, data) } } } ``` 可用事件请参考 [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin) ## build-done-callback > 构建完成事件回调 > 本质上是在webpack plugin的 done 事件后触发 ``` js { name: 'build-done-callback', fn: function () { console.log('done!') } } ``` ================================================ FILE: docs/en/vux-loader.md ================================================ --- nav: zh-CN ---

    vux!vue

    ### 介绍 `vux-loader`工具的作用是对`.vue`代码进行预处理,不限于 vux 组件库。 它是针对`webpack+vue-loader`项目的工程化工具,简化了webpack插件和loader的使用和编写,支持在vue-loader处理之前进行预处理,同时内置对vux组件专用的配置和优化插件。 当然除了.vue文件外,你还可以对js文件进行预处理。说好的处理.vue文件,为什么连`js`文件也不放过呢?因为只有处理js才能实现理想工程化。举个例子,如果用户需要在`main.js`中调用vux的plugin,他需要这样做: ``` js import AlertPlugin from 'vux/src/plugins/Alert' import ToastPlugin from 'vux/src/plugins/Toast' ``` 虽然路径不长,但是看着相当不和谐,为了简化这个操作vux提供了更简洁的写法: ``` js import { AlertPlugin, ToastPlugin } from 'vux' ``` 这个操作即是通过`js-parser`插件解析main.js里的`import`语法来实现的,最终进入babel的代码和上面单独引入一致。 这个工具也许会帮你进一步打开`Vue`项目工程化的思路。

    作为通用工具,即使你没有使用Vux,依然可以使用它来进行各种代码处理。
    目前支持 vue-loader@>9.x, 低版本没有具体测试。
    `vux!vue`的loader写法只兼容webpack@1.x,为了兼容webpack@2.x,处理后的loader配置是`vux-loader!vue-loader`

    ### 实现方式 说明一下`vux-loader`是如何和`vue-loader`搞基配合的,vue-loader的issue里不少同学遇到预处理.vue的问题。 如果你看过`webpack`loader的介绍,理论上说如果需要自己手动先处理代码再传入`vue-loader`,你只需要这样对loader做配置: `vue-loader!my-loader`。但是如果你试过,就知道这个目前行不通。`vue-loader`不会接收上一个loader的source进行处理,因为.vue文件的特殊性,它直接生成了loader string进入下一个处理,处理后的结果大概是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 那么vue-loader提供了自定义loader string的选项,直接配置可否呢,[#531](https://github.com/vuejs/vue-loader/issues/531),直接配置会破坏原来的loader参数,导致不能Live reload。 那么最终能想到的就是直接修改`vue-loader`生成的代码,这样既不会影响`vue-loader`原来的逻辑,也能自由控制。 最终实现方式是前置`vux-loader`即:`vux-loader!vue-loader`,vux-loader将vue-loader生成的代码二次处理,分割loader string并(强行)插入vux的 template-loader, style-loader, script-loader,那么你就能理解vux-loader的配置方式了,在3大基本loader里获取到vux-loader的插件配置列表逐个做处理就行了。 上面的代码强行处理后是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!./../../../../vux-loader/src/style-loader.js!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./../../../../vux-loader/src/script-loader.js!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!./../../../../vux-loader/src/template-loader.js!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 原理就是这么简单(但是处理时还是遇到一些问题),为了方便使用,编写了常用的一些插件。 至于`js-parser`就只是在`babel-loader`前加上了`vux-loader`的`js-loader`。 而所有的loader, plugins设置都是在`vux-loader`的`merge`方法里来完成。 ### 实际使用场景 我们可以使用`vux-loader`做一些很有意思的事。 比如 [#542](https://github.com/vuejs/vue-loader/issues/542) 提到的根据当前feature为判断要输出哪一部分代码,现在已经直接支持,请看 `template-feature-switch`部分。 对于公用组件而言,完全可以实现构建时进行组件瘦身,只保留使用到的代码。 当然如果你愿意,你完全可以自己实现一门语言。 ## 安装 ``` bash npm install vux-loader --save-dev ``` ## 使用 为了减少使用成本,只需要调用`merge`方法对原来`webpack`配置进行操作: ``` js const webpackConfig = {} // 原来的webpack配置 const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [{ name: 'vux-ui' }] }) ```

    更新配置后需要重启npm run dev命令

    ### Options - `env` 非必选,定义当前环境变量,只在vux-loader里使用,用来判断哪些插件需要被执行(如果plugin有定义envs的话),目的是实现一份配置多个环境使用。 ### Plugins 插件为一个数组列表,根据需要可以添加你需要的插件,插件名为必须,有些组件不需要额外配置选项。 公用参数为: - `name` `必须`,插件名字 - `envs` `非必须`,数组,当前插件在哪些环境变量里执行,不定义则默认执行 下面的插件配置代码将省略 `plugins:[]`的书写。 #### script-parser > script代码替换处理 ``` js [{ name: 'script-parser', fn: function (source) { return source.replace('VARIABLE', 'v2') } }] ``` #### style-parser > style代码替换处理 ``` js [{ name: 'style-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` #### template-parser > template代码替换处理 > 适用于对``模板代码做自定义处理 > 适用于某些更改不频繁但非服务端配置的文字,可能调用多次,也可能手动更改或者批量替换相对麻烦 > > 同样你也可以用来从接口获取最新配置替换特定的占位字符 > > 当然也适用于在源码中对`pm`进行吐槽(千万要记得production环境要有配置,否则可能会上新闻。) * name 插件名字, `template-parser` * replaceList 非必须,正则匹配列表,将直接调用 `replace` 方法进行替换 * fn 非必须,相比params更灵活的方法,可以对字符串进行处理后返回 插件配置: ``` js [ name: "template-parser", replaceList: [{ test: /DeathToPM/g, replaceString: '微博-随时随地发现新鲜事' }, { test: /呵呵我们压根没有底线/g, replaceString: '我是有底线的' }], fn: function (templateSource) { return templateSource.replace('智障', '机智') } ] ``` #### js-parser > 项目里js文件处理,在babel-loader前进行处理 ``` js [{ name: 'js-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` #### template-feature-switch 实现根据变量切换template代码 参数: - `features`,变量列表,值只能为true或者false ``` js { name: 'template-feature-switch', features: { feature1: true, feature2: false } } ``` ``` html ``` 那么当 feature1 为 true时,将输出 `AWESOME FEATURE 1 is ON`, 反之则输出 `AWESOME FEATURE 1 is OFF`。注意`on`标签内不限定内容,可以为任何标签代码块,但`避免在on off 里面再嵌套 on off` #### vux-ui

    vux组件的配套工具,如果没有使用vux, 不需要添加。

    如果配合`vux`使用,需要手动启用。默认不需要设置选项。 该插件做了以下处理: - 配置`babel`编译 vux 的js源码 - 修改vue-loader为 `vux-loader!vue-loader` - `import`组件调用解析为单组件引入 ``` { name: 'vux-ui' } ``` 那么你就可以很方便地引入组件了: ``` // 0.x import Group from 'vux/src/components/group' import Cell from 'vux/src/components/cell' // 2.x import { Group, Cell } from 'vux' ``` #### i18n

    请使用正确的 yml 格式。冒号和值之间是有一个空格的,错误的格式将无法生效。

    ``` yml on-show: en: emits when popup shows zh-CN: 弹窗显示时触发 ``` 1. 如果你只是调用中文语言的vux组件,那么你不需要做任何配置。 2. 如果你需要调用英文语言的vux组件,需要配置语言 ``` js { name: 'i18n', vuxStaticReplace: true, vuxLocale: 'en' } ``` 3. 如果你想和demo站点一样可以写`i18n`block,并且需要动态切换语言,那么需要配置插件抽取i18n的内容,并设置非静态替换 ``` js { name: 'i18n', vuxStaticReplace: false, staticReplace: false, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] } ``` 然后你就可以引用`vuex-i18n`插件实现多语言切换了。 参考 [main.js](https://github.com/airyland/vux/blob/254574ef30a8c4d341feb7d2ff8245792657bda2/src/main.js#L84-L96) 及 [vuex-i18n 文档](https://www.npmjs.com/package/vuex-i18n)(仅当参考,你也可以使用其他i18n插件)。 #### less-theme

    注意,path所在文件必须是简单的less变量对,不能import其他文件或者引入变量。

    > less 变量设置和替换 > 适用于全局变量替换, 方便切换主题 > 这意味着,你不再需要为每个页面引入全局的less文件了,你只需要设置lang为less就可以直接使用变量了 ``` ``` 插件配置: ``` js { name: 'less-theme', path: 'src/styles/theme.less' } ``` #### duplicate-style > css 重复代码清除

    建议使用vux组件的用户使用,因为vux直接引用less,最终构建的css文件确实会有冗余。

    > 在webpack 构建完成后对生成的css文件使用cssnano进行重复样式清除。建议只在production环境下开启,因为dev环境没有必要。 ``` js { name: 'duplicate-style', events: { done: function () { console.log('done!') } } } ``` #### html-build-callback >html文件处理事件回调 > 适用于在写入html(一般为index.html)文件前进行内容修改,比如替换特定内容 > 比如写入js配置变量,改变cdn域名,将manifest文件直接写入html以减少请求等 ``` js { name: 'html-build-callback', events: { 'after-html-processing': function (data, cb) { data.html += 'magic footer' cb(null, data) } } } ``` 可用事件请参考 [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin) #### build-done-callback > 构建完成事件回调 > 本质上是在webpack plugin的 done 事件后触发 ``` js { name: 'build-done-callback', fn: function () { console.log('done!') } } ``` ### 使用案例 #### 统一引入less(sass)变量 如果你翻看`vue-loader`的issue, 就会发现不少同学在说,为什么sass, less 变量不能全局使用,需要在每个.vue组件里引入,这个很重复,有没有办法解决。目前除了在webpack里定义路径`alias`外没有其他方法。但是有了`vux-loader`,你可以用`style-parser`在每个`.vue`文件的style前面加上变量的引入了,只要一句代码。 ``` { name: 'style-parser', fn: function (source) { return "@import '../styles/variable.less'\n" + source // 你可以根据this.resourcePath 来确定是否要引入以及引入的相对路径 } } ``` #### 减少重复代码 `vux`的组件有几十个,同样demo也有几十个,因为webpack并不支持require变量,那么在main里中实现每个组件异步加载都需要这样: ``` js const routes = [ { path: '/component/actionsheet', component: function (resolve) { require(['./demos/Actionsheet'], resove) } } ] ``` 作为懒人,写几十次这样的代码是一件比较烦人的事,作为热爱地球的人,这样也很不环保,但是有了`vux-loader`, 我们可以这样: ``` js const routes = [] ``` 然后在`js-parser`里获取列表数组直接替换, 并且可以调用webpack的`this.addDependency`添加依赖实现修改列表时自动`reload`。从此添加删除组件只要加上或者删除名字就可以了,真是机智。 ``` js { name: 'js-parser', test: /main\.js/, fn: function (source) { this.addDependency(demoPath) let list = fs.readFileSync(demoPath, 'utf-8') list = JSON.parse(list) let str = [] list.forEach(one => { str.push(`{ path: '/component/${toDash(one)}', component: function (resolve) { require(['./demos/${one}.vue'], resolve) } }`) }) str = `[${str.join(',\n')}]` source = source.replace('const routes = []', 'const routes = ' + str) return source } } ``` #### 实现import语法转换 vux是没有main入口文件的,因此需要把import语句转换成单个组件引入。但是因为对语法树分析比较烦,能用正则替换的当然就是用正则替换,简单粗暴实用。具体可以看源码。 #### 开源组件多语言支持 如果以js方式分发,一般默认一个语言,用户可以加上另外的语言包,但是代码里必不可少存在相应的转换函数,对于只需要单语言的人来是个浪费和繁琐。要么所有一起打包,这个也极浪费。要么一个一个打包,这样又失去了动态多语言支持。 于是`vux`在`vux-loader`的支持下实现了`使用时构建`,无论是静态输出和动态支持。 ### Bug 反馈 && 功能建议 请到[https://github.com/airyland/vux-loader/issues](https://github.com/airyland/vux-loader/issues) ### Todo - [ ] 支持各个插件的异步返回形式 - [ ] 在插件中提供webpack的 this 参数 ================================================ FILE: docs/en/wechat/other.md ================================================ --- title: 其他问题 --- ## 如何实现`weui.io`或者星巴克页面的设置顶部bar和页面下拉背景色 微信对于部分合作方开放了`jssdk`的`setBounceBackground`及`setNavigationBarColor`权限,目测暂无申请入口。 ================================================ FILE: docs/en/wechat/vue-wechat-jssdk.md ================================================ --- title: Vue 应用中使用微信 jssdk --- # Vue 应用中使用微信 jssdk ::: warning 分享接口只有认证公众号才能使用,域名必须备案且在微信后台设置。 先确认已经满足使用`jssdk`的要求再进行开发。 ::: `WechatPlugin` 插件提供了`commonJS`的引入方式,因此你不需要在 `index.html` 引入文件。 ## 版本要求 `WechatPlugin`在`vux@^2.1.0-rc.19`开始支持 ## 引入 在 `main.js` 中全局引入: ``` js import { WechatPlugin } from 'vux' Vue.use(WechatPlugin) console.log(Vue.wechat) // 可以直接访问 wx 对象。 ``` ## 组件外使用 考虑到你需要在引入插件后调用`config`方法进行配置,你可以通过 `Vue.wechat` 在组件外部访问`wx`对象。 `jssdk`需要请求签名配置接口,你可以直接使用 VUX 基于 `Axios` 封装的 `AjaxPlugin`。 ``` js import { WechatPlugin, AjaxPlugin } from 'vux' Vue.use(WechatPlugin) Vue.use(AjaxPlugin) Vue.http.get('/api', ({data}) => { Vue.wechat.config(data.data) }) ``` ## 组件中使用 那么之后任何组件中都可以通过 `this.$wechat` 访问到 `wx` 对象。 ``` js export default { created () { this.$wechat.onMenuShareTimeline({ title: 'hello VUX' }) } } ``` ================================================ FILE: docs/en/wechat/wechat-document-title.md ================================================ --- title: 微信 Vue 单页面应用设置标题 --- # 微信 Vue 单页面应用设置标题 在微信`iOS` `webview`更新到`WKWebView`之前我们可以通过加载一个 `iframe` 来实现单页面应用`title`更改。但是17年初更新到 `WKWebView` 后该方法也失效。 目前该问题已经解决,在微信 iOS 客户端 `6.3.5` 之后的版本都可以通过 `document.title` 设置标题了。 ================================================ FILE: docs/examples/loading.html ================================================ scripts

    I'm Loading

    ================================================ FILE: docs/examples/umd_component_include_all.html ================================================ scripts
    ================================================ FILE: docs/examples/umd_component_include_single.html ================================================ scripts
    ================================================ FILE: docs/examples/umd_plugin_include_all.html ================================================ scripts
    ================================================ FILE: docs/examples/umd_plugin_include_single.html ================================================ scripts
    ================================================ FILE: docs/i18n.js ================================================ const list = { Intro: { 'zh-CN': '介绍' }, Install: { 'zh-CN': '安装' }, Examples: { 'zh-CN': '栗子' // 故意的,不需要改 }, Changelog: { 'zh-CN': '版本更新' }, faq: { 'zh-CN': '常见问题' }, title: { 'en': 'VUX - Vue.js Mobile UI Component Framework', 'zh-CN': 'VUX - 基于 WeUI 和 Vue 的移动端组件库' }, Directives: { 'zh-CN': '指令 Directives' }, Guide: { 'zh-CN': '教程' }, Css: { 'zh-CN': '样式 CSS' }, '1px': { 'zh-CN': '1px 解决方案' }, 'css-close-icon': { 'zh-CN': 'css 关闭图标' }, Components: { 'zh-CN': '组件' }, Component: { 'zh-CN': '组件' }, 'donot need import': { 'en': 'You don\'t need to import this component.', 'zh-CN': '该组件可以直接使用,不需要引入。' }, Releases: { 'zh-CN': '发布日志' }, Toolkit: { 'zh-CN': '工具函数 Tools' }, Lab: { 'zh-CN': '实验室' }, Donate: { 'zh-CN': '捐赠、赞助' }, 'return FAQ': { 'zh-CN': '返回 【常见问题】' }, 'Realtime developers': { 'zh-CN': '实时 VUX 开发者' }, 'Developers in 24h': { 'zh-CN': '24小时内 VUX 开发者' }, 'globally register': { 'en': 'you can also register globally', 'zh-CN': '在入口文件全局引入' }, 'default slot': { en: 'default', 'zh-CN': '默认插槽' }, 'required version': { 'zh-CN': '版本要求' }, 'qr': { 'en': 'view demos on mobile', 'zh-CN': '二维码' }, 'component source code': { 'zh-CN': '组件源码' }, 'demo url': { 'zh-CN': 'demo 原始链接' }, 'edit document': { 'zh-CN': '编辑文档' }, 'demo source code': { 'zh-CN': 'demo 源码' }, 'example': { 'zh-CN': '使用例子' }, 'Props': { 'zh-CN': '属性' }, 'Events': { 'zh-CN': '事件' }, 'Slots': { 'zh-CN': '插槽' }, 'Functions': { 'zh-CN': '方法' }, 'Prop': { 'zh-CN': '属性' }, 'Event': { 'zh-CN': '事件' }, 'Slot': { 'zh-CN': '插槽' }, 'Function': { 'zh-CN': '方法' }, 'Related issues': { 'zh-CN': '相关 issue' }, 'Contributors': { 'zh-CN': '贡献者' }, 'Referrences': { 'zh-CN': '参考资料' }, 'Tips': { 'zh-CN': '重要提示及已知问题' }, 'component tutorial': { 'zh-CN': '组件使用教程' }, 'name': { 'zh-CN': '名字' }, 'type': { 'zh-CN': '类型' }, 'params': { 'zh-CN': '参数' }, 'required vesion': { 'zh-CN': '版本要求' }, 'description': { 'zh-CN': '说明' }, 'default value': { en: 'default', 'zh-CN': '默认值' }, 'Total commits quantity:': { 'zh-CN': '该组件(包含文档)迭代次数' }, 'Total contributors quantity:': { 'zh-CN': '贡献人数' }, 'contribute': { 'zh-CN': '贡献次数' }, 'date format': { en: 'date-format', 'zh-CN': '日期格式化' }, 'number 格式化工具': { en: 'number' }, 'url 参数解析': { en: 'url querystring' }, 'string 处理工具': { en: 'string' }, 'Search documents': { 'zh-CN': '搜索文档' }, 'No search results': { 'zh-CN': 'Sorry,无搜索结果' }, 'click to copy': { 'zh-CN': '点击复制' }, 'copy done!': { 'zh-CN': '复制成功' }, 'copy fail!': { 'zh-CN': '复制失败' }, 'Variables': { 'zh-CN': '样式变量' }, 'is inherited': { 'zh-CN': '是否继承自另一变量' }, 'inherited name': { 'zh-CN': '继承自变量' }, 'Online developers': { 'zh-CN': '在线 VUX 开发者' }, 'priceCurrency': { en: 'USD', 'zh-CN': 'CNY' }, 'Local Registration': { 'zh-CN': '局部注册' }, 'Global Registration': { 'zh-CN': '全局注册' } } module.exports = function (key, lang = 'en') { if (!list[key]) { return key } if (!list[key][lang]) { return key } return list[key][lang] } ================================================ FILE: docs/index.html ================================================ VUX - Vue 移动端 UI 组件库

    VUX

    一个凑合的 Vue.js 移动端 UI 组件库

    体验不极致

    是的,VUX 还有很多问题,远远不完美,但一直在解决。

    如果你在使用并且觉得有一些问题,不妨开个 issue 反馈一下,我们乐意解决详细描述的问题。

    维护靠个人

    是的,没有团队维护。国内大多数开发者都选择了有知名前端团队维护的组件库。

    当然你也可以试试选择 VUX,毕竟维护两年时间,star 12k,一定程度上也说明并不比大公司团队开源的差,不是么?

    开始有了赞助支持

    是的,我们开始有了赞助来支持开发迭代。

    如果 VUX 帮你节省了大量时间的话,欢迎捐赠以鼓励支持作者。

    ================================================ FILE: docs/package.json ================================================ { "scripts": { "gen": "ream generate", "start": "ream start", "build": "ream build", "dev": "ream dev" }, "dependencies": { "axios": "^0.16.1", "babel-plugin-component": "^1.1.0", "element-ui": "^2.3.4", "github-markdown-css": "^2.6.0", "highlight.js": "^9.11.0", "js-yaml": "^3.11.0", "less": "^2.7.2", "less-loader": "^4.0.3", "markdown-it-container": "^2.0.0", "markdown-it-emoji": "^1.3.0", "markdown-it-task-lists": "^2.0.0", "nprogress": "^0.2.0", "v-markdown-loader": "0.6.3", "vue": "2.5.16", "vue-clipboard2": "^0.1.0", "vue-lazyload": "^1.2.3", "vue-meta": "^1.0.4", "vue-template-compiler": "2.5.16", "yaml-loader": "^0.4.0" }, "devDependencies": { "algoliasearch": "^3.26.0", "markdown-it": "^8.3.1", "ream": "^1.0.0-rc.6", "shelljs": "^0.8.1", "vue-server-renderer": "^2.5.16", "yargs": "^11.0.0" } } ================================================ FILE: docs/ream.config.js ================================================ var hljs = require('highlight.js'); var taskLists = require('markdown-it-task-lists'); var customContainer = require('markdown-it-container') var emoji = require('markdown-it-emoji'); var markdown = require('markdown-it')({ titleSuffix: ' | VUX - Vue 移动端 UI 组件库', ignoreSuffixChar: '|', html: true, breaks: true, typographer: true, highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return '
    ' +
                   hljs.highlight(lang, str, true).value +
                   '
    '; } catch (__) {} } return '
    ' + markdown.utils.escapeHtml(str) + '
    '; } }) // .use(taskLists) .use(customContainer, 'tip', { validate: function(params) { return params.trim() === 'tip' } }) .use(customContainer, 'warning', { validate: function(params) { return params.trim() === 'warning' } }) .use(customContainer, 'danger', { validate: function(params) { return params.trim() === 'danger' } }) .use(emoji) module.exports = { entry: 'src/_index.js', generate: { routes: require('./src/routes.json') }, extendWebpack(config) { config.module .rule('markdown') .test(/\.md$/) .use('v-markdown') .loader('v-markdown-loader') .options(markdown) .end() .end() } } ================================================ FILE: docs/server.js ================================================ const options = require('./ream.config.js') const ream = require('ream') const app = ream(options) const http = require('http') app.prepare() .then(() => { const server = http.createServer((req, res) => { const handle = app.getRequestHandler() handle(req, res) }) server.listen(3000) }) app.on('ready', () => { console.log('Ready!') }) ================================================ FILE: docs/src/App.vue ================================================ ================================================ FILE: docs/src/algolia-search.vue ================================================ ================================================ FILE: docs/src/app.css ================================================ @import '~highlight.js/styles/atom-one-light.css'; body, ul, li, p { margin: 0; padding: 0; } body { height: 100vh; overflow: hidden; min-width: 1200px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } .contributors-tip { font-size: 12px; color: #666; } .guide-link { font-size: 18px; padding: 5px 0; } .markdown-body h1.vux-component-name { font-family: Josefin Sans,sans-serif; font-size: 2.5em; font-weight: 600; color: #d95353; border-bottom: none; padding-bottom: 0; margin-top: 0; } .component-description { width: calc(100vw - 375px - 340px - 60px); font-size: 14px; color: #635f5f; } .title-box { margin-bottom: 15px; padding-top: 10px; } .header-nav { padding-left: 15px; padding-bottom: 3px; border-bottom: 1px solid #ececec; } tr.slot-disabled { color: #999; } .type, .change { height: 18px; font-size: 12px; width: 60px; color: #fff; border-radius: 2px; text-align: center; display: inline-block; padding: 0 3px; } .type { color: #666; background-color: #ececec; } .change { height: 14px; width: 72px; line-height: 14px; } .type-object { background-color: #33ab70; } .type-string { background-color: #27394c; } .type-boolean { background-color: #d95353; } .type-function { background-color: #f4a358; } .type-number { background-color: #288af0; } .type-array { background-color: #eb1ac1; } .change-deprecated { background-color: #d95353; } .change-todo { background-color: #27394c; } .change-enhance, .change-fix { background-color: #288af0; } .change-feature { background-color: #288af0; } .change-change { background-color: #e96900; } .markdown-body .highlight pre, .markdown-body pre { max-width: 1000px; } .markdown-body .source-code-dialog .highlight pre { max-width: none; } .github-icon { display: inline-block; } .github-star { display: inline-block; padding-top: 10px; } .chapter { margin-bottom: 15px; padding-left: 15px; padding-right: 15px; } .chapter-title { padding-bottom: 5px; font-size: 16px; color: #333; } .chapter-page { padding-left: 2px; } .chapter-page-item { list-style: none; } .chapter-page-item a { font-size: 14px; color: #666; height: 30px; line-height: 30px; text-decoration: none; } .chapter-page-item a.router-link-exact-active, .chapter-page-item a.router-link-active, a.router-link-active[data-current-category="faq"][href="/zh-CN/faq/"] { color: green; font-weight: bold; border-bottom: 1px solid green; } @font-face { font-family: Josefin Sans; font-style: normal; font-weight: 300; src: local("Josefin Sans Light"),local("JosefinSans-Light"),url(https://fonts-gstatic.proxy.ustclug.org/s/josefinsans/v9/C6HYlRF50SGJq1XyXj04z7sKtFnhOiVZh9MDlvO1Vys.woff2) format("woff2"); } @font-face { font-family: Josefin Sans; font-style: normal; font-weight: 300; src: local("Josefin Sans Light"),local("JosefinSans-Light"),url(https://fonts-gstatic.proxy.ustclug.org/s/josefinsans/v9/C6HYlRF50SGJq1XyXj04z0ZRWJQ0UjzR2Uv6RollX_g.woff2) format("woff2"); } .big-title { font-size: 3em!important; color: #42b983 } .big-title,.product-title { font-family: Josefin Sans,sans-serif } .product-title { border-top: 1px solid #333; padding-top: 15px; padding-bottom: 10px; font-size: 14px; color: #666; text-align: center } .analytics { width: 120px; margin: 0 auto; z-index: 999; border: 1px solid #666; padding: 5px 1px; color: #666; margin-top: 25px; margin-bottom: 50px; } @media only screen and (min-height:560px) { .analytics { position: absolute; bottom: 10px; margin-bottom: 0; left: 13px; } } .vux-version { text-align: center; width: 50px; color: #ccc; text-shadow: 0 0 6px rgba(0,120,255,.9) } .vux-title { font-weight: 400; font-family: Josefin Sans,sans-serif; text-align: center; font-size: 40px; margin: 20px 0 } .vux-github { color: #fff } .vux-sub-title { font-size: 14px; font-family: Josefin Sans,sans-serif } .vux-time-ago { padding-top: 10px; } .vux-time-ago > span { color: #ececec; font-size: 1.3em; text-shadow: 0 0 6px rgba(0,120,255,.9) } .vux-box,.vux-center { text-align: center } .app { display: flex; } .vux-box { width: 150px; height: 100vh; overflow: hidden; color: #ccc; background-color: #171717; } .sidebar-inner { height: 100vh; padding: 0 15px; position: relative; } .summary { height: 100vh; width: 205px; overflow: hidden; border-right: 1px solid #E6E6E6 } .summary-inner { padding-top: 15px; height: 100vh; width: 205px; box-sizing: border-box; overflow: scroll; } .survey { color: #ccc; font-size: 12px; text-decoration: none; } .content { height: 100vh; overflow: scroll; padding-top: 60px; flex: 1; } .markdown-body { overflow: scroll; padding-bottom: 50px; padding-right: 15px; } .header-nav { position: fixed; top: 0; background-color: #fff; z-index: 888; width: 100%; } @import '~github-markdown-css/github-markdown.css'; .content .markdown-body { padding-left: 25px; color: #555; } .markdown-body code { padding: 0; padding-top: 0.2em; padding-bottom: 0.2em; margin: 0; font-size: 85%; color: #687168; background-color: #def5df; border-radius: 3px; } .markdown-body code::before, .markdown-body code::after { letter-spacing: -0.2em; content: "\00a0"; } .markdown-body pre { word-wrap: normal; } .markdown-body pre > code { padding: 0; margin: 0; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0; } .markdown-body .highlight pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f8f8f8; border-radius: 3px; } .markdown-body pre code { display: inline; max-width: auto; padding: 0; margin: 0; overflow: visible; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0; } .markdown-body pre code::before, .markdown-body pre code::after { content: normal; } .markdown-body a { color: #22ab28; text-decoration: none; } .markdown-body a:hover, .summary a:hover { color: #148a1a; } /**docute**/ div.warning, div.tip, div.danger,p.warning { padding: 12px 24px 12px 20px; margin: 2em 0; border-left: 4px solid; position: relative; border-bottom-right-radius: 2px; border-top-right-radius: 2px; &:before { content: "!"; position: absolute; top: 14px; left: -12px; color: #fff; width: 20px; height: 20px; border-radius: 100%; text-align: center; line-height: 20px; font-weight: bold; font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; } &.no-bg { background-color: #f8f8f8; } } div.warning, p.warning { border-left-color: #f7d24c; background-color: #fefbed; &:before { background-color: #f7d24c; } } div.danger, p.danger { border-left-color: #f66; background-color: rgba(255, 102, 102, 0.06); &:before { background-color: #f66; } } div.tip { border-left-color: #3c763d; background-color: rgba(241, 249, 241, 0.83); &:before { background-color: #3c763d; } } .markdown-body .tip p { margin-bottom: 0; } .markdown-body .warning p { margin-bottom: 0; } .markdown-body .danger p { margin-bottom: 0; } /** * docute */ .header-nav { display: flex; align-items: center } .header-nav.is-mobile .nav-list { height: auto; line-height: auto } .header-nav.is-mobile .nav-list .nav-item { float: none } .header-nav.is-mobile .nav-list .nav-item>a,.header-nav.is-mobile .nav-list .nav-item>div { border-bottom: none; height: auto; line-height: 30px } .header-nav.is-mobile .nav-list .dropdown-list { background-color: transparent } .header-nav.is-mobile .nav-list .nav-item-dropdown .dropdown-list { position: static; display: block; transform: none; border: none; padding: 0 } .nav-list { list-style: none; padding-left: 0; margin: 0; line-height: 35px; height: 47px } .nav-list .sep { height: 1px; background-color: #f0f0f0; display: block; margin: 8px 0 } .nav-list .label { display: block; margin-top: 5px; font-size: 12px } .nav-list .nav-item { float: left; margin-right: 20px } .nav-list .nav-item>a,.nav-list .nav-item>div { color: #666; height: 47px; line-height: 47px; font-size: 14px; border-bottom: 3px solid transparent; display: block; text-decoration: none; } .nav-list .nav-item>a.link-active,.nav-list .nav-item>div.link-active { border-bottom-color: #42b983; color: #333 } .nav-list .nav-item>a:hover,.nav-list .nav-item>div:hover { color: #333 } .nav-list .nav-item>a:hover:not(.router-link-active),.nav-list .nav-item>div:hover:not(.router-link-active) { border-bottom-color: #e2e2e2 } .nav-list .search-item { float: left; position: relative; top: 3px; } .nav-list .nav-item .arrow { display: inline-block; vertical-align: middle; margin-top: -1px; margin-left: 6px; width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #ccc; } .nav-list .nav-item-dropdown { position: relative; } .nav-list .nav-item-dropdown .dropdown-list { position: absolute; z-index: 9999; top: 100%; left: 50%; transform: translateX(-50%); display: none; background-color: #fff; margin: 0; list-style-type: none; border: 1px solid #e2e2e2; border-radius: 3px; white-space: nowrap; padding: 10px 0 } .nav-list .nav-item-dropdown .dropdown-list .dropdown-item { font-size: 13px; line-height: 28px } .nav-list .nav-item-dropdown .dropdown-list .dropdown-item .router-link-active,.nav-list .nav-item-dropdown .dropdown-list .dropdown-item .router-link:hover { color: #42b983 } .nav-list .nav-item-dropdown:hover .dropdown-list { display: block } .component-list-item > a{ font-size: 14px; } .code-box { font-size: 16px; } .el-icon-info { padding-left: 3px; } .el-card { box-shadow: none!important; border-radius: 0!important; } .tip-answer-box { padding-top: 15px; } .prop-name { color: green; font-weight: 400; white-space: nowrap; font-family: Menlo,'Menlo Regular',Consolas,monospace; } .chapter-page-item { font-weight: 300; } .contributor-item { padding-left: 10px; } .el-dialog__wrapper.sourcec-code-dialog { right: 410px; } .el-dialog__wrapper.sourcec-code-dialog .el-dialog { margin-right: 0!important; } .component-tip-tag { vertical-align: middle; } .component-tip-question { font-size: 18px; font-weight: 500; padding-left: 10px; vertical-align: middle; } .markdown-body table { display: table!important; width: 100%; } .markdown-body h1, .markdown-body h2 { font-family: Menlo,'Menlo Regular',Consolas,monospace; font-weight: 300; } .markdown-body h3 { font-family: Menlo,'Menlo Regular',Consolas,monospace; font-size: 1.4em; font-weight: 500; } .markdown-body table th, .markdown-body table td { border: none; } .markdown-body table tr { border-top: 1px solid #ddd; } .markdown-body table thead tr { background-color: #f0f0f0; } .markdown-body table { border-bottom: 1px solid #ddd; } .algolia-search .algolia-highlight { font-weight: 500; font-style: normal; } .el-input.is-active .el-input__inner, .el-input__inner:focus { border-color: #22ab28; } .markdown-body h2.vux-component-name-sub-item { font-family: "Helvetica Neue","Open Sans",Arial,sans-serif; font-size: 2.3em; color: #d95353; font-weight: 300; } .prop-name > span { cursor: pointer; } .prop-name > span:hover { background-color: green; color: #fff; } .import-code-box { position: relative; } .markdown-body .highlight .import-code-box pre, .markdown-body .import-code-box pre { max-width: none; } .import-code-box:hover .el-tooltip { color: #666; } .import-code-box .el-tooltip { position: absolute; right: 15px; top: 10px; color: #ccc; cursor: pointer; } .demos { display: flex; border: 1px solid #ffdd57; border-radius: 3px; border-top-left-radius: 0px; margin-bottom: 25px; max-height: 600px; overflow: hidden; min-width: 750px; } .demo-header { margin-top: 10px; display: flex; } .demo-title { background: #ffdd57; border-radius: 3px 3px 0 0; display: inline-block; font-size: 8px; font-weight: 700; padding: 4px 8px; text-transform: uppercase; vertical-align: top; } .demos > div { flex: 1; overflow: hidden; } .demo-code-box { position: relative; border-left: 1px solid #ffdd57; } .demo-code-masker { cursor: pointer; position: absolute; top: 0; bottom: -10px; left: 0; right: 0; background-color: hsla(0, 0%, 100%, .8); border: none; display: flex; justify-content: center; align-items: center; } .demo-code-masker > div { color: #7a7a7a; cursor: pointer; font-weight: 600; font-size: 12px; text-align: center; } .demo-iframe-box { display: flex; justify-content: center; position: relative; } .demo-code-masker:hover { background-color: rgba(255, 221, 87, .8); } .demo-code-box pre { margin-bottom: 0; } .demo-path { color: #999; } .demo-qr { opacity: 0.6; display: none; } .demo-code-masker:hover .demo-qr { opacity: 1; display: inline; } .hide-code { position: absolute; right: 15px; top: 5px; font-size: 14px; cursor: pointer; } .el-icon-arrow-left, .el-icon-arrow-right { font-weight: bold; } a.anchor { display: block; position: relative; top: -50px; visibility: hidden; float: none!important; } .markdown-body .toc ul { list-style: none; margin-bottom: 0; padding-left: 1em; } .markdown-body .toc ul li ul { padding-left: 15px; } .components-with-toc { width: 80%; } .toc { position: fixed; right: 15px; top:50px; border-left: 1px solid #ececec; overflow: hidden; z-index: 2500; background-color: #fff; } .toc a { font-size: 12px; color: #888; } .component-header-v1 { min-height: 600px; } .el-tabs__item.is-active, .el-tabs__item:hover { color: green; } .el-tabs__active-bar { background-color: green; } .markdown-body table tr:nth-child(2n) { background-color: transparent; } .markdown-body table thead tr { height: 44px; background-color: #f7f7f7; } .best-companies { text-align: center; } .best-companies a { display: inline-block; } .best-companies img { height: 60px; margin: 0; } #x-sponsor { margin-top: 30px; border: 1px solid #444; color: #666; text-align: center; line-height: 60px; font-size: 12px; cursor: pointer; } .juejin { text-decoration: none; display: block; margin-bottom: 10px; position: fixed; top: 0; left: 150px; width: 190px; } .juejin img { width: 205px; } .juejin:hover .juejin-desc { color: #007fff; } .juejin-name { color: #007fff; } .juejin-url { color: #888; padding-left: 15px; font-size: 12px; } .juejin-desc { font-size: 12px; } .juejin:visited, .juejin:visited .juejin-desc { color: #666; } ================================================ FILE: docs/src/component.vue ================================================ ================================================ FILE: docs/src/index.js ================================================ import Vue from 'vue' import Router from 'vue-router' import App from './App.vue' import Vuex from 'vuex' import axios from 'axios' import yaml from 'js-yaml' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import VueLazyload from 'vue-lazyload' import VueClipboard from 'vue-clipboard2' Vue.use(VueClipboard) Vue.use(VueLazyload, { lazyComponent: true }) Vue.use(ElementUI) Vue.use(Vuex) if (__BROWSER__) { require('./app.css') } const createStore = () => new Vuex.Store({ state: { contributors: [] }, mutations: { UPDATE_CONTRIBUTORS(state, payload) { state.contributors = payload } }, actions: { async updateContributors({ commit }, payload) { const data = await axios.get(`https://api.github.com/repos/airyland/vux/contributors?per_page=100`).then(res => { const list = res.data list.forEach((user, index) => { // my girl if (user.login === 'QiongrongJiang') { const saved = user list.splice(index, 1) list.splice(1, 0, saved) return } }) return list }) commit('UPDATE_CONTRIBUTORS', data) } } }) const list = [{ chapter: 'about', pages: [{ path: 'version', title: '版本发布说明' }] }] // Lazy-loading (i.e. code-split) your markdown file as vue component const routes = [] Vue.use(Router) const createRouter = () => { const router = new Router({ mode: 'history', routes }) if (__BROWSER__) { const nprogress = require('nprogress') require('nprogress/nprogress.css') if (process.env.NODE_ENV === 'production') { ; (function (i, s, o, g, r, a, m) { i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () { (i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o), m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m) })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); ga('create', 'UA-89859411-2', 'auto'); ga('send', 'pageview'); router.afterEach(function (to) { ga('set', 'page', to.fullPath); ga('send', 'pageview'); }) } router.beforeEach((from, to, next) => { nprogress.start() next() }) router.afterEach(() => { nprogress.done() window.scrollTo(0, 0) }) } return router } export default { createRouter, createStore, App } ================================================ FILE: docs/src/summary.js ================================================ module.exports = [{ title: '入坑指南', pages: [{ title: '关于 VUX', path: '/zh-CN/' }, { title: '使用 VUX 之前', path: '/zh-CN/about/before-using-vux.html' }, { title: '常见问题', path: '/zh-CN/faq/' }, { title: '使用案例', path: '/zh-CN/about/showcase.html' }, { title: '更新日志', path: '/zh-CN/changelog/changelog.html' }, { title: '贡献者们', path: '/zh-CN/about/contributors.html' }] }, { title: '安装使用', pages: [{ title: '安装', path: '/zh-CN/install/npm.html' }, { title: '快速开始', path: '/zh-CN/install/biolerplate.html' }, { title: '手动安装配置', path: '/zh-CN/install/manual-usage.html' }, { title: '代码示例', path: '/zh-CN/install/usage.html' }] }, { title: '定制', pages: [{ title: '国际化', path: '/zh-CN/development/i18n.html' }, { title: '主题', path: '/zh-CN/development/theme.html' }] }, { title: '开发', pages: [{ title: '路由', path: '/zh-CN/development/router.html' }, { title: '在 Nuxt.js 中使用', path: '/zh-CN/development/vux-nuxt-ssr.html' }, { title: 'TypeScript 支持', path: '/zh-CN/development/typescript.html' }, { title: 'Ajax 请求', path: '/zh-CN/development/vue-ajax.html' }, { title: '点击延迟', path: '/zh-CN/development/remove-click-delay-fastclick.html' }, { title: '使用微信 jssdk', path: '/zh-CN/wechat/vue-wechat-jssdk.html' }, { title: '添加谷歌统计', path: '/zh-CN/development/vue-google-analytics.html' }, { title: '页面切换显示 Loading', path: '/zh-CN/development/vue-show-loading.html' }, { title: '异步加载组件', path: '/zh-CN/development/vue-webpack-code-splitting-async.html' }, { title: '区分测试环境和生产环境', path: '/zh-CN/development/vue-webpack-env.html' }, { title: '全局公共函数', path: '/zh-CN/development/vue-global-method.html' }, { title: 'autoprefix 配置', path: '/zh-CN/development/vue-loader-autoprefix.html' }, { title: '禁用 eslint', path: '/zh-CN/development/vue-loader-disable-eslint.html' }] }, { title: 'vux-loader', pages: [{ title: 'vux-loader 是什么?', path: '/zh-CN/vux-loader/about.html' }, { title: '安装使用', path: '/zh-CN/vux-loader/install.html' }, { title: '插件列表', path: '/zh-CN/vux-loader/plugins.html' }] }, { title: '贡献', pages: [{ title: '提交 issue', path: '/zh-CN/contribution/issue.html' }, { title: '提交 pr', path: '/zh-CN/contribution/pr.html' }, { title: '本地开发', path: '/zh-CN/contribution/vux-development.html' }, { title: '感谢', path: '/zh-CN/about/thanks.html' }] }] ================================================ FILE: docs/src/tool-routes-en.json ================================================ [ { "title": "base64", "path": "/en/tools/base64.html" }, { "title": "cookie", "path": "/en/tools/cookie.html" }, { "title": "date format", "path": "/en/tools/date.html" }, { "title": "debounce", "path": "/en/tools/debounce-throttle.html" }, { "title": "md5", "path": "/en/tools/md5.html" }, { "title": "number format", "path": "/en/tools/number.html" }, { "title": "url querystring", "path": "/en/tools/querystring.html" }, { "title": "trim string", "path": "/en/tools/string.html" } ] ================================================ FILE: docs/src/tool-routes-zh-CN.json ================================================ [ { "title": "base64", "path": "/zh-CN/tools/base64.html" }, { "title": "cookie", "path": "/zh-CN/tools/cookie.html" }, { "title": "date format", "path": "/zh-CN/tools/date.html" }, { "title": "debounce", "path": "/zh-CN/tools/debounce-throttle.html" }, { "title": "md5", "path": "/zh-CN/tools/md5.html" }, { "title": "number 格式化工具", "path": "/zh-CN/tools/number.html" }, { "title": "url 参数解析", "path": "/zh-CN/tools/querystring.html" }, { "title": "string 处理工具", "path": "/zh-CN/tools/string.html" } ] ================================================ FILE: docs/watch.js ================================================ const watch = require('node-watch') const path = require('path') const dir = path.resolve(__dirname, '../src/') delete require.cache[require.resolve('./compile.js')] require(require.resolve('./compile.js')) watch(dir, { recursive: true }, function (filename) { if (/\.yml|\.md/.test(filename) && !/locales/.test(filename)) { try { delete require.cache[require.resolve('../build/build-docs.js')] require('../build/build-docs.js') delete require.cache[require.resolve('./compile.js')] require(require.resolve('./compile.js')) } catch (e) { console.log(e) } } }) ================================================ FILE: docs/zh-CN/README.md ================================================ --- title: 关于 VUX ---



    预览地址>>








    ::: warning VUX 必须配合 `vux-loader` 使用,请按照[https://github.com/airyland/vux/tree/v2/packages/vue-cli-3-example](https://github.com/airyland/vux/tree/v2/packages/vue-cli-3-example)配置。
    **less@3.x** 有严重的兼容问题,请暂时使用 **less@^2.7.3**。 vux-loader 工具是针对webpack+vue-loader项目的工程化工具,简化了webpack插件和loader的使用和编写,支持在vue-loader处理之前进行预处理,同时内置对vux组件专用的配置和优化插件。 详见 vux-loader readme.md ::: ## 简介 `VUX`(读音 [v'ju:z],同 `views`)是基于`WeUI`和`Vue`(2.x)开发的移动端UI组件库,主要服务于微信页面。 基于`webpack + vue-loader + vux`可以快速开发移动端页面,配合`vux-loader`方便你在`WeUI`的基础上定制需要的样式。 `vux-loader`保证了组件按需使用,因此不用担心最终打包了整个vux的组件库代码。 `VUX`并不完全依赖于`WeUI`,`VUX` 在 `WeUI` 的基础上扩展了多个常用组件,但是尽量保持整体UI样式接近`WeUI`的设计规范。 ::: warning VUX 并不是一个能解决所有场景的完美解决方案(实际上也没有一个方案能解决所有问题),也会出现某些`bug`或者某些特性不支持,所以如果遇到问题麻烦及时**不带情绪正确反馈**,**我们乐于及时解决描述详细方便重现的问题**。
    即使你不直接使用 `VUX` 组件代码, 你依然可以参考 VUX 代码来实现自己的组件库。如果一定程度上帮助到了你,那么维护这个项目也就有所意义。 ::: ## 订阅版本发布通知 请使用微信扫描 ## 提示 ::: tip `VUX` 是`库`而非`框架`,虽然有专用的 `vux-loader`,但并不影响你自由地使用其他组件库或者工具库。

    `VUX` 使用的 CSS 预处理工具是 `less`(同 WeUI),但(利益于 .vue 单文件组件的灵活性)这并不影响你使用 `SASS` 等其他预处理器。

    用以表示该组件库时请使用大写名字 `VUX`,用在说明版本号时使用小写 `vux@2.x`。 ::: ================================================ FILE: docs/zh-CN/about/before-using-vux.md ================================================ --- title: 开始使用 VUX 之前 --- # 开始使用 VUX 之前 ::: warning 如果你刚从后端转到前端,可能会被目前前端(表面的)工程复杂度惊吓到,但是放心,使用 `vue-cli` 从模板创建项目可以快速开始编码、构建,仅仅是几行简单的命令不是么? ::: 在使用 VUX 之前需要你至少已经会使用 `Vue`,同时需要你大概了解 `Node.js`,`npm`,`cnpm`,`yarn` 这些东西。 ::: tip 建议 `Node.js` 版本在 `7.6.0` 以上。 ::: ## 相关工具 ### WeUI VUX 样式基于 [WeUI](https://github.com/weui/weui),但是你不必通过使用 VUX 来使用 `WeUI`。简单的页面你可以直接引入 `WeUI` 样式。详细请参考 [WeUI 文档](https://github.com/weui/weui)。 ### Vue VUX 基于 `Vue` 的组件库,意味着你需要有 `Vue` 的基础知识。 如果还没有了解过,建议先看看[Vue 官方文档](https://cn.vuejs.org)。 ### Webpack 如果你直接使用 `vux2` 模板,你可以暂时不用了解。当你需要自定义一些配置时自然就能很快了解了。 [Webpack 文档](https://webpack.js.org) ### vue-cli Vue 官方用于快速创建项目的工具。 ``` js bash npm install vue-cli -g ``` 或者使用 yarn ``` bash yarn global add vue-cli ``` [vue-cli 文档](https://github.com/vuejs/vue-cli) ### vue-loader webpack loader,用于编译 `.vue` 文件,官方模板已经帮你配置好。 [vue-loader 文档](https://vue-loader.vuejs.org) ### vux-loader VUX 组件库的 webpack loader,实现按需加载等功能。它不是替代 `vue-loader` 而是配合 `vue-loader` 使用。如果你使用 vux2 模板,暂不需要手动使用它。 ================================================ FILE: docs/zh-CN/about/contributors.vue ================================================ ================================================ FILE: docs/zh-CN/about/showcase.md ================================================ --- title: VUX 使用案例 --- # 使用案例 > 如果你的产品在使用`VUX`, 请直接发 PR 修改 `docs/zh-CN/about/showcase.md`(置于列表最后,统一格式:二维码必须无白边框)。 原味生活 牛小云 ================================================ FILE: docs/zh-CN/about/thanks.md ================================================ --- title: 感谢 --- # Thanks ## VUX 参考或者使用了以下开源项目的代码 + [Vue](https://github.com/vuejs/vue) + [WeUI](https://github.com/weui/weui) + [FrozenUI](https://github.com/frozenui/frozenui) + [Ant Design](https://github.com/ant-design/ant-design) + [Ant Design Mobile](http://github.com/ant-design/ant-design-mobile) + [XScroll](https://github.com/huxiaoqi567/xscroll) + [Ionic](https://github.com/driftyco/ionic) + [SUI Mobile](https://github.com/sdc-alibaba/SUI-Mobile) + [PhotoSwipe](https://github.com/dimsemenov/PhotoSwipe) ## 工具框架 - 世界上最好的语言 `JavaScript` - 啥都能做的前端构建工具 [Webpack](https://webpack.js.org/) - 简单好用的文档展示工具 [Docute](https://docute.js.org/#/) ================================================ FILE: docs/zh-CN/contribution/donate.md ================================================ --- title: 捐赠支持 VUX --- ## 感谢赞助商



    ## 感谢捐赠用户 > 即将上线,捐赠时的附加信息也将显示在这里。
    ## 捐赠方式 > 欢迎通过下面渠道进行捐赠 * [Patreon](https://www.patreon.com/airyland) * [Open Collective](https://opencollective.com/vux) * [PayPal](https://paypal.me/airyland) * 支付宝 airyland@qq.com * 企业赞助捐赠请联系 i@mao.li ================================================ FILE: docs/zh-CN/contribution/issue.md ================================================ --- title: 如何提 issue --- # 如何提 issue ::: tip 使用本项目意味着你也有义务帮助其变得更好。 ::: 不要浪费维护者时间。 不要让维护者帮你学习`Vue`,帮你熟悉`vue-loader`,甚至帮你写代码。 不要认为随便一句话就能让维护者明白你的意思,我们没有你想象的那么厉害。 不要提没有任何意义的、代码中带有业务逻辑不方便重现的Issue。 直接关闭你的`issue`不是对你不满,是你提问题方式不对,没有必要再浪费时间说明为什么要关闭你的`issue`。 马虎的提问,缺少上下文和重现步骤是在浪费彼此时间。 ================================================ FILE: docs/zh-CN/contribution/pr.md ================================================ --- title: 如何贡献 --- # 如何贡献 ## 文档更新 如果修改了组件代码,需要在组件目录的`metas.yml`加上changes,直接使用`next`作为版本号(如果已经存在该版本号,则直接添加变更条目即可)。 中括号内为变更类型,可选值 `fix` `enhance` `feature` `change` 比如: ``` yml changes: next: en: - '[fix] fix *** bug #issueId' - '[feature] new feature' zh-CN: - '[fix] 修复 *** bug #issueId' ``` ::: tip 当文档相关的 `PR`被合并后,文档服务器会在`5分钟`内拉取最新代码并执行`npm run build-docs`及`npm run build`实现文档及`demo`更新。 ::: ## 为什么使用 next 为版本号 `next` 表示下个版本,未发布时在 `changelog` 里显示 `next`,提醒用户一些开发中的代码尚未发布。 当进行版本发布时,文档中的 `next` 会被脚本代替成真正的版本号,至此发布完毕。 这样的最大好处是更新代码时可以直接写 `changelog` 不用在意要写哪个版本号,在发布时翻 commit 记录写 `changelog` 是件比较浪费时间的事。 ================================================ FILE: docs/zh-CN/contribution/vux-development.md ================================================ --- title: VUX 源码本地运行 --- # VUX 源码本地运行 > phantomjs 可能会遇到无法下载的问题,建议先用 cnpm 全局安装 ``` bash cnpm install -g phantomjs-prebuilt ``` ::: tip 请更新 `NodeJS` 版本到 `v7.6.0` 以上,`build` 命令逐步使用 `async`。 ::: ``` bash yarn # 使用 yarn.lock 保证依赖版本一致 yarn dev # 或者 npm run dev yarn dev -- --env.include datetime,alert # 只运行部分组件,耗时短 ``` ## 本地查看文档 ``` bash npm run doc:build // 构建文档 cd docs yarn npm run dev // 耗时较长 ``` ## 文档相关命令 > docs 目录下的相关命令,它们是 npm run doc:build 的一部分,但是可以单独运行 ``` bash node compile // 生成文档路由 node component-contributions // 从 git 记录里获取每个组件提交历史 npm run doc:dev // 运行 ``` ## 构建部分文档 文档页面比较多等待 build 命令会长,因此可以只构建部分文档方便快速查看。 ``` bash cd docs node compile --include datetime // 只构建 datetime 相关文档,多个时以英文逗号分隔,如:datetime,alert npm run dev ``` ## 官网文档部署说明 线上页面有 `30分钟` 页面缓存,定时每 `20分钟` 构建一次,因此提交后可能会需要 `1小时` 才会更新到官网。 ================================================ FILE: docs/zh-CN/css/1px.md ================================================ --- title: 1px 解决方案 --- # 1px 解决方案 ::: tip 1px 方案在 VUX 组件内应用广泛,包括 `Grid`, `ButtonTab`, `XTable`, `XButton`, `Cell` 等等。 利用 `Flexbox` + `1px` 你可以实现复杂的宫格布局。 ::: ## 引入 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` ## 可用类名: - `vux-1px-l` 左边框 - `vux-1px-r` 右边框 - `vux-1px-t` 上边框 - `vux-1px-b` 下边框 - `vux-1px-tb` 上下边框 - `vux-1px` 全边框 ================================================ FILE: docs/zh-CN/css/close.md ================================================ --- title: 纯 css close 图标 --- # 纯 css close 图标 ::: tip 关闭图标主要应用于弹窗(dialog)关闭按钮以及弹窗(popup)头部的关闭按钮。 ::: ## 使用 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` ## 类名 - `vux-close` ``` html ``` 可以参考 `XDialog` 演示。 ## 颜色 当你想设置图标颜色时,直接设置 color 即可。 ``` html ``` ================================================ FILE: docs/zh-CN/css.md ================================================ --- nav: zh-CN --- ## 1px ::: tip 1px 方案在 VUX 组件内应用广泛,包括 Grid, ButtonTab, XTable, XButton, Cell 等等。

    利用 flexbox + 1px 你可以实现复杂的宫格布局。 ::: 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` 可用类名: - `vux-1px-l` 左边框 - `vux-1px-r` 右边框 - `vux-1px-t` 上边框 - `vux-1px-b` 下边框 - `vux-1px-tb` 上下边框 - `vux-1px` 全边框 ## close ::: tip 关闭图标主要应用于弹窗(popup)的左侧关闭按钮。 ::: 在你项目的`App.vue`引入,组件内不需要再重复引入。 ``` html ``` 可用类名: - vux-close ``` html ``` 可以参考 `XDialog` 演示。 当你想设置图标颜色时,直接设置 color 即可。 ``` html ``` ================================================ FILE: docs/zh-CN/development/i18n.md ================================================ --- title: 国际化 i18n --- # 国际化 ::: warning 暂时只支持配合`vux-loader`使用。 如果你只需要默认的中文组件,那么你可以略过下面说明,只要启用`vux-ui`插件即可。 ::: 默认不配置此插件时,vux源码会按照默认语言`zh-CN`进行静态编译,和原来的使用没有明显不同。 详细请参照 vux-loader的vux-i18n文档 ================================================ FILE: docs/zh-CN/development/remove-click-delay-fastclick.md ================================================ --- title: Vue 应用移除移动端页面点击延迟 --- # 移除移动端页面点击延迟 ::: tip 直接使用 `WeUI` 样式并引入 `fastclick` 会导致一些点击问题,VUX 组件内部已经做了相关处理。 ::: ## 引入 fastclick 在`main.js`里引用`fastclick` ``` js const FastClick = require('fastclick') FastClick.attach(document.body) // done ``` ================================================ FILE: docs/zh-CN/development/router.md ================================================ --- title: vue-router 路由 --- # 路由 推荐直接使用官方 [vue-router](https://github.com/vuejs/vue-router),`VUX`部分组件支持`link`属性直接支持`vue-router`的路由参数,`vux2`模板内置了`vue-router`。 ::: warning 如果使用了过渡(转场动画),在`iPhone`上使用`左划返回`时动画会再执行一遍,目前没有找到可行的处理方法,如果你有处理方案,欢迎`PR`。 [https://github.com/airyland/vux/pull/2259](https://github.com/airyland/vux/pull/2259) ::: ================================================ FILE: docs/zh-CN/development/theme.md ================================================ --- title: 主题颜色配置 --- # 主题颜色配置 ## 配置插件 ::: warning 暂时只支持配合`vux-loader`使用。 注意的是主题文件不能引入其他less文件,只能为简单变量列表。 ::: 请配置vux-loader的`less-theme`插件,指定用以覆盖的less文件路径: ``` js { name: 'less-theme', path: 'src/styles/theme.less' // 相对项目根目录路径 } ``` ## 可配置颜色 源码地址:https://github.com/airyland/vux/blob/v2/src/styles/variable.less ::: tip 更多配置需求请通过 issue 提出。 ::: ## demo站点的示例配置 源代码地址:[https://github.com/airyland/vux/blob/v2/src/theme.less](https://github.com/airyland/vux/blob/v2/src/theme.less) ## 内部如何实现的? `vux-loader` 在每个 `less` 文件的编译过程中重写了 `less-loader` 的变量参数,使其能直接覆盖原来变量。 ================================================ FILE: docs/zh-CN/development/typescript.md ================================================ --- title: TypeScript 支持 --- # TypeScript 支持 > 即将支持 ================================================ FILE: docs/zh-CN/development/vue-ajax.md ================================================ --- title: Vue 中使用 ajax --- # 发送 ajax 请求 :::tip `AjaxPlugin` 插件依赖于 [axios](https://github.com/mzabriskie/axios) 详细 API 文档请查看:[axios](https://github.com/mzabriskie/axios) ::: ## 版本要求 `AjaxPlugin`在`vux@^2.1.0-rc.20`开始支持 ## 引入 `main.js` 入口文件中引入: ``` js import { AjaxPlugin } from 'vux' Vue.use(AjaxPlugin) ``` ## 兼容性问题 需要注意的是`axios`是基于`Promise`的,因此如果你需要兼容低版本浏览器([caniuse](http://caniuse.com/#feat=promises)),需要引入`polyfill`。 `Polyfill` 推荐使用 [es6-promise](https://github.com/stefanpenner/es6-promise) ``` js require('es6-promise').polyfill() ``` ## 全局使用 ``` js Vue.http.post('/api').then() ``` ## 组件中使用 ``` js export default { created () { this.$http.post('/api').then(({data}) => { console.log(data) }) } } ``` ================================================ FILE: docs/zh-CN/development/vue-global-method.md ================================================ --- title: Vue 全局公用函数 --- # vue 全局公用函数 如果你需要让一个工具函数在每个组件可用,可以把方法挂载到 `Vue.prototype`上。 ## 注册 `main.js` 中 ``` js Vue.prototype.$method = function () {} ``` ::: tip 一般建议函数名使用 `$` 前缀。像 `vue-router` 的 `$route` 和 `$router`。 ::: ## 使用 那么组件代码里 ``` js export default { created () { this.$method() } } ``` ## 说明 挂载到 `prototype` 上是为了方便组件内直接用 `this.$method` 来使用,你也可以直接用 `Vue.method`,这样同样可以全局使用,不过在组件内就需要再 import 一次 `Vue` 了。 ================================================ FILE: docs/zh-CN/development/vue-google-analytics.md ================================================ --- title: Vue 中使用谷歌统计 --- # 使用谷歌统计 单页面应用切换时要手动发送页面统计,首先在 `index.html` 或者 `main.js` 里引入谷歌统计代码: ``` js (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-yourID', 'auto') ga('send', 'pageview') // 是否要统计着陆页面访问,取决于你的需求,这个不一定需要,会和`router`统计有重复 ``` ``` js // main.js 里,如果你使用了 vue-router router.afterEach(function (to) { if (window.ga) { window.ga('set', 'page', to.fullPath) // 你可能想根据请求参数添加其他参数,可以修改这里的 to.fullPath window.ga('send', 'pageview') } }) ``` ================================================ FILE: docs/zh-CN/development/vue-loader-autoprefix.md ================================================ --- title: vue-loader autoprefix 推荐配置 --- # autoprefix 推荐配置 `vue`官方模板的设置是`last 2 versions`,可能会导致你在某些`Android`机子上出现问题。 如果你使用 `last 7 versions` 会生成不必要的`-ms`前缀代码. 因此建议同`WeUI`一样,使用配置 `['iOS >= 7', 'Android >= 4.1']` ## 直接在 vux-loader 里配置 如果你没有在 postcss 里配置,你可以直接配置 vux-loader 的 `duplicate-style` 插件: ``` js { name: 'duplicate-style', options: { cssProcessorOptions : { safe: true, zindex: false, autoprefixer: { add: true, "browsers": [ "iOS >= 7", "Android >= 4.1" ] } } } } ``` ================================================ FILE: docs/zh-CN/development/vue-loader-disable-eslint.md ================================================ --- title: vue-loader 禁用 eslint --- # 禁用 eslint 并不推荐禁用`eslint`, 编码规范可以一定程序上保证代码质量。但是如果你确实想禁用,可以删除`build/webpack.base.conf.js`里的相关代码。 ``` js preLoaders: [ { test: /\.vue$/, loader: 'eslint', include: [ path.join(projectRoot, 'src') ], exclude: /node_modules/ }, { test: /\.js$/, loader: 'eslint', include: [ path.join(projectRoot, 'src') ], exclude: /node_modules/ } ] ``` 如果你只是想禁用对某一文件的检测,那么可以在`.eslintignore`里添加规则。 如果你是想禁止某一行的检测,那么可以使用`// eslint-disable-line`。 更加灵活的使用参考 [eslint文档](http://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments)。 ================================================ FILE: docs/zh-CN/development/vue-router-page-transition.md ================================================ --- title: 页面切换转场动画 --- # 页面切换转场动画 你可以注意到 demo 站点的切换动画,本质上是使用 `Vue` 的 `transition` 组件实现的。 - [ ] todo ================================================ FILE: docs/zh-CN/development/vue-show-loading.md ================================================ --- title: 页面切换时显示loading --- # 页面切换显示loading 移动端如果使用异步组件加载那么还是需要一点等待时间的,在网络慢时等待时间会更长。显示`Loading`状态缓解一下用户等待情绪就十分重要。 如果你使用`vue-router`和`vuex`,那么可以很容易实现。 首先,注册一个`module`来保存状态 ``` js const store = new Vuex.Store({}) // 这里你可能已经有其他 module store.registerModule('vux', { // 名字自己定义 state: { isLoading: false }, mutations: { updateLoadingStatus (state, payload) { state.isLoading = payload.isLoading } } }) ``` 然后使用`vue-router`的`beforeEach`和`afterEach`来更改`loading`状态 ``` js router.beforeEach(function (to, from, next) { store.commit('updateLoadingStatus', {isLoading: true}) next() }) router.afterEach(function (to) { store.commit('updateLoadingStatus', {isLoading: false}) }) ``` 在`App.vue`里使用`loading`组件并从`vuex`获取`isLoading`状态 ``` html ``` ``` js import { Loading } from 'vux' import { mapState } from 'vuex' export default { components: { Loading }, computed: { ...mapState({ isLoading: state => state.vux.isLoading }) } } ``` done. 如果你觉得在加载比较快时`Loading`组件一闪而过体验也不大好,那么你可以延迟设置`loading=false`。 ================================================ FILE: docs/zh-CN/development/vue-webpack-code-splitting-async.md ================================================ --- title: Vue 异步加载组件 --- # 异步加载组件 将所有页面组件一次性加载是一个很浪费资源和考验用户耐心的做法,尤其在移动端。 ## 使用方法 `webpack` 提供了[code splitting](https://webpack.js.org/guides/code-splitting-require/),你可以按照下面写法实现当切换到特定路由时才加载代码。 需要注意的是 `vue-loader@13.0.0` 语法有所变更,具体参照发布说明 [v13.0.0](https://github.com/vuejs/vue-loader/releases/tag/v13.0.0) ``` js // vue-loader@13.0.0 之前 const Foo = () => import('./Foo.vue') // 在 Vue 2.4 + vue-router 2.7 版本下可以正确运行 // vue-loader@13.0.0 const Foo = () => import('./Foo.vue').then(m => m.default) ``` ## 组件打包问题 如果你在不同的进行了代码分割的 .vue 文件引入了相同的组件,在打包时两个路由的代码都会重复打包该组件。 你可以对重复使用的组件在 `main.js` 进行全局注册,以减少相应 chunk file 的大小并提高下载速度。 ================================================ FILE: docs/zh-CN/development/vue-webpack-env.md ================================================ --- title: Vue + webpack 区分测试环境和生产环境 --- # 区分测试环境和生产环境 如果你使用了 `vux2` 模板或者 `webpack` 模板,默认你可以直接通过判断 `process.env.NODE_ENV` 来区分 比如`统计代码`仅放在 `production` 环境,在不同环境里使用不同的 `API` 接口地址。 ``` js if (process.env.NODE_ENV === 'production') { // 干一些线上才要做的事情 } if (process.env.NODE_ENV === 'development') { // 干一些测试时不可告人的事情 } ``` 如果你是自己配置的环境,可以参考 [webpack DefinePlugin 文档](https://webpack.js.org/plugins/define-plugin/) ================================================ FILE: docs/zh-CN/development/vux-nuxt-ssr.md ================================================ --- title: vux 使用 nuxt 实现服务端渲染 --- # vux 使用 nuxt 实现服务端渲染 请直接参考源码目录 [/ssr/nuxt](https://github.com/airyland/vux/tree/v2/ssr/nuxt) ================================================ FILE: docs/zh-CN/directives/v-transfer-dom.md ================================================ --- title: v-transfer-dom 指令 --- # v-transfer-dom 指令 在移动端应用里,为了便于代码组织,通常我们要将组件放在各个路由的 `.vue` 文件里,但是因为此时组件并不在 `body` 下,加上定位,overflowscrolling 设置等原因,会出现遮罩在弹层之上,z-index 失效等问题。 因此我们推荐在纯弹窗类组件比如 `Alert` `Popup` `XDialog` 等组件上使用 `v-transfer-dom` 实现自动移动到 body 下,解决以上问题。 ::: warning 必须有一个 `div` 作为占位元素否则会出错 ::: ## 使用 ### 注册局部指令 ``` js import { TransferDom } from 'vux' export default { directives: { TransferDom } } ``` ### 注册全局指令 ``` js import { TransferDom } from 'vux' Vue.directive('transfer-dom', TransferDom) ``` ### 模板使用 ``` html
    ``` ================================================ FILE: docs/zh-CN/faq/$t-is-not-defined.md ================================================ --- title: $t is not defined --- # $t is not defined 一般是两个原因: ## cnpm 导致的问题 如果是直接使用模板后并且使用 `cnpm` 报错,请升级到 `vux-loader@1.0.58`。 该问题出现原因是 `cnpm` 安装后 `node_modules` 里的模块为快捷方式,而 `vux-loader` 之前版本没有考虑到这一特殊情况。 ## 未正确配置动态 i18n demo例子使用`$t`是因为demo启用了`i18n`,如果你没有使用`vuex-i18n`等相关`i18n`插件,请不要在代码中使用`$t`函数 ================================================ FILE: docs/zh-CN/faq/Uncaught-SyntaxError-Unexpected-token-export.md ================================================ --- title: Uncaught SyntaxError: Unexpected token export --- # Uncaught SyntaxError: Unexpected token export 原因是Vux的js源码没配置babel,你可以在webpack配置的loaders加上 ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者启用`vux-loader`的`vux-ui`插件。 ================================================ FILE: docs/zh-CN/faq/can-vux-used-in-weapp.md ================================================ --- title: VUX 能否在微信小程序里使用 --- # VUX 能否在微信小程序里使用 Sorry,不能。微信小程序是个相对封闭的平台,无法简单地适配也没有计划支持小程序版本。 目前企业主体用户可以使用 `web-view` 组件直接显示 web 页面,间接地使用 VUX。 ## mpvue 解决方案能否直接迁移 VUX 目前 [mpvue](http://mpvue.com/) 还不支持 `slot`、`slot-scope`,因此迁移有难度,并且小程序存在较多差异,暂未计划。 ================================================ FILE: docs/zh-CN/faq/cannot-resolve-inline-desc.md ================================================ --- title: Can't resolve '../inline-desc' in --- # Can't resolve '../inline-desc' in webpack resolve 配置 ``` js resolve: { extensions: ['', '.js', '.vue', '.json'] } ``` ================================================ FILE: docs/zh-CN/faq/difference-between-vux-template-and-webpack-template.md ================================================ --- title: vux2 和 Vue 官方 webpack 模板区别是什么? --- # vux2 和 Vue 官方 webpack 模板区别是什么? `vux2` 模板 fork 自 `webpack` 模板并进行了配置,和 `webpack` 模板基本同步,因此建议直接使用 vux2 模板。 ================================================ FILE: docs/zh-CN/faq/document-framework.md ================================================ --- title: 文档是用什么工具编写的? --- # 文档是用什么工具编写的? 早期版本使用的是 [Docute](https://docute.js.org) by [egoist](https://github.com/egoist)。在`Docute`基础上做了一点样式修改。 2.2.1 后面版本出于 `SEO` 考虑使用 `Vue` 的服务端渲染,使用的框架同样是 [egoist](https://github.com/egoist) 开发的 [ream](https://github.com/ream/ream)。 文档部分样式参照了 [eggjs.org](https://eggjs.org/),表示感谢。 因此目前无论是组件还是文档都是完全基于 `Vue`,cool right? ================================================ FILE: docs/zh-CN/faq/does-vux-support-weex.md ================================================ --- title: VUX 是否支持 weex --- # VUX 是否支持 weex 不支持,也没有计划支持。 ================================================ FILE: docs/zh-CN/faq/dupicate-style.md ================================================ --- title: 样式冗余 --- # 样式冗余 ## 调试时 调试时如果你审查元素样式发现有多个重复样式是正常的,因为不同组件可能引用了同样的less源码,而调试时是直接把不同组件样式用` ``` - 配置 `vue-loader`(通过配置vux-loader实现) ``` js // vux-loader module.exports = { configureWebpack: config => { require('@vux/loader').merge(config, { plugins: ['vux-ui', { name: 'less-theme', path: 'src/theme.less' }] }) } } ``` - 配置`babel-loader`以正确编译 VUX 的js源码(通过配置vux-loader实现) ``` js module.exports = { configureWebpack: config => { require('@vux/loader').merge(config, { plugins: ['vux-ui', { name: 'less-theme', path: 'src/theme.less' }] }) } } ``` - 安装`less-loader`以正确编译less源码 ``` bash npm install less less-loader --save-dev ``` - 安装 `yaml-loader` 以正确进行语言文件读取 ``` bash npm install yaml-loader --save-dev ``` - 添加 `viewport` meta ``` html ``` - 添加 `Fastclick` 移除移动端点击延迟 ``` js const FastClick = require('fastclick') FastClick.attach(document.body) ``` - 添加 `vue-router`(如果不需要前端路由,可忽略) ``` js import VueRouter from 'vue-router' Vue.use(VueRouter) ``` - 添加 webpack plugin, 在构建后去除重复css代码(通过配置vux-loader实现) ``` js plugins: [{ name: 'duplicate-style' }] ``` - 如果你使用 `webpack-simple` 模板或者 webpack 配置里缺少 .vue extension 配置,请记得配置: ``` js resolve: { extensions: ['.js', '.vue', '.json'] ``` ================================================ FILE: docs/zh-CN/install/npm.md ================================================ --- title: VUX 安装使用 --- # 安装使用(webpack) ::: warning 如果你从没使用过 `VUX`,请参考 快速入门
    不推荐使用 `umd` 方式引用组件,但是如果不得不使用,可以参考 umd 构建 ::: 直接安装或者更新: ``` js npm install vux --save ``` 或者使用 `yarn` ``` js yarn add vux // 安装 yarn upgrade vux // 更新 ``` 如果你想直接从 Github 安装,请指定 `v2` 分支 ``` bash npm install git://github.com/airyland/vux.git#v2 ``` 如果你是从`0.x`更新,请参考: 更新到`2.x` ::: warning vux2必须配合`vux-loader`使用, 请在`build/webpack.base.conf.js`里参照如下代码进行配置: ::: ``` js const vuxLoader = require('vux-loader') const webpackConfig = originalConfig // 原来的 module.exports 代码赋值给变量 webpackConfig module.exports = vuxLoader.merge(webpackConfig, { plugins: ['vux-ui'] }) ``` ::: warning vux@0.x 已经停止维护,请尽快迁移到 vue@2.x & vuex@2.x & vux@2.x,虽然要花点时间,但是完全值得。 ::: ================================================ FILE: docs/zh-CN/install/umd.md ================================================ --- title: umd 组件构建及使用 --- # umd 组件构建及使用 ::: tip 从`2.0`开始,推荐使用`webpack`来调用组件,因此不再在`repo`中保存`umd`文件,但提供了生成命令。 请更新 `NodeJS` 到版本 `7.6.0`及以上。 例子可查看:[https://github.com/airyland/vux/tree/v2/docs/examples](https://github.com/airyland/vux/tree/v2/docs/examples) ::: ::: danger 强烈建议使用 `webpack` 的方式开发,在 3.0 之后可能不再提供支持。 ::: ## 生成命令 ``` bash git clone https://github.com/airyland/vux.git --depth=1 // or just update: git pull cd vux npm install npm run build-components ``` 默认生成的语言是`zh-CN`,模块命名空间为`vux`,如`vuxGroup`,`vuxCell`,你可以在命令行中配置。 ``` npm run build-components -- --locale='en' --namespace='X' ``` ## 目录结构 生成的文件夹结构如: ::: tip 出于目录结构一致性考虑,即使是子组件也是一个文件夹,并且会有一个空的`index.min.css`样式文件。 ::: ``` |- dist/ |- vux.min.js ------------ 所有组件打包,仅用于测试,不推荐在生产环境使用 |- vux.min.css ----------- 所有组件样式打包,同样仅用于测试 |- components |- actionsheet |- index.min.js -------- 组件js代码 |- index.min.css ------- 组件css代码 ``` ::: tip vux.min.js 包括了所有的组件、插件及默认地址库,都挂载在全局变量vux上。当然为了使用方便同样直接挂载到了`window`上。 组件调用举例: `vuxCell` 插件调用举例:`vuxAlertPlugin` 默认地址库调用:`vuxChinaAddressData` ::: ## 组件使用 ``` html scripts
    ``` ## 插件使用 ``` html scripts
    ``` ## 生成css工具样式 包括`1px`解决方案,构建文件位于`dist/styles/*.css`,构建方式: ``` bash npm run build-styles ``` ::: tip 为了使用方便,可以使用`npm run xbuild`来执行`build-components` 及 `build-styles` ::: ================================================ FILE: docs/zh-CN/install/upgrade-to-vux2.md ================================================ --- title: 升级到 vux@2.x --- # 升级到 vux@2.x ### Vue@2.x 主要变更 参考[Vue官方文档](https://cn.vuejs.org/v2/guide/migration.html)进行迁移, 这也是Vux组件的代码更新部分,主要包括: - `:value.sync` 已经废弃 - `broadcast` 方法已经废弃 - `@click` 需要更改为 `@click.native` - `v-for`的`(index, item)` => `(item, index)` ### vue-router 更新 配合vue2, `vue-router`同样需要更新到2.0版本以上 原来的路由配置修改为: ``` js const routes = [{ path: '/vux/2.0', component: Vux2Demo }] const router = new VueRouter({ routes }) ``` 原来的路由挂载修改为: ``` js new Vue({ router, render: h => h(App) }).$mount('#app') ``` `go` 已经不是过去的 `go`了,要用`push`方法来跳转 ``` js this.$router.go('/somewhere') ``` `v-link`也废弃了,使用`router-link`组件来代替 其他请参考官方升级文档: [https://cn.vuejs.org/v2/guide/migration-vue-router.html](https://cn.vuejs.org/v2/guide/migration-vue-router.html) ### 不再生成`umd`文件 但是你可以使用`npm run build-components`来生成,请参考文档首页。 ### 双向绑定修改为 `v-model` 所有相关Vux调用的 `:value.sync`都需要更改成 `v-model` ``` js // 0.x // 2.x ``` ### 使用 vux-loader 原来你可能在webpack中做了这样的配置以正确编译vux的js源码: ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者你也可能使用了低版本`vux-loader`的`getBabelLoader`方法。 现在你可以直接删除这一句了,直接使用vux-loader。 在`webpack.base.conf.js`中这样配置: ``` js const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [ { name: 'vux-ui' } ] }) ``` ### 引入方式变更 原来你可能是单个组件引入,现在在`vux-loader`的支持下你可以直接这样写: ``` js import { Group, Cell } from 'vux' ``` ### 组件名字变更

    为什么不参照其他组件库全部加上`X`前缀,主要是因为觉得总要写个`X`相当不顺手。

    因Vue2.0限制组件名不能与原有的html5标签一样,因此部分组件进行了重命名,加上 `x-`前缀,所有需要加前缀的组件如下: - `Progress` => `XProgress` - `Switch` => `XSwitch` - `Dialog` => `XDialog` - `Address` => `XAddress` - `Circle` => `XCircle` - `XButton` - `XImg` - `XInput` - `XTextarea` - `XHeader` ### 各个组件变更: #### Swiper 引入路径变化 目录结构变化,与其他有子组件的统一,导致引入方式变化: ``` js // 0.x import Swiper from 'vux/src/components/swiper' import SwiperItem from 'vux/src/components/swiper-item' // 2.0 import Swiper from 'vux/src/components/swiper/swiper' import SwiperItem from 'vux/src/components/swiper/swiper-item' // 或者 import { Swiper, SwiperItem } from 'vux/src/components/swiper' // with vux-loader ``` #### ColorPicker 废弃

    `2.0.0`可用用,但是后面不再维护。

    这个组件可以由`cell`配合`slot`扩展出来,而且更灵活。没有做过统计,但是感觉使用人数应该挺少。 #### Countdown 废弃

    `2.0.0`可用,但是后面不再维护。

    功能薄弱,比较鸡肋。 #### Scroller reset方法更新 由于 Vue@2.x 的`broadcast`方法已经废弃,并且之前的设计也并不是很好,uuid的绑定也其实是没必要的。 - reset方法变成使用ref的`reset()`方法 - pullup reset 变成 ref 的 `donePullup()`方法 - pullup disable 变成 ref 的 `disablePullup()`方法 - pullup enable 变成 ref 的 `enablePullup()`方法 - pulldown reset 变成 ref 的 `donePulldown()`方法 - pullup和pulldown的status绑定变成`v-model="status"`绑定,示例 ``` js status: { pullupStatus: 'default', pulldownStatus: 'default' } ``` 详细参照Scroller文档进行更新 #### 表单默认required为true 保持和`html`规范一致, 影响的组件有 `XInput` `Checklist` #### Checklist 不显示错误提示 考虑到错误样式目前并不优雅,而用户有自定义错误样式的需要,因此处理成emit一个错误事件+底部slot, 用户可自行处理。 #### XInput 的valid获取 由于Vue2的$ref不再是响应的,因此不能直接在模板中通过ref调用组件的valid值(会报undefined),所以需要变成在提交时再进行ref来获取valid值。 #### XAddress 默认地址数据更新 目前引用方式: ``` js import { XAddress, ChinaAddressData } from 'vux' ``` 如果你想继续使用旧版本数据 ``` js import { XAddress, ChinaAddressV1Data } from 'vux' ``` 按照[最新统计局数据](http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201608/t20160809_1386477.html)进行更新,部分区域已经不存在,部分id做了更新。因此请*谨慎*更新,评估后端数据存储设计和前端交互再进行更新,避免错误更新用户数据或者导致数据丢失。 完整更新如下图:

    ================================================ FILE: docs/zh-CN/install/usage.md ================================================ --- title: VUX 调用例子 --- # 调用示例 ## .vue文件中调用组件 ``` html ``` ## main.js中调用plugin ``` js import { AlertPlugin, ToastPlugin } from 'vux' Vue.use(AlertPlugin) Vue.use(ToastPlugin) // 详细使用请参考对应组件文档 ``` ================================================ FILE: docs/zh-CN/lab/index.md ================================================ --- title: 实验室 --- # 实验室 > beta 测试组件将出现在这里。 - [VChart](/zh-CN/components/v-chart.html) ================================================ FILE: docs/zh-CN/production/inline-manifest.md ================================================ --- title: Vue 打包优化:使用 inline manifest --- # inline manifest ::: tip `manifest` 文件为路径配置和异步组件名字列表,该做法可以减少一个`http`请求。 确保 `vux-loader`更新到 `^1.0.35`。 ::: ## 修改 index.html 模板 首先在页面的``前加入代码: ``` html <%=htmlWebpackPlugin.files.webpackManifest%> ``` ## 配置 vux-loader 在`vux-loader`配置的 `plugins` 列表中加入`inline-manifest`插件 ``` js plugins:[{ name: 'inline-manifest' }] ``` 或者简化写法直接使用名字: ``` js plugins:['inline-manifest'] ``` ================================================ FILE: docs/zh-CN/tools/base64.md ================================================ --- title: base64 --- # base64 ``` js import { base64 } from 'vux' base64.encode('VUX') base64.decode('VlVY') ``` ================================================ FILE: docs/zh-CN/tools/cookie.md ================================================ --- title: cookie --- # cookie ``` js import { cookie } from 'vux' ``` `get *cookie.get(name, [options])*` 获取 cookie 值。`options` 参数可选,取值如下: 1. `converter` 转换函数。如果所获取的 cookie 有值,会在返回前传给 `converter` 函数进行转换。 1. 选项对象。对象中可以有两个属性:`converter` 和 `raw`. `raw` 是布尔值,为真时,不会对获取到的 cookie 值进行 URI 解码。 **注**:如果要获取的 cookie 键值不存在,则返回 `undefined`. 例子: ```js // setup document.cookie = 'foo=1' document.cookie = 'bar=2' cookie.get('foo') // returns '1' cookie.get('bar', function(s) { return parseInt(s); } ) // returns 2 ``` `set *cookie.set(name, value, [options])` 设置 cookie 值。参数 `options` 可选,可以有以下属性:`path`(字符串)、`domain`(字符串)、 `expires`(数值或日期对象)、`raw`(布尔值)。当 `raw` 为真值时,在设置 cookie 值时,不会进行 URI 编码。 例子: ```js cookie.set('foo', 3) cookie.set('bar', 4, { domain: 'example.com', path: '/', expires: 30 }) ```` `remove *cookie.remove(name, [options])` 移除指定的 cookie. 例子: ```js cookie.remove('foo') cookie.remove('bar', { domain: 'example.com', path: '/' }) ``` ================================================ FILE: docs/zh-CN/tools/date.md ================================================ --- title: vue date 日期格式化 --- # date format 相对`moment`来说`十分轻量`的实现。 ## 使用 ``` js import { dateFormat } from 'vux' dateFormat(new Date(), 'YYYY-MM-DD HH:mm:ss') ``` ## 作为 filter 使用 ``` js import { dateFormat } from 'vux' export default { filters: { dateFormat } } ``` ================================================ FILE: docs/zh-CN/tools/debounce-throttle.md ================================================ --- title: debounce --- ## debounce ::: warning 请注意了解 `debounce` 和 `throttle` 的区别。如果尚不清楚,请`google`之。 ::: ``` js import { debounce } from 'vux' debounce(func, [wait=0], [options={}]) ``` [详细文档](https://lodash.com/docs/#debounce) ## throttle ``` js import { throttle } from 'vux' throttle(func, [wait=0], [options={}]) ``` [详细文档](https://lodash.com/docs/#throttle) ================================================ FILE: docs/zh-CN/tools/md5.md ================================================ --- title: md5 --- # md5 ::: tip 该工具直接依赖于 [blueimp-md5](https://github.com/blueimp/JavaScript-MD5) 注意: `md5`是消息摘要算法并非加密算法,用于需要加密的场景会有安全问题。 ::: ``` js import { md5 } from 'vux' md5('VUX') ``` ================================================ FILE: docs/zh-CN/tools/number.md ================================================ --- title: vue 数字格式化 --- # number 格式化工具 `numberComma`用于分割数字,默认为3位分割,一般用于格式化`金额`。 `numberPad`用于按照位数补0 `numberRandom`用于生成两个整数范围内的随机整数 ``` js import { numberComma, numberPad, numberRandom } from 'vux' numberComma(21342132) // 21,342,132 numberComma(21342132, 4) // 2134,2132 numberComma(21342132.234) // 21,342,132.234 numberPad(1) // 01 numberPad(234, 4) // 0234 numberRandom(1, 7) // 2 ``` ================================================ FILE: docs/zh-CN/tools/querystring.md ================================================ --- title: Vue querystring url 参数解析 --- # url 参数解析 ```js import { querystring } from 'vux' querystring.parse('a=b&c=d') // {a:'b',c:'d'}, 默认参数为 location.search querystring.stringify({a:'b',c:'d'}) // 'a=b&c=d',注意不支持复杂嵌套的结构 ``` ================================================ FILE: docs/zh-CN/tools/string.md ================================================ --- title: Vue string 工具函数 --- # string 处理工具 ``` js import { trim } from 'vux' trim(' 1024 ') // 1024 ``` ================================================ FILE: docs/zh-CN/umd.md ================================================ --- nav: zh-CN --- ## 组件 umd 文件

    从`2.0`开始,推荐使用`webpack`来调用组件,因此不再在`repo`中保存`umd`文件,但提供了生成命令。
    请更新 `NodeJS` 到版本 `7.6.0`及以上。
    例子可查看:[https://github.com/airyland/vux/tree/v2/docs/examples](https://github.com/airyland/vux/tree/v2/docs/examples)

    ### 生成命令 ``` bash git clone https://github.com/airyland/vux.git --depth=1 // or just update: git pull cd vux npm install npm run build-components ``` 默认生成的语言是`zh-CN`,模块命名空间为`vux`,如`vuxGroup`,`vuxCell`,你可以在命令行中配置。 ``` npm run build-components -- --locale='en' --namespace='X' ``` ### 目录结构 生成的文件夹结构如:

    出于目录结构一致性考虑,即使是子组件也是一个文件夹,并且会有一个空的`index.min.css`样式文件。

    ``` |- dist/ |- vux.min.js ------------ 所有组件打包,仅用于测试,不推荐在生产环境使用 |- vux.min.css ----------- 所有组件样式打包,同样仅用于测试 |- components |- actionsheet |- index.min.js -------- 组件js代码 |- index.min.css ------- 组件css代码 ```

    vux.min.js 包括了所有的组件、插件及默认地址库,都挂载在全局变量vux上。当然为了使用方便同样直接挂载到了`window`上。
    组件调用举例: `vuxCell`
    插件调用举例:`vuxAlertPlugin`
    默认地址库调用:`vuxChinaAddressData`

    ### 组件使用 ``` html scripts
    ``` ### 插件使用 ``` html scripts
    ``` ### 生成css工具样式 包括`1px`解决方案,构建文件位于`dist/styles/*.css`,构建方式: ``` bash npm run build-styles ```

    为了使用方便,可以使用`npm run xbuild`来执行`build-components` 及 `build-styles`

    ================================================ FILE: docs/zh-CN/upgrade-to-2.md ================================================ --- nav: zh-CN --- ### Vue@2.x 主要变更 参考[Vue官方文档](https://cn.vuejs.org/v2/guide/migration.html)进行迁移, 这也是Vux组件的代码更新部分,主要包括: - `:value.sync` 已经废弃 - `broadcast` 方法已经废弃 - `@click` 需要更改为 `@click.native` - `v-for`的`(index, item)` => `(item, index)` ### vue-router 更新 配合vue2, `vue-router`同样需要更新到2.0版本以上 原来的路由配置修改为: ``` js const routes = [{ path: '/vux/2.0', component: Vux2Demo }] const router = new VueRouter({ routes }) ``` 原来的路由挂载修改为: ``` js new Vue({ router, render: h => h(App) }).$mount('#app') ``` `go` 已经不是过去的 `go`了,要用`push`方法来跳转 ``` js this.$router.go('/somewhere') ``` `v-link`也废弃了,使用`router-link`组件来代替 其他请参考官方升级文档: [https://cn.vuejs.org/v2/guide/migration-vue-router.html](https://cn.vuejs.org/v2/guide/migration-vue-router.html) ### 不再生成`umd`文件 但是你可以使用`npm run build-components`来生成,请参考文档首页。 ### 双向绑定修改为 `v-model` 所有相关Vux调用的 `:value.sync`都需要更改成 `v-model` ``` js // 0.x // 2.x ``` ### 使用 vux-loader 原来你可能在webpack中做了这样的配置以正确编译vux的js源码: ``` js { test: /vux.src.*?js$/, loader: 'babel' } ``` 或者你也可能使用了低版本`vux-loader`的`getBabelLoader`方法。 现在你可以直接删除这一句了,直接使用vux-loader。 在`webpack.base.conf.js`中这样配置: ``` js const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [ { name: 'vux-ui' } ] }) ``` ### 引入方式变更 原来你可能是单个组件引入,现在在`vux-loader`的支持下你可以直接这样写: ``` js import { Group, Cell } from 'vux' ``` ### 组件名字变更

    为什么不参照其他组件库全部加上`X`前缀,主要是因为觉得总要写个`X`相当不顺手。

    因Vue2.0限制组件名不能与原有的html5标签一样,因此部分组件进行了重命名,加上 `x-`前缀,所有需要加前缀的组件如下: - `Progress` => `XProgress` - `Switch` => `XSwitch` - `Dialog` => `XDialog` - `Address` => `XAddress` - `Circle` => `XCircle` - `XButton` - `XImg` - `XInput` - `XTextarea` - `XHeader` ### 各个组件变更: #### Swiper 引入路径变化 目录结构变化,与其他有子组件的统一,导致引入方式变化: ``` js // 0.x import Swiper from 'vux/src/components/swiper' import SwiperItem from 'vux/src/components/swiper-item' // 2.0 import Swiper from 'vux/src/components/swiper/swiper' import SwiperItem from 'vux/src/components/swiper/swiper-item' // 或者 import { Swiper, SwiperItem } from 'vux/src/components/swiper' // with vux-loader ``` #### ColorPicker 废弃

    `2.0.0`可用用,但是后面不再维护。

    这个组件可以由`cell`配合`slot`扩展出来,而且更灵活。没有做过统计,但是感觉使用人数应该挺少。 #### Countdown 废弃

    `2.0.0`可用,但是后面不再维护。

    功能薄弱,比较鸡肋。 #### Scroller reset方法更新 由于 Vue@2.x 的`broadcast`方法已经废弃,并且之前的设计也并不是很好,uuid的绑定也其实是没必要的。 - reset方法变成使用ref的`reset()`方法 - pullup reset 变成 ref 的 `donePullup()`方法 - pullup disable 变成 ref 的 `disablePullup()`方法 - pullup enable 变成 ref 的 `enablePullup()`方法 - pulldown reset 变成 ref 的 `donePulldown()`方法 - pullup和pulldown的status绑定变成`v-model="status"`绑定,示例 ``` js status: { pullupStatus: 'default', pulldownStatus: 'default' } ``` 详细参照Scroller文档进行更新 #### 表单默认required为true 保持和`html`规范一致, 影响的组件有 `XInput` `Checklist` #### Checklist 不显示错误提示 考虑到错误样式目前并不优雅,而用户有自定义错误样式的需要,因此处理成emit一个错误事件+底部slot, 用户可自行处理。 #### XInput 的valid获取 由于Vue2的$ref不再是响应的,因此不能直接在模板中通过ref调用组件的valid值(会报undefined),所以需要变成在提交时再进行ref来获取valid值。 #### XAddress 默认地址数据更新 目前引用方式: ``` js import { XAddress, ChinaAddressData } from 'vux' ``` 如果你想继续使用旧版本数据 ``` js import { XAddress, ChinaAddressV1Data } from 'vux' ``` 按照[最新统计局数据](http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201608/t20160809_1386477.html)进行更新,部分区域已经不存在,部分id做了更新。因此请*谨慎*更新,评估后端数据存储设计和前端交互再进行更新,避免错误更新用户数据或者导致数据丢失。 完整更新如下图:

    ================================================ FILE: docs/zh-CN/vux-loader/about.md ================================================ --- title: vux-loader 介绍 --- # vux-loader 介绍

    `vux-loader`工具的作用是对`.vue`代码进行预处理,不限于 vux 组件库。 它是针对`webpack+vue-loader`项目的工程化工具,简化了webpack插件和loader的使用和编写,支持在vue-loader处理之前进行预处理,同时内置对vux组件专用的配置和优化插件。 当然除了.vue文件外,你还可以对js文件进行预处理。说好的处理.vue文件,为什么连`js`文件也不放过呢?因为只有处理js才能实现理想工程化。举个例子,如果用户需要在`main.js`中调用vux的plugin,他需要这样做: ``` js import AlertPlugin from 'vux/src/plugins/Alert' import ToastPlugin from 'vux/src/plugins/Toast' ``` 虽然路径不长,但是看着相当不和谐,为了简化这个操作vux提供了更简洁的写法: ``` js import { AlertPlugin, ToastPlugin } from 'vux' ``` 这个操作即是通过`js-parser`插件解析main.js里的`import`语法来实现的,最终进入`babel`处理的代码和上面单独引入一致。 这个工具也许会帮你进一步打开`Vue`项目工程化的思路。 ::: tip 作为通用工具,即使你没有使用 `VUX`,依然可以使用它来进行各种代码处理。 ::: ## 为什么不使用 babel-plugin-import * VUX 希望使用源码分发 * VUX 希望无侵入地解决一系列的工程问题,不需要用户手动配置 webpack * VUX 希望能统计(匿名)使用情况 而上面这些单纯使用 `babel-plugin-import` 无法做到,所以造了个轮子。 ================================================ FILE: docs/zh-CN/vux-loader/how-vux-loader-works.md ================================================ --- title: vux-loader 实现方式 --- # vux-loader 实现方式 说明一下`vux-loader`是如何和`vue-loader`搞基配合的。 如果你看过`webpack`loader的介绍,理论上说如果需要自己手动先处理代码再传入`vue-loader`,你只需要这样对loader做配置: `vue-loader!my-loader`。但是如果你试过,就知道这个目前行不通。`vue-loader`不会接收上一个loader的source进行处理,因为.vue文件的特殊性,它直接生成了loader string进入下一个处理,处理后的结果大概是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 那么vue-loader提供了自定义loader string的选项,直接配置可否呢,[#531](https://github.com/vuejs/vue-loader/issues/531),直接配置会破坏原来的loader参数,导致不能Live reload。 那么最终能想到的就是直接修改`vue-loader`生成的代码,这样既不会影响`vue-loader`原来的逻辑,也能自由控制。 最终实现方式是前置`vux-loader`即:`vux-loader!vue-loader`,vux-loader将vue-loader生成的代码二次处理,分割loader string并(强行)插入vux的 template-loader, style-loader, script-loader,那么你就能理解vux-loader的配置方式了,在3大基本loader里获取到vux-loader的插件配置列表逐个做处理就行了。 上面的代码强行处理后是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!./../../../../vux-loader/src/style-loader.js!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./../../../../vux-loader/src/script-loader.js!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!./../../../../vux-loader/src/template-loader.js!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 原理就是这么简单(但是处理时还是遇到一些问题),为了方便使用,编写了常用的一些插件。 至于`js-parser`就只是在`babel-loader`前加上了`vux-loader`的`js-loader`。 而所有的loader, plugins设置都是在`vux-loader`的`merge`方法里来完成。 ================================================ FILE: docs/zh-CN/vux-loader/install.md ================================================ --- title: vux-loader 使用 --- # vux-loader 使用 ## 安装 ``` bash npm install vux-loader --save-dev ``` ## 使用 为了降低使用成本及不侵入原来配置,只需要调用`merge`方法对原来`webpack`配置进行操作: ``` js const webpackConfig = {} // 原来的 webpack.base.js 配置 const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [{ name: 'vux-ui' }] }) ``` ::: warning 更新配置后需要重新运行 `npm run dev` 命令。 ::: ## Options - `env` 非必选,定义当前环境变量,只在 vux-loader 里使用,用来判断哪些插件需要被执行(如果plugin有定义envs的话),目的是实现一份配置多个环境使用。 ## Plugins 插件为一个数组列表,根据需要可以添加你需要的插件,插件名为必须,有些组件不需要额外配置选项。 公用参数为: - `name` `必须`,插件名字 - `envs` `非必须`,数组,当前插件在哪些环境变量里执行,不定义则默认执行 下面的插件配置代码将省略 `plugins:[]`的书写。 ::: tip 当使用默认配置时,可以使用简写 ``` js plugins: ['vux-ui'] ``` 等同于 ``` js plugins: [{ name: 'vux-ui' }] ``` ::: ================================================ FILE: docs/zh-CN/vux-loader/plugins.md ================================================ --- title: vux-loader 插件列表 --- # vux-loader 插件列表 ## script-parser > .vue 文件的 script 部分代码处理 ``` js [{ name: 'script-parser', fn: function (source) { return source.replace('VARIABLE', 'v2') } }] ``` ## style-parser > `` 代码处理 ``` js [{ name: 'style-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` ## template-parser > template代码替换处理 > 适用于对``模板代码做自定义处理 * name 插件名字, `template-parser` * replaceList 非必须,正则匹配列表,将直接调用 `replace` 方法进行替换 * fn 非必须,相比params更灵活的方法,可以对字符串进行处理后返回 插件配置: ``` js [ name: "template-parser", replaceList: [{ test: /DeathToPM/g, replaceString: '微博-随时随地发现新鲜事' }, { test: /呵呵我们压根没有底线/g, replaceString: '我是有底线的' }], fn: function (templateSource) { return templateSource.replace('智障', '机智') } ] ``` #### js-parser > 项目里js文件处理,在babel-loader前进行处理 ``` js [{ name: 'js-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` ## template-feature-switch 实现根据变量切换template代码 参数: - `features`,变量列表,值只能为true或者false ``` js { name: 'template-feature-switch', features: { feature1: true, feature2: false } } ``` ``` html ``` 那么当 feature1 为 true时,将输出 `AWESOME FEATURE 1 is ON`, 反之则输出 `AWESOME FEATURE 1 is OFF`。注意`on`标签内不限定内容,可以为任何标签代码块,但`避免在on off 里面再嵌套 on off` ## vux-ui

    vux组件的配套工具,如果没有使用vux, 不需要添加。

    如果配合`vux`使用,需要手动启用。默认不需要设置选项。 该插件做了以下处理: - 配置`babel`编译 vux 的js源码 - 修改vue-loader为 `vux-loader!vue-loader` - `import`组件调用解析为单组件引入 ``` { name: 'vux-ui' } ``` 那么你就可以很方便地引入组件了: ``` // 0.x import Group from 'vux/src/components/group' import Cell from 'vux/src/components/cell' // 2.x import { Group, Cell } from 'vux' ``` ## i18n 1. 如果你只是调用中文语言的vux组件,那么你不需要做任何配置。 2. 如果你需要调用英文语言的vux组件,需要配置语言 ``` js { name: 'i18n', vuxStaticReplace: true, vuxLocale: 'en' } ``` 3. 如果你想和demo站点一样可以写`i18n`block,并且需要动态切换语言,那么需要配置插件抽取i18n的内容,并设置非静态替换 ``` js { name: 'i18n', vuxStaticReplace: false, staticReplace: false, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] } ``` 然后你就可以引用`vuex-i18n`插件实现多语言切换了。 参考 [main.js](https://github.com/airyland/vux/blob/v2/src/main.js#L84-L96) 及 [vuex-i18n 文档](https://www.npmjs.com/package/vuex-i18n)(仅当参考,你也可以使用其他i18n插件)。 ## less-theme

    注意,path所在文件必须是简单的less变量对,不能import其他文件或者引入变量。

    > less 变量设置和替换 > 适用于全局变量替换, 方便切换主题 > 这意味着,你不再需要为每个页面引入全局的less文件了,你只需要设置lang为less就可以直接使用变量了 ``` ``` 插件配置: ``` js { name: 'less-theme', path: 'src/styles/theme.less' } ``` ## duplicate-style > css 重复代码清除

    建议使用vux组件的用户使用,因为vux直接引用less,最终构建的css文件确实会有冗余。

    > 在webpack 构建完成后对生成的css文件使用cssnano进行重复样式清除。建议只在production环境下开启,因为dev环境没有必要。 ``` js { name: 'duplicate-style', events: { done: function () { console.log('done!') } } } ``` ## html-build-callback >html文件处理事件回调 > 适用于在写入html(一般为index.html)文件前进行内容修改,比如替换特定内容 > 比如写入js配置变量,改变cdn域名,将manifest文件直接写入html以减少请求等 ``` js { name: 'html-build-callback', events: { 'after-html-processing': function (data, cb) { data.html += 'magic footer' cb(null, data) } } } ``` 可用事件请参考 [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin) ## build-done-callback > 构建完成事件回调 > 本质上是在webpack plugin的 done 事件后触发 ``` js { name: 'build-done-callback', fn: function () { console.log('done!') } } ``` ================================================ FILE: docs/zh-CN/vux-loader.md ================================================ --- nav: zh-CN ---

    vux!vue

    ### 介绍 `vux-loader`工具的作用是对`.vue`代码进行预处理,不限于 vux 组件库。 它是针对`webpack+vue-loader`项目的工程化工具,简化了webpack插件和loader的使用和编写,支持在vue-loader处理之前进行预处理,同时内置对vux组件专用的配置和优化插件。 当然除了.vue文件外,你还可以对js文件进行预处理。说好的处理.vue文件,为什么连`js`文件也不放过呢?因为只有处理js才能实现理想工程化。举个例子,如果用户需要在`main.js`中调用vux的plugin,他需要这样做: ``` js import AlertPlugin from 'vux/src/plugins/Alert' import ToastPlugin from 'vux/src/plugins/Toast' ``` 虽然路径不长,但是看着相当不和谐,为了简化这个操作vux提供了更简洁的写法: ``` js import { AlertPlugin, ToastPlugin } from 'vux' ``` 这个操作即是通过`js-parser`插件解析main.js里的`import`语法来实现的,最终进入babel的代码和上面单独引入一致。 这个工具也许会帮你进一步打开`Vue`项目工程化的思路。

    作为通用工具,即使你没有使用Vux,依然可以使用它来进行各种代码处理。
    目前支持 vue-loader@>9.x, 低版本没有具体测试。
    `vux!vue`的loader写法只兼容webpack@1.x,为了兼容webpack@2.x,处理后的loader配置是`vux-loader!vue-loader`

    ### 实现方式 说明一下`vux-loader`是如何和`vue-loader`搞基配合的,vue-loader的issue里不少同学遇到预处理.vue的问题。 如果你看过`webpack`loader的介绍,理论上说如果需要自己手动先处理代码再传入`vue-loader`,你只需要这样对loader做配置: `vue-loader!my-loader`。但是如果你试过,就知道这个目前行不通。`vue-loader`不会接收上一个loader的source进行处理,因为.vue文件的特殊性,它直接生成了loader string进入下一个处理,处理后的结果大概是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 那么vue-loader提供了自定义loader string的选项,直接配置可否呢,[#531](https://github.com/vuejs/vue-loader/issues/531),直接配置会破坏原来的loader参数,导致不能Live reload。 那么最终能想到的就是直接修改`vue-loader`生成的代码,这样既不会影响`vue-loader`原来的逻辑,也能自由控制。 最终实现方式是前置`vux-loader`即:`vux-loader!vue-loader`,vux-loader将vue-loader生成的代码二次处理,分割loader string并(强行)插入vux的 template-loader, style-loader, script-loader,那么你就能理解vux-loader的配置方式了,在3大基本loader里获取到vux-loader的插件配置列表逐个做处理就行了。 上面的代码强行处理后是这样的: ``` js /* styles */ require("!!vue-style-loader!css-loader!vue-loader/lib/style-rewriter?id=data-v-20d7ba9a!./../../../../vux-loader/src/style-loader.js!vue-loader/lib/selector?type=styles&index=0!./index.vue") /* script */ __vue_exports__ = require("!!babel-loader!vue-loader/lib/selector?type=script&index=0!./../../../../vux-loader/src/script-loader.js!./index.vue") /* template */ var __vue_template__ = require("!!vue-loader/lib/template-compiler?id=data-v-20d7ba9a!./../../../../vux-loader/src/template-loader.js!vue-loader/lib/selector?type=template&index=0!./index.vue") ``` 原理就是这么简单(但是处理时还是遇到一些问题),为了方便使用,编写了常用的一些插件。 至于`js-parser`就只是在`babel-loader`前加上了`vux-loader`的`js-loader`。 而所有的loader, plugins设置都是在`vux-loader`的`merge`方法里来完成。 ### 实际使用场景 我们可以使用`vux-loader`做一些很有意思的事。 比如 [#542](https://github.com/vuejs/vue-loader/issues/542) 提到的根据当前feature为判断要输出哪一部分代码,现在已经直接支持,请看 `template-feature-switch`部分。 对于公用组件而言,完全可以实现构建时进行组件瘦身,只保留使用到的代码。 当然如果你愿意,你完全可以自己实现一门语言。 ## 安装 ``` bash npm install vux-loader --save-dev ``` ## 使用 为了减少使用成本,只需要调用`merge`方法对原来`webpack`配置进行操作: ``` js const webpackConfig = {} // 原来的webpack配置 const vuxLoader = require('vux-loader') module.exports = vuxLoader.merge(webpackConfig, { options: {}, plugins: [{ name: 'vux-ui' }] }) ```

    更新配置后需要重启npm run dev命令

    ### Options - `env` 非必选,定义当前环境变量,只在vux-loader里使用,用来判断哪些插件需要被执行(如果plugin有定义envs的话),目的是实现一份配置多个环境使用。 ### Plugins 插件为一个数组列表,根据需要可以添加你需要的插件,插件名为必须,有些组件不需要额外配置选项。 公用参数为: - `name` `必须`,插件名字 - `envs` `非必须`,数组,当前插件在哪些环境变量里执行,不定义则默认执行 下面的插件配置代码将省略 `plugins:[]`的书写。 #### script-parser > script代码替换处理 ``` js [{ name: 'script-parser', fn: function (source) { return source.replace('VARIABLE', 'v2') } }] ``` #### style-parser > style代码替换处理 ``` js [{ name: 'style-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` #### template-parser > template代码替换处理 > 适用于对``模板代码做自定义处理 > 适用于某些更改不频繁但非服务端配置的文字,可能调用多次,也可能手动更改或者批量替换相对麻烦 > > 同样你也可以用来从接口获取最新配置替换特定的占位字符 > > 当然也适用于在源码中对`pm`进行吐槽(千万要记得production环境要有配置,否则可能会上新闻。) * name 插件名字, `template-parser` * replaceList 非必须,正则匹配列表,将直接调用 `replace` 方法进行替换 * fn 非必须,相比params更灵活的方法,可以对字符串进行处理后返回 插件配置: ``` js [ name: "template-parser", replaceList: [{ test: /DeathToPM/g, replaceString: '微博-随时随地发现新鲜事' }, { test: /呵呵我们压根没有底线/g, replaceString: '我是有底线的' }], fn: function (templateSource) { return templateSource.replace('智障', '机智') } ] ``` #### js-parser > 项目里js文件处理,在babel-loader前进行处理 ``` js [{ name: 'js-parser', fn: function (source) { return source.replace('black', 'white') } }] ``` #### template-feature-switch 实现根据变量切换template代码 参数: - `features`,变量列表,值只能为true或者false ``` js { name: 'template-feature-switch', features: { feature1: true, feature2: false } } ``` ``` html ``` 那么当 feature1 为 true时,将输出 `AWESOME FEATURE 1 is ON`, 反之则输出 `AWESOME FEATURE 1 is OFF`。注意`on`标签内不限定内容,可以为任何标签代码块,但`避免在on off 里面再嵌套 on off` #### vux-ui

    vux组件的配套工具,如果没有使用vux, 不需要添加。

    如果配合`vux`使用,需要手动启用。默认不需要设置选项。 该插件做了以下处理: - 配置`babel`编译 vux 的js源码 - 修改vue-loader为 `vux-loader!vue-loader` - `import`组件调用解析为单组件引入 ``` { name: 'vux-ui' } ``` 那么你就可以很方便地引入组件了: ``` // 0.x import Group from 'vux/src/components/group' import Cell from 'vux/src/components/cell' // 2.x import { Group, Cell } from 'vux' ``` #### i18n

    请使用正确的 yml 格式。冒号和值之间是有一个空格的,错误的格式将无法生效。

    ``` yml on-show: en: emits when popup shows zh-CN: 弹窗显示时触发 ``` 1. 如果你只是调用中文语言的vux组件,那么你不需要做任何配置。 2. 如果你需要调用英文语言的vux组件,需要配置语言 ``` js { name: 'i18n', vuxStaticReplace: true, vuxLocale: 'en' } ``` 3. 如果你想和demo站点一样可以写`i18n`block,并且需要动态切换语言,那么需要配置插件抽取i18n的内容,并设置非静态替换 ``` js { name: 'i18n', vuxStaticReplace: false, staticReplace: false, extractToFiles: 'src/locales/components.yml', localeList: ['en', 'zh-CN'] } ``` 然后你就可以引用`vuex-i18n`插件实现多语言切换了。 参考 [main.js](https://github.com/airyland/vux/blob/254574ef30a8c4d341feb7d2ff8245792657bda2/src/main.js#L84-L96) 及 [vuex-i18n 文档](https://www.npmjs.com/package/vuex-i18n)(仅当参考,你也可以使用其他i18n插件)。 #### less-theme

    注意,path所在文件必须是简单的less变量对,不能import其他文件或者引入变量。

    > less 变量设置和替换 > 适用于全局变量替换, 方便切换主题 > 这意味着,你不再需要为每个页面引入全局的less文件了,你只需要设置lang为less就可以直接使用变量了 ``` ``` 插件配置: ``` js { name: 'less-theme', path: 'src/styles/theme.less' } ``` #### duplicate-style > css 重复代码清除

    建议使用vux组件的用户使用,因为vux直接引用less,最终构建的css文件确实会有冗余。

    > 在webpack 构建完成后对生成的css文件使用cssnano进行重复样式清除。建议只在production环境下开启,因为dev环境没有必要。 ``` js { name: 'duplicate-style', events: { done: function () { console.log('done!') } } } ``` #### html-build-callback >html文件处理事件回调 > 适用于在写入html(一般为index.html)文件前进行内容修改,比如替换特定内容 > 比如写入js配置变量,改变cdn域名,将manifest文件直接写入html以减少请求等 ``` js { name: 'html-build-callback', events: { 'after-html-processing': function (data, cb) { data.html += 'magic footer' cb(null, data) } } } ``` 可用事件请参考 [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin) #### build-done-callback > 构建完成事件回调 > 本质上是在webpack plugin的 done 事件后触发 ``` js { name: 'build-done-callback', fn: function () { console.log('done!') } } ``` ### 使用案例 #### 统一引入less(sass)变量 如果你翻看`vue-loader`的issue, 就会发现不少同学在说,为什么sass, less 变量不能全局使用,需要在每个.vue组件里引入,这个很重复,有没有办法解决。目前除了在webpack里定义路径`alias`外没有其他方法。但是有了`vux-loader`,你可以用`style-parser`在每个`.vue`文件的style前面加上变量的引入了,只要一句代码。 ``` { name: 'style-parser', fn: function (source) { return "@import '../styles/variable.less'\n" + source // 你可以根据this.resourcePath 来确定是否要引入以及引入的相对路径 } } ``` #### 减少重复代码 `vux`的组件有几十个,同样demo也有几十个,因为webpack并不支持require变量,那么在main里中实现每个组件异步加载都需要这样: ``` js const routes = [ { path: '/component/actionsheet', component: function (resolve) { require(['./demos/Actionsheet'], resove) } } ] ``` 作为懒人,写几十次这样的代码是一件比较烦人的事,作为热爱地球的人,这样也很不环保,但是有了`vux-loader`, 我们可以这样: ``` js const routes = [] ``` 然后在`js-parser`里获取列表数组直接替换, 并且可以调用webpack的`this.addDependency`添加依赖实现修改列表时自动`reload`。从此添加删除组件只要加上或者删除名字就可以了,真是机智。 ``` js { name: 'js-parser', test: /main\.js/, fn: function (source) { this.addDependency(demoPath) let list = fs.readFileSync(demoPath, 'utf-8') list = JSON.parse(list) let str = [] list.forEach(one => { str.push(`{ path: '/component/${toDash(one)}', component: function (resolve) { require(['./demos/${one}.vue'], resolve) } }`) }) str = `[${str.join(',\n')}]` source = source.replace('const routes = []', 'const routes = ' + str) return source } } ``` #### 实现import语法转换 vux是没有main入口文件的,因此需要把import语句转换成单个组件引入。但是因为对语法树分析比较烦,能用正则替换的当然就是用正则替换,简单粗暴实用。具体可以看源码。 #### 开源组件多语言支持 如果以js方式分发,一般默认一个语言,用户可以加上另外的语言包,但是代码里必不可少存在相应的转换函数,对于只需要单语言的人来是个浪费和繁琐。要么所有一起打包,这个也极浪费。要么一个一个打包,这样又失去了动态多语言支持。 于是`vux`在`vux-loader`的支持下实现了`使用时构建`,无论是静态输出和动态支持。 ### Bug 反馈 && 功能建议 请到[https://github.com/airyland/vux-loader/issues](https://github.com/airyland/vux-loader/issues) ### Todo - [ ] 支持各个插件的异步返回形式 - [ ] 在插件中提供webpack的 this 参数 ================================================ FILE: docs/zh-CN/wechat/other.md ================================================ --- title: 其他问题 --- ## 如何实现`weui.io`或者星巴克页面的设置顶部bar和页面下拉背景色 微信对于部分合作方开放了`jssdk`的`setBounceBackground`及`setNavigationBarColor`权限,目测暂无申请入口。 ================================================ FILE: docs/zh-CN/wechat/vue-wechat-jssdk.md ================================================ --- title: Vue 应用中使用微信 jssdk --- # Vue 应用中使用微信 jssdk ::: warning 分享接口只有认证公众号才能使用,域名必须备案且在微信后台设置。 先确认已经满足使用`jssdk`的要求再进行开发。 ::: `WechatPlugin` 插件提供了`commonJS`的引入方式,因此你不需要在 `index.html` 引入文件。 ## 版本要求 `WechatPlugin`在`vux@^2.1.0-rc.19`开始支持 ## 引入 在 `main.js` 中全局引入: ``` js import { WechatPlugin } from 'vux' Vue.use(WechatPlugin) console.log(Vue.wechat) // 可以直接访问 wx 对象。 ``` ## 组件外使用 考虑到你需要在引入插件后调用`config`方法进行配置,你可以通过 `Vue.wechat` 在组件外部访问`wx`对象。 `jssdk`需要请求签名配置接口,你可以直接使用 VUX 基于 `Axios` 封装的 `AjaxPlugin`。 ``` js import { WechatPlugin, AjaxPlugin } from 'vux' Vue.use(WechatPlugin) Vue.use(AjaxPlugin) Vue.http.get('/api', ({data}) => { Vue.wechat.config(data.data) }) ``` ## 组件中使用 那么之后任何组件中都可以通过 `this.$wechat` 访问到 `wx` 对象。 ``` js export default { created () { this.$wechat.onMenuShareTimeline({ title: 'hello VUX' }) } } ``` ================================================ FILE: docs/zh-CN/wechat/wechat-document-title.md ================================================ --- title: 微信 Vue 单页面应用设置标题 --- # 微信 Vue 单页面应用设置标题 在微信`iOS` `webview`更新到`WKWebView`之前我们可以通过加载一个 `iframe` 来实现单页面应用`title`更改。但是17年初更新到 `WKWebView` 后该方法也失效。 目前该问题已经解决,在微信 iOS 客户端 `6.3.5` 之后的版本都可以通过 `document.title` 设置标题了。 ================================================ FILE: index.html ================================================ vux-ui
    <%=htmlWebpackPlugin.files.webpackManifest%> ================================================ FILE: index.js ================================================ // THIS FILE IS ONLY FOR IDE ENTRY CHECKING NOT FOR REAL USAGE console.warn('VUX: 如果你看到这一行,说明 vux-loader 配置有问题或者代码书写规范的原因导致无法解析成按需引入组件,会导致打包体积过大。请升级到最新版本 vux-loader,建议开启 eslint(standard)。') import Actionsheet from './src/components/actionsheet/index.vue' import Agree from './src/components/agree/index.vue' import AjaxPlugin from './src/plugins/ajax/index.js' import Alert from './src/components/alert/index.vue' import AlertModule from './src/plugins/alert/module' import AlertPlugin from './src/plugins/alert/index.js' import AppPlugin from './src/plugins/app/index.js' import Array2stringFilter from './src/filters/array2String.js' import Badge from './src/components/badge/index.vue' import base64 from './src/tools/base64/index.js' import Blur from './src/components/blur/index.vue' import Box from './src/components/box/index.vue' import BusPlugin from './src/plugins/bus/index.js' import ButtonTab from './src/components/button-tab/button-tab.vue' import ButtonTabItem from './src/components/button-tab/button-tab-item.vue' import Calendar from './src/components/calendar/index.vue' import Card from './src/components/card/index.vue' import Cell from './src/components/cell/index.vue' import CellBox from './src/components/cell-box/index.vue' import CellFormPreview from './src/components/cell-form-preview/index.vue' import Checker from './src/components/checker/checker.vue' import CheckerItem from './src/components/checker/checker-item.vue' import CheckIcon from './src/components/check-icon/index.vue' import Checklist from './src/components/checklist/index.vue' import ChinaAddressData from './src/datas/china_address.json' import ChinaAddressV1Data from './src/datas/china_address_v1.json' import ChinaAddressV2Data from './src/datas/china_address_v2.json' import ChinaAddressV3Data from './src/datas/china_address_v3.json' import ChinaAddressV4Data from './src/datas/china_address_v4.json' import ChinamobileTool from './src/tools/validator/chinaMobile.js' import ClickOutsideDirective from './src/directives/click-outside/index.js' import Clocker from './src/components/clocker/index.vue' import CloseDialogsPlugin from './src/plugins/close-dialogs/index.js' import ColorPicker from './src/components/color-picker/index.vue' import ConfigPlugin from './src/plugins/config/index.js' import Confirm from './src/components/confirm/index.vue' import ConfirmPlugin from './src/plugins/confirm/index.js' import cookie from './src/tools/cookie/index.js' import Countdown from './src/components/countdown/index.vue' import Countup from './src/components/countup/index.vue' import dateFormat from './src/tools/date/format.js' import dateRange from './src/tools/date/range.js' import Datetime from './src/components/datetime/index.vue' import DatetimePlugin from './src/plugins/datetime/index.js' import DatetimeRange from './src/components/datetime-range/index.vue' import DatetimeView from './src/components/datetime-view/index.vue' import debounce from './src/tools/debounce/index.js' import Demobasic from './src/components/fullpage/DemoBasic.vue' import Demoindex from './src/components/popover/DemoIndex.vue' import DevicePlugin from './src/plugins/device/index.js' import DevTip from './src/components/dev-tip/index.vue' import Divider from './src/components/divider/index.vue' import Drawer from './src/components/drawer/index.vue' import Flexbox from './src/components/flexbox/flexbox.vue' import FlexboxItem from './src/components/flexbox/flexbox-item.vue' import Flow from './src/components/flow/flow.vue' import FlowLine from './src/components/flow/flow-line.vue' import FlowState from './src/components/flow/flow-state.vue' import FormatTimeFilter from './src/filters/format-time.js' import FormPreview from './src/components/form-preview/index.vue' import FriendlyTimeFilter from './src/filters/friendly-time.js' import Fullpage from './src/components/fullpage/index.vue' import Grid from './src/components/grid/grid.vue' import GridItem from './src/components/grid/grid-item.vue' import Group from './src/components/group/index.vue' import GroupTitle from './src/components/group-title/index.vue' import Icon from './src/components/icon/index.vue' import InlineCalendar from './src/components/inline-calendar/index.vue' import InlineDesc from './src/components/inline-desc/index.vue' import InlineLoading from './src/components/inline-loading/index.vue' import InlineXNumber from './src/components/inline-x-number/index.vue' import InlineXSwitch from './src/components/inline-x-switch/index.vue' import InviewDirective from './src/directives/inview/index.js' import Loading from './src/components/loading/index.vue' import LoadingModule from './src/plugins/loading/module' import LoadingPlugin from './src/plugins/loading/index.js' import LoadMore from './src/components/load-more/index.vue' import LocalePlugin from './src/plugins/locale/index.js' import Marquee from './src/components/marquee/marquee.vue' import MarqueeItem from './src/components/marquee/marquee-item.vue' import Masker from './src/components/masker/index.vue' import md5 from './src/tools/md5/index.js' import Msg from './src/components/msg/index.vue' import Name2valueFilter from './src/filters/name2value.js' import numberComma from './src/tools/number/comma.js' import numberPad from './src/tools/number/pad.js' import numberRandom from './src/tools/number/random.js' import numberRange from './src/tools/number/range.js' import NumberRoller from './src/components/number-roller/index.vue' import Panel from './src/components/panel/index.vue' import Picker from './src/components/picker/index.vue' import Popover from './src/components/popover/index.vue' import Popup from './src/components/popup/index.vue' import PopupHeader from './src/components/popup-header/index.vue' import PopupPicker from './src/components/popup-picker/index.vue' import PopupRadio from './src/components/popup-radio/index.vue' import Previewer from './src/components/previewer/index.vue' import Qrcode from './src/components/qrcode/index.vue' import querystring from './src/tools/querystring/index.js' import Radio from './src/components/radio/index.vue' import Range from './src/components/range/index.vue' import Rater from './src/components/rater/index.vue' import Scroller from './src/components/scroller/index.vue' import Search from './src/components/search/index.vue' import Selector from './src/components/selector/index.vue' import Shake from './src/components/shake/index.vue' import Spinner from './src/components/spinner/index.vue' import Step from './src/components/step/step.vue' import StepItem from './src/components/step/step-item.vue' import Sticky from './src/components/sticky/index.vue' import stringTrim from './src/tools/string/trim.js' import Swipeout from './src/components/swipeout/swipeout.vue' import SwipeoutButton from './src/components/swipeout/swipeout-button.vue' import SwipeoutItem from './src/components/swipeout/swipeout-item.vue' import Swiper from './src/components/swiper/swiper.vue' import SwiperItem from './src/components/swiper/swiper-item.vue' import Tab from './src/components/tab/tab.vue' import Tabbar from './src/components/tabbar/tabbar.vue' import TabbarItem from './src/components/tabbar/tabbar-item.vue' import TabItem from './src/components/tab/tab-item.vue' import throttle from './src/tools/throttle/index.js' import Timeline from './src/components/timeline/timeline.vue' import TimelineItem from './src/components/timeline/timeline-item.vue' import Tip from './src/components/tip/index.vue' import Toast from './src/components/toast/index.vue' import ToastPlugin from './src/plugins/toast/index.js' import TransferDom from './src/directives/transfer-dom/index.js' import TransferDomDirective from './src/directives/transfer-dom/index.js' import trim from './src/tools/string/trim' import Value2nameFilter from './src/filters/value2name.js' import VArea from './src/components/v-chart/v-area.vue' import VAxis from './src/components/v-chart/v-axis.vue' import VBar from './src/components/v-chart/v-bar.vue' import VChart from './src/components/v-chart/v-chart.vue' import VGuide from './src/components/v-chart/v-guide.vue' import Video from './src/components/video/index.vue' import ViewBox from './src/components/view-box/index.vue' import VLegend from './src/components/v-chart/v-legend.vue' import VLine from './src/components/v-chart/v-line.vue' import VPie from './src/components/v-chart/v-pie.vue' import VPoint from './src/components/v-chart/v-point.vue' import VScale from './src/components/v-chart/v-scale.vue' import VTooltip from './src/components/v-chart/v-tooltip.vue' import VuxComponentListData from './src/datas/vux_component_list.json' import WechatEmotion from './src/components/wechat-emotion/index.vue' import WechatPlugin from './src/plugins/wechat/index.js' import WeekCalendar from './src/components/week-calendar/index.vue' import WepayInput from './src/components/wepay-input/index.vue' import XAddress from './src/components/x-address/index.vue' import XButton from './src/components/x-button/index.vue' import XCircle from './src/components/x-circle/index.vue' import XDialog from './src/components/x-dialog/index.vue' import XHeader from './src/components/x-header/index.vue' import XHr from './src/components/x-hr/index.vue' import XImg from './src/components/x-img/index.vue' import XInput from './src/components/x-input/index.vue' import XNumber from './src/components/x-number/index.vue' import XProgress from './src/components/x-progress/index.vue' import XSwitch from './src/components/x-switch/index.vue' import XTable from './src/components/x-table/index.vue' import XTextarea from './src/components/x-textarea/index.vue' import XForm from './src/components/x-form/form.vue' import XFormField from './src/components/x-form/field.vue' export { Actionsheet, Agree, AjaxPlugin, Alert, AlertModule, AlertPlugin, AppPlugin, Array2stringFilter, Badge, base64, Blur, Box, BusPlugin, ButtonTab, ButtonTabItem, Calendar, Card, Cell, CellBox, CellFormPreview, Checker, CheckerItem, CheckIcon, Checklist, ChinaAddressData, ChinaAddressV1Data, ChinaAddressV2Data, ChinaAddressV3Data, ChinaAddressV4Data, ChinamobileTool, ClickOutsideDirective, Clocker, CloseDialogsPlugin, ColorPicker, ConfigPlugin, Confirm, ConfirmPlugin, cookie, Countdown, Countup, dateFormat, dateRange, Datetime, DatetimePlugin, DatetimeRange, DatetimeView, debounce, Demobasic, Demoindex, DevicePlugin, DevTip, Divider, Drawer, Flexbox, FlexboxItem, Flow, FlowLine, FlowState, FormatTimeFilter, FormPreview, FriendlyTimeFilter, Fullpage, Grid, GridItem, Group, GroupTitle, Icon, InlineCalendar, InlineDesc, InlineLoading, InlineXNumber, InlineXSwitch, InviewDirective, Loading, LoadingModule, LoadingPlugin, LoadMore, LocalePlugin, Marquee, MarqueeItem, Masker, md5, Msg, Name2valueFilter, numberComma, numberPad, numberRandom, numberRange, NumberRoller, Panel, Picker, Popover, Popup, PopupHeader, PopupPicker, PopupRadio, Previewer, Qrcode, querystring, Radio, Range, Rater, Scroller, Search, Selector, Shake, Spinner, Step, StepItem, Sticky, stringTrim, Swipeout, SwipeoutButton, SwipeoutItem, Swiper, SwiperItem, Tab, Tabbar, TabbarItem, TabItem, throttle, Timeline, TimelineItem, Tip, Toast, ToastPlugin, TransferDom, TransferDomDirective, trim, Value2nameFilter, VArea, VAxis, VBar, VChart, VGuide, Video, ViewBox, VLegend, VLine, VPie, VPoint, VScale, VTooltip, VuxComponentListData, WechatEmotion, WechatPlugin, WeekCalendar, WepayInput, XAddress, XButton, XCircle, XDialog, XHeader, XHr, XImg, XInput, XNumber, XProgress, XSwitch, XTable, XTextarea, XForm, XFormField } ================================================ FILE: package.json ================================================ { "name": "vux", "version": "2.11.1", "main": "index.js", "description": "Mobile web UI based on Vue and WeUI", "keywords": [ "vux", "vue", "vue ui", "weui", "vue-components", "web-components", "component", "components", "mobile ui", "framework", "frontend" ], "author": "airyland ", "repository": { "type": "git", "url": "https://github.com/airyland/vux" }, "license": "MIT", "scripts": { "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js", "build": "node build/build.js && rimraf docs/demos/v2/ && mkdirp docs/demos/v2 && cp -r docs/demos/temp/* docs/demos/v2/", "build:cdn": "node build/build.js --cdn && rimraf docs/demos/v2/ && mkdirp docs/demos/v2 && cp -rf docs/demos/temp/* docs/demos/v2/", "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", "test": "npm run unit", "lint": "eslint --ext .js,.vue src test/unit/specs", "doc:build": "node build/build-docs.js && node docs/component-contributors && node docs/compile", "doc:dev": "cd docs && npm run dev", "build-main": "node build/build-main.js", "build-components": "node build/build-components.js", "build-styles": "node build/build-styles.js", "xbuild": "npm run build-styles && npm run build-components" }, "dependencies": { "@antv/f2": "^3.1.4-beta.2", "array-filter": "^1.0.0", "array-find": "^1.0.0", "array-map": "^0.0.0", "array-shuffle": "^1.0.1", "async-validator": "^3.5.2", "autosize": "^3.0.20", "axios": "^0.15.3", "big.js": "^3.1.3", "blueimp-md5": "^2.6.0", "countup.js": "^1.8.1", "exif-js": "^2.3.0", "fastclick": "^1.0.6", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "object-assign": "^4.1.0", "qr.js": "0.0.0", "shake.js": "^1.2.2", "validator": "^9.3.0", "vanilla-masker": "^1.2.0", "vux-blazy": "^1.6.4", "vux-xscroll": "^3.1.10", "x-photoswipe": "^4.1.3-rc.1" }, "devDependencies": { "autoprefixer": "^6.7.2", "autosize": "^3.0.20", "babel-core": "^6.22.1", "babel-eslint": "^7.1.1", "babel-loader": "^7.1.1", "babel-plugin-istanbul": "^3.1.2", "babel-plugin-transform-runtime": "^6.22.0", "babel-preset-es2015": "^6.22.0", "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.22.0", "babel-runtime": "^6.20.0", "chai": "^3.5.0", "chalk": "^1.1.3", "co": "^4.6.0", "codecov": "^2.3.0", "connect-history-api-fallback": "^1.3.0", "cross-env": "^3.1.4", "css-loader": "^0.26.1", "dayjs": "^1.8.16", "docute-cli": "^0.4.0", "eslint": "^3.14.1", "eslint-config-standard": "^6.2.1", "eslint-friendly-formatter": "^2.0.7", "eslint-loader": "^1.6.1", "eslint-plugin-html": "^2.0.0", "eslint-plugin-promise": "^3.4.0", "eslint-plugin-standard": "^2.0.1", "eventsource-polyfill": "^0.9.6", "express": "^4.14.1", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^0.10.0", "friendly-errors-webpack-plugin": "^1.1.3", "function-bind": "^1.1.0", "gh-pages": "^0.12.0", "glob": "^7.1.1", "html-webpack-plugin": "^2.30.1", "http-proxy-middleware": "^0.17.3", "inject-loader": "^3.0.1", "js-yaml": "^3.7.0", "js-yaml-loader": "^0.1.0", "json-loader": "^0.5.4", "karma": "^1.4.1", "karma-coverage": "^1.1.1", "karma-mocha": "^1.3.0", "karma-phantomjs-launcher": "^1.0.4", "karma-phantomjs-shim": "^1.5.0", "karma-sinon-chai": "^1.3.1", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "0.0.31", "karma-webpack": "^2.0.2", "less": "^2.7.2", "less-loader": "^2.2.3", "lodash": "^4.17.4", "lolex": "^1.5.2", "mkdirp": "^0.5.1", "mocha": "^3.2.0", "node-fetch": "^1.6.3", "node-notifier": "^5.1.2", "node-watch": "^0.4.1", "opn": "^4.0.2", "ora": "^1.1.0", "phantomjs-prebuilt": "^2.1.14", "postcss": "^5.2.10", "postcss-less": "^0.15.0", "progress-bar-webpack-plugin": "^1.10.0", "rimraf": "^2.6.1", "semver": "^5.3.0", "shelljs": "^0.7.6", "sinon": "^2.3.8", "sinon-chai": "^2.12.0", "sort-object": "^3.0.2", "thunkify": "^2.1.2", "touch": "^1.0.0", "url-loader": "^0.5.7", "v-markdown-loader": "^0.6.3-beta.2", "vue": "2.5.16", "vue-hot-reload-api": "^2.0.11", "vue-loader": "^14.1.1", "vue-router": "^2.5.3", "vue-style-loader": "^2.0.0", "vue-template-compiler": "2.5.16", "vue-test-utils": "1.0.0-beta", "vuex": "^2.3.1", "vuex-i18n": "^1.3.1", "vuex-router-sync": "^4.2.0", "vux-loader": "^1.2.9", "webpack": "^3.8.1", "webpack-bundle-analyzer": "^2.2.1", "webpack-dev-middleware": "^1.12.0", "webpack-dev-server": "^2.9.4", "webpack-hot-middleware": "^2.20.0", "webpack-merge": "^2.6.1", "yargs": "^6.6.0" }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" } } ================================================ FILE: packages/loader/README.md ================================================ # vux-loader A webpack loader for processing .vue file before vue-loader ================================================ FILE: packages/loader/libs/get-i18n-block.js ================================================ const reg = /]*>([\s\S]*?)<\/i18n>/ const fs = require('fs') const yamlReader = require('js-yaml') // @todo add global cache // @todo try parse function get (code, isFile = false) { let content = code if (isFile) { content = fs.readFileSync(code, 'utf-8') } const results = content.match(/]*>([\s\S]*?)<\/i18n>/) try { const local = yamlReader.safeLoad(results[1]) return local } catch (e) { return {} } } function getWithLocale ({ code = '', isFile = false, locale = ''}) { const rs = get(code, isFile) let _rs = {} for (let i in rs) { _rs[i] = typeof rs[i][locale] === 'undefined' ? i : rs[i][locale] } return _rs } exports.get = get exports.getWithLocale = getWithLocale ================================================ FILE: packages/loader/libs/parse-i18n-function.js ================================================ const parse5 = require('parse5') const acorn = require('acorn') const escodegen = require('escodegen') function generateHTML(rs) { try { let processed = escodegen.generate(rs, { format: { semicolons: false, quotes: 'single', compact: false, indent: { style: '', base: 0, adjustMultilineComment: false } } }) processed = processed.replace(/;$/, '') .replace(/;\s/g, ' ') .replace('let VALUE = ', '') .replace(/;,/g, ',') .replace(/;]/g, ']') return processed } catch (e) { } } function parseExpression(list, key, map) { list.forEach((one, index) => { let value = one[key] || one if (value.type === 'BinaryExpression') { for (let i in value) { if (i === 'left' || i === 'right') { let expression = value[i] if (expression.type === 'CallExpression' && expression.callee && expression.callee.name === '$t' && expression.arguments.length === 1) { value[i] = buildStringExpression(map[expression.arguments[0].value] || expression.arguments[0].value) } } } } else if (value.type === 'CallExpression') { const rs = buildStringExpression(map[value.arguments[0].value] || value.arguments[0].value) if (key) { list[index][key] = rs } else { list[index] = rs } } }) } function doParseExpression(code, map) { if (typeof code === 'string' && !/\$t/.test(code)) { return } let rs = code if (typeof code === 'string') { rs = acorn.parse(code, { sourceType: 'module' }) } for (let i = 0; i < rs.body.length; i++) { const one = rs.body[i] if (one.type === 'ExpressionStatement' && one.expression.type === 'ArrayExpression') { const elements = one.expression.elements parseExpression(elements, '', map) } else if (one.type === 'ExpressionStatement' && one.expression && one.expression.callee && one.expression.callee.name === '$t' && one.expression.arguments.length === 1 && one.expression.arguments[0].type !== 'BinaryExpression') { one.expression = buildStringExpression(map[one.expression.arguments[0].value] || one.expression.arguments[0].value) } else if (one.type === 'ExpressionStatement') { // 表达式模式 parseCallExpression(one.expression, map) } else if (one.type === 'VariableDeclaration') { // 对象类型 var properties = one.declarations[0].init.properties parseExpression(properties, 'value', map) } else { } } return rs } function parseAttrs(nodes, map) { nodes.forEach(one => { if (one.nodeName === '#text' && /{{/.test(one.value) && /}}/.test(one.value)) { one.value = one.value.replace(/{{(.*?)}}/g, function (a, b) { if (b.indexOf('$t(') === -1) { return a } let rs = acorn.parse(b, { sourceType: 'module' }) if (rs.body.length === 1 && rs.body[0].expression && rs.body[0].type === 'ExpressionStatement' && rs.body[0].expression.type === 'CallExpression' && rs.body[0].expression.arguments[0].type === 'Literal') { rs = doParseExpression(rs, map) if (!rs) { return a } else { let html = generateHTML(rs) if (html) { html = html.replace(/^'(.*?)'$/, '$1') return html } } } else { return a } }) } if (one.attrs) { for (let i in one.attrs) { let attr = one.attrs[i] if (/\$t/.test(attr.value)) { if (/^:/.test(attr.name) && /^{/.test(attr.value)) { attr.value = 'let VALUE = ' + attr.value } let rs = doParseExpression(attr.value, map) let processed = generateHTML(rs) if (processed) { attr.value = processed } else { // stay unchanged } } } } if (one.childNodes && one.childNodes.length) { parseAttrs(one.childNodes, map) } }) } function parse(code, map) { const documentFragment = parse5.parseFragment(code) parseAttrs(documentFragment.childNodes, map) const html = parse5.serialize(documentFragment) return html } exports.parse = parse function parseCallExpression (expression, map) { for (let i in expression) { if (i === 'left' || i === 'right') { let currentExpression = expression[i] if (currentExpression.type === 'CallExpression' && currentExpression.callee && currentExpression.callee.name === '$t' && currentExpression.arguments.length === 1) { expression[i] = buildStringExpression(map[currentExpression.arguments[0].value] || currentExpression.arguments[0].value) } else { if (currentExpression.type === 'BinaryExpression') { parseCallExpression(currentExpression, map) } } } } } function buildStringExpression(value) { return { "type": "ExpressionStatement", "expression": { "type": "Literal", "value": value, "raw": "'" + value + "'" } } } ================================================ FILE: packages/loader/libs/replace-i18n-for-script.js ================================================ const matchI18nReg = /this.\$t\('?(.*?)'?\)/g function replace (code, map = {}, options = {}) { if (!code) { return } code = code.replace(matchI18nReg, function (a, b) { return addQuotes(map[b] || b) }) return code } exports.replace = replace function addQuotes (str) { return `'${str}'` } ================================================ FILE: packages/loader/package.json ================================================ { "name": "@vux/loader", "version": "2.1.3", "description": "extended loader for vux", "main": "src/index.js", "keywords": [ "vux", "vue", "vue-loader", "vux-loader", "webpack-loader", "weui", "vue-components", "web-components", "component", "components", "mobile ui", "framework", "frontend" ], "author": "airyland ", "repository": { "type": "git", "url": "https://github.com/airyland/vux" }, "license": "MIT", "scripts": { "test": "mocha test/test.js --slow 5000 --timeout 15000" }, "dependencies": { "@babel/core": "^7.4.3", "acorn": "^5.1.2", "babel-plugin-transform-imports": "^1.5.1", "compare-versions": "^3.1.0", "cssnano": "^3.7.4", "draftlog": "^1.0.10", "escodegen": "^1.9.0", "inline-manifest-webpack-plugin": "^3.0.1", "js-yaml": "^3.6.1", "less": "^2.7.1", "loader-utils": "^0.2.15", "node-cli-config": "^0.0.1", "parse5": "^3.0.2", "progress-bar-webpack-plugin": "^1.9.3", "strip-comments": "^0.4.4", "strip-css-comments": "^3.0.0", "touch": "^1.0.0", "underscore": "^1.8.3", "uuid": "^3.0.1", "webpack-merge": "^1.1.2", "webpack-sources": "^0.1.2" }, "devDependencies": { "chai": "^3.5.0", "coffee-loader": "^0.7.2", "coffee-script": "^1.12.2", "css-loader": "^0.26.1", "expose-loader": "^0.7.1", "extract-text-webpack-plugin": "^2.0.0", "file-loader": "^0.9.0", "hash-sum": "^1.0.2", "jsdom": "^9.9.1", "less-loader": "^2.2.3", "lodash": "^4.17.4", "memory-fs": "^0.4.1", "mkdirp": "^0.5.1", "normalize-newline": "^3.0.0", "pug": "^2.0.0-beta6", "rimraf": "^2.5.4", "source-map": "^0.5.6", "stylus": "^0.54.5", "stylus-loader": "^2.4.0", "sugarss": "^0.2.0", "vue": "^2.1.7", "vue-html-loader": "^1.2.3", "vue-loader": "^9.9.5", "vue-style-loader": "^1.0.0", "webpack": "^3.1.0" } } ================================================ FILE: packages/loader/parse.js ================================================ var babylon = require('babylon') const acorn = require('acorn') const escodegen = require('escodegen') // const plugin = require('babel-plugin-import') // console.log(babylon) const code = `import{ Group as Groups, Cell } from 'vux'; import { X } from 'x' import a from './a' import x from 'vux/src/components/cell' var b = '1'` const rs = babylon.parse(code, { sourceType: 'module' }) // console.log(rs) const format = function (obj) { return JSON.stringify(obj, null, 2) } const tree = acorn.parse(code, { sourceType: 'module' }) console.log(format(tree.body)) const append = acorn.parse(`import{ a, xb, c,de} from 'xxx'`, { sourceType: 'module' }) // 重新生成 tree.body.unshift(append) const code2 = escodegen.generate(tree, { format: { semicolons: false, indent: { style: ' ', base: 0, adjustMultilineComment: false } } }) console.log(code2) ================================================ FILE: packages/loader/plugins/duplicate-style/index.js ================================================ /** * https://github.com/NMFR/optimize-css-assets-webpack-plugin * MIT LICENSE */ var _ = require('underscore') var webpackSources = require('webpack-sources') function OptimizeCssAssetsPlugin(options) { options = options || {} this.options = Object.assign({ canPrint: undefined, cssProcessor: require('cssnano'), assetNameRegExp : /\.css$/g, cssProcessorOptions : { safe: true, zindex: false, autoprefixer: false } }, options) }; OptimizeCssAssetsPlugin.prototype.print = function() { if (this.options.canPrint || (typeof this.options.canPrint === 'undefined' && (process.env.NODE_ENV === 'production' || process.env.__VUX_BUILD__))) { console.log.apply(console, arguments); } }; OptimizeCssAssetsPlugin.prototype.processCss = function(css) { return this.options.cssProcessor.process(css, this.options.cssProcessorOptions) }; OptimizeCssAssetsPlugin.prototype.createCssAsset = function(css, originalAsset) { return new webpackSources.RawSource(css); }; OptimizeCssAssetsPlugin.prototype.apply = function(compiler) { var self = this; if (compiler.hooks) { const plugin = { name: "WebpackDuplicateStyle" }; compiler.hooks.emit.tap(plugin, (compilation) => { self.print("\n\n======== vux-loader: duplicate-style start~ ========"); self.print("Starting to optimize CSS..."); var assets = compilation.assets; var cssAssetNames = _.filter(_.keys(assets), function (assetName) { return assetName.match(self.options.assetNameRegExp); }); var hasErrors = false; var promises = []; _.each(cssAssetNames, function (assetName) { self.print("Processing " + assetName + "..."); var asset = assets[assetName]; var originalCss = asset.source(); // avoid 0% => 0 originalCss = originalCss.replace(/:\s*0%/g, ":1234%"); var promise = self.processCss(originalCss); promise.then( function (result) { if (hasErrors) { self.print("Skiping " + assetName + " because of an error."); return; } var processedCss = result.css; // replace back to 0% processedCss = processedCss.replace(/:1234%/g, ":0%"); assets[assetName] = self.createCssAsset(processedCss, asset); var ratio = ""; if (originalCss.length) { ratio = ", ratio:" + Math.round( ((processedCss.length * 100) / originalCss.length) * 100 ) / 100 + "%"; } self.print( "Processed " + assetName + ", before: " + originalCss.length + ", after: " + processedCss.length + ratio ); }, function (err) { hasErrors = true; self.print("Error processing file: " + assetName); console.error(err); } ); promises.push(promise); }); Promise.all(promises).then(function () { self.print("======== vux-loader: duplicate-style done! ========\n"); }); }); } else { compiler.plugin("emit", function (compilation, compileCallback) { self.print("\n\n======== vux-loader: duplicate-style start~ ========"); self.print("Starting to optimize CSS..."); var assets = compilation.assets; var cssAssetNames = _.filter(_.keys(assets), function (assetName) { return assetName.match(self.options.assetNameRegExp); }); var hasErrors = false; var promises = []; _.each( cssAssetNames, function(assetName) { self.print('Processing ' + assetName + '...') var asset = assets[assetName]; var originalCss = asset.source(); // avoid 0% => 0 originalCss = originalCss.replace(/:\s*0%/g, ':1234%') var promise = self.processCss(originalCss) promise.then( function (result) { if (hasErrors) { self.print('Skiping ' + assetName + ' because of an error.') return; } var processedCss = result.css; // replace back to 0% processedCss = processedCss.replace(/:1234%/g, ':0%') assets[assetName] = self.createCssAsset(processedCss, asset); var ratio = '' if (originalCss.length) { ratio = ', ratio:' + (Math.round(((processedCss.length * 100) / originalCss.length) * 100) / 100) + '%' } self.print('Processed ' + assetName + ', before: ' + originalCss.length + ', after: ' + processedCss.length + ratio) }, function(err) { hasErrors = true; self.print('Error processing file: ' + assetName) console.error(err) } ); promises.push(promise) } ); Promise.all(promises).then(function () { compileCallback() self.print('======== vux-loader: duplicate-style done! ========\n') }, compileCallback) }) } } module.exports = OptimizeCssAssetsPlugin ================================================ FILE: packages/loader/plugins/html-build-callback/index.js ================================================ 'use strict' /** * https://www.npmjs.com/package/html-webpack-plugin * Async html-webpack-plugin-before-html-generation html-webpack-plugin-before-html-processing html-webpack-plugin-alter-asset-tags html-webpack-plugin-after-html-processing html-webpack-plugin-after-emit Sync: html-webpack-plugin-alter-chunks */ /** * { events: { 'before-html-generation': function (data, cb) { cb(null, data) } } } */ function Plugin(options) { this.events = options.events } Plugin.prototype.apply = function (compiler) { let _this = this; const plugin = { name: "WebpackHtmlBuildCallback" }; if (compiler.hooks) { compiler.hooks.compilation.tap(plugin, (compilation) => { for (let i in _this.events) { compilation.plugin("html-webpack-plugin-" + i, _this.events[i]); } }); } else { compiler.plugin("compilation", function (compilation) { for (let i in _this.events) { compilation.plugin("html-webpack-plugin-" + i, _this.events[i]); } }); } }; module.exports = Plugin; ================================================ FILE: packages/loader/src/after-less-loader.js ================================================ 'use strict' const utils = require('loader-utils') module.exports = function (source) { this.cacheable() const _this = this const config = this.vux || utils.getLoaderConfig(this, 'vux') if (!config.plugins || !config.plugins.length) { return source } config.plugins.forEach(function (plugin) { // style-parser if (plugin.name === 'after-less-parser') { if (plugin.fn) { source = plugin.fn.call(_this, source) } } }) return source } ================================================ FILE: packages/loader/src/before-template-compiler-loader.js ================================================ 'use strict' const utils = require('loader-utils') const parseXIcon = require('./libs/parse-x-icon') module.exports = function (source) { this.cacheable() const _this = this const config = this.vux || utils.getLoaderConfig(this, 'vux') if (!config.plugins || !config.plugins.length) { return source } if (config.options.useVuxUI && source.indexOf('') > -1) { source = parseXIcon(source, config) } config.plugins.forEach(function (plugin) { // style-parser if (plugin.name === 'before-template-compiler-parser') { if (plugin.fn) { source = plugin.fn.call(_this, source) } } }) return source } ================================================ FILE: packages/loader/src/i18n-loader.js ================================================ const yamlReader = require('js-yaml') module.exports = function (source, map) { let i18n = {} try { i18n = yamlReader.safeLoad(source) } catch (e) { console.log(e) } this.callback( null, `export default function (Component) { Component.options.__i18n = ${ JSON.stringify(i18n) } }`, map ) } ================================================ FILE: packages/loader/src/index.js ================================================ 'use strict' const path = require('path') const fs = require('fs') const merge = require('webpack-merge') const yaml = require('js-yaml') const _ = require('lodash') const pkg = require('../package') var webpack = require('webpack') const scriptLoader = path.join(__dirname, './script-loader.js') const noopLoader = path.join(__dirname, './noop-loader.js') const styleLoader = path.join(__dirname, './style-loader.js') const templateLoader = path.join(__dirname, './template-loader.js') const jsLoader = path.join(__dirname, './js-loader.js') const afterLessLoader = path.join(__dirname, './after-less-loader.js') const beforeTemplateCompilerLoader = path.join(__dirname, './before-template-compiler-loader.js') const projectRoot = process.cwd() const getLessVariables = require('./libs/get-less-variables') /** * Plugins */ const htmlBuildCallbackPlugin = require('../plugins/html-build-callback') const DuplicateStyle = require('../plugins/duplicate-style') /** build done callback **/ function DonePlugin(callbacks) { this.callbacks = callbacks || function () {} // Setup the plugin instance with options... } DonePlugin.prototype.apply = function (compiler) { let callbacks = this.callbacks if (compiler.hooks) { const plugin = {name: 'WebpackDonePlugin'}; compiler.hooks.done.tap(plugin, function () { callbacks.forEach(function (fn) { fn() }) }); } else { compiler.plugin('done', function () { callbacks.forEach(function (fn) { fn() }) }); } }; /** emit plugin **/ function EmitPlugin(callback) { this.callback = callback } EmitPlugin.prototype.apply = function (compiler) { let callback = this.callback if (compiler.hooks) { const plugin = {name: 'WebpackEmitPlugin'}; compiler.hooks.emit.tapAsync(plugin, (compilation, cb) => { callback(compilation, cb) }) } else { compiler.plugin("emit", function (compilation, cb) { callback(compilation, cb) }); } } function hasPlugin(name, list) { const match = list.filter(function (one) { return one.name === name }) return match.length > 0 } function getFirstPlugin(name, list) { const match = list.filter(function (one) { return one.name === name }) return match[0] } module.exports.merge = function (oldConfig, vuxConfig) { oldConfig.module.rules.push({ resourceQuery: /^\?vue&type=template/, enforce: 'pre', loader: path.resolve(__dirname, './template-loader') }) oldConfig.module.rules.push({ test: /\.js/, loader: path.resolve(__dirname, './js-loader') }) // ditto oldConfig.module.rules.push({ resourceQuery: /\.js/, loader: path.resolve(__dirname, './js-loader') }) oldConfig.module.rules.push({ resourceQuery: /^\?vue&type=script/, enforce: 'pre', loader: path.resolve(__dirname, './script-loader') }) oldConfig.module.rules.push({ resourceQuery: /blockType=i18n/, loader: path.resolve(__dirname, './i18n-loader') }) let variables = {} oldConfig = Object.assign({ plugins: [] }, oldConfig) let config = Object.assign({ module: {}, plugins: [] }, oldConfig) if (!vuxConfig) { vuxConfig = { options: {}, plugins: [] } } if (!vuxConfig.options) { vuxConfig.options = { buildEnvs: ['production'] } } if (typeof vuxConfig.options.ssr === 'undefined') { vuxConfig.options.ssr = false } const buildEnvs = vuxConfig.options.buildEnvs || ['production'] if (buildEnvs.indexOf(process.env.NODE_ENV) !== -1) { process.env.__VUX_BUILD__ = true } else { process.env.__VUX_BUILD__ = false } if (!vuxConfig.plugins) { vuxConfig.plugins = [] } if (vuxConfig.plugins.length) { vuxConfig.plugins = vuxConfig.plugins.map(function (plugin) { if (typeof plugin === 'string') { return { name: plugin } } return plugin }) } vuxConfig.allPlugins = vuxConfig.allPlugins || [] // check multi plugin instance const pluginGroup = _.groupBy(vuxConfig.plugins, function (plugin) { return plugin.name }) for (let group in pluginGroup) { if (pluginGroup[group].length > 1) { throw (`only one instance is allowed. plugin name: ${group}`) } } // if exists old vux config, merge options and plugins list let oldVuxConfig = oldConfig.vux || null oldConfig.plugins.forEach(function (plugin) { if (plugin.constructor.name === 'LoaderOptionsPlugin' && plugin.options.vux) { oldVuxConfig = plugin.options.vux } }) if (oldVuxConfig) { // merge old options vuxConfig.options = Object.assign(oldVuxConfig.options, vuxConfig.options) // merge old plugins list vuxConfig.plugins.forEach(function (newPlugin) { let isSame = false oldVuxConfig.allPlugins.forEach(function (oldPlugin, index) { if (newPlugin.name === oldPlugin.name) { oldVuxConfig.allPlugins.splice(index, 1) oldVuxConfig.allPlugins.push(newPlugin) isSame = true } }) if (!isSame) { oldVuxConfig.allPlugins.push(newPlugin) } }) vuxConfig.allPlugins = oldVuxConfig.allPlugins } else { vuxConfig.allPlugins = vuxConfig.plugins } // filter plugins by env if (vuxConfig.options.env && vuxConfig.allPlugins.length) { vuxConfig.plugins = vuxConfig.allPlugins.filter(function (plugin) { return typeof plugin.envs === 'undefined' || (typeof plugin.envs === 'object' && plugin.envs.length && plugin.envs.indexOf(vuxConfig.options.env) > -1) }) } if (!vuxConfig.options.projectRoot) { vuxConfig.options.projectRoot = projectRoot } var themes = vuxConfig.plugins.filter(function (plugin) { return plugin.name === 'less-theme' }) if (themes.length) { const themePath = path.join(vuxConfig.options.projectRoot, themes[0].path) variables = getLessVariables(themes[0].path) } let vuxVersion try { let vuePackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/package.json') vuxVersion = require(vuePackagePath).version } catch (e) {} // get vue version let vueVersion try { let vuePackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vue/package.json') vueVersion = require(vuePackagePath).version } catch (e) {} vuxConfig.options.vueVersion = vueVersion let vueLoaderVersion try { let vueLoaderPackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vue-loader/package.json') vueLoaderVersion = require(vueLoaderPackagePath).version } catch (e) {} vuxConfig.options.vueLoaderVersion = vueLoaderVersion require('./libs/report')({ vueVersion: vueVersion, vuxVersion: vuxVersion, vueLoaderVersion: vueLoaderVersion }) // check webpack version by module.loaders let isWebpack2 if (typeof vuxConfig.options.isWebpack2 !== 'undefined') { isWebpack2 = vuxConfig.options.isWebpack2 } else if (oldConfig.module && oldConfig.module.rules) { isWebpack2 = true } else if (oldConfig.module && oldConfig.module.loaders) { isWebpack2 = false } if (typeof isWebpack2 === 'undefined') { const compareVersions = require('compare-versions') const pkg = require(path.resolve(vuxConfig.options.projectRoot, 'package.json')) if (pkg.devDependencies.webpack) { isWebpack2 = compareVersions(pkg.devDependencies.webpack.replace('^', '').replace('~', ''), '2.0.0') > -1 } else { isWebpack2 = true } } if (!isWebpack2) { if (!config.vue) { config.vue = { loaders: { i18n: 'vux-loader/src/noop-loader.js' } } } else { if (!config.vue.loaders) { config.vue.loaders = {} } config.vue.loaders.i18n = 'vux-loader/src/noop-loader.js' } } let loaderKey = 'rules' // isWebpack2 ? 'rules' : 'loaders' config.module[loaderKey] = config.module[loaderKey] || [] const useVuxUI = hasPlugin('vux-ui', vuxConfig.plugins) vuxConfig.options.useVuxUI = true /** * ======== set vux options ======== */ // for webpack@2.x, options should be provided with LoaderOptionsPlugin if (isWebpack2) { if (!config.plugins) { config.plugins = [] } // delete old config for webpack2 config.plugins.forEach(function (plugin, index) { if (plugin.constructor.name === 'LoaderOptionsPlugin' && plugin.options.vux) { config.plugins.splice(index, 1) } }) config.plugins.push(new webpack.LoaderOptionsPlugin({ vux: vuxConfig })) } else { // for webpack@1.x, merge directly config = merge(config, { vux: vuxConfig }) } // if (hasPlugin('inline-manifest', vuxConfig.plugins)) { // var InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin') // config.plugins.push(new InlineManifestWebpackPlugin({ // name: 'webpackManifest' // })) // } if (hasPlugin('progress-bar', vuxConfig.plugins)) { const ProgressBarPlugin = require('progress-bar-webpack-plugin') const pluginConfig = getFirstPlugin('progress-bar', vuxConfig.plugins) config.plugins.push(new ProgressBarPlugin(pluginConfig.options || {})) } if (hasPlugin('vux-ui', vuxConfig.plugins)) { let mapPath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/components/map.json') if (vuxConfig.options.vuxDev) { mapPath = path.resolve(vuxConfig.options.projectRoot, 'src/components/map.json') } const maps = require(mapPath) if (isWebpack2) { config.plugins.push(new webpack.LoaderOptionsPlugin({ vuxMaps: maps })) } else { config = merge(config, { vuxMaps: maps }) } } // get less variable alias if (hasPlugin('vux-ui', vuxConfig.plugins)) { let variablePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/styles/variable.less') if (vuxConfig.options.vuxDev) { variablePath = path.resolve(vuxConfig.options.projectRoot, 'src/styles/variable.less') } // parse alias const rs = {} try { const content = fs.readFileSync(variablePath, 'utf-8').split('\n').filter(line => /\/\/\salias/.test(line)).map(line => { const value = line.split('// alias ')[1].replace(/\s+/g, '').trim() const key = line.split('// alias ')[0].replace(/\s+/g, '').trim().split(':')[0].replace(/^@/, '') return [key, value] }).forEach(one => { rs[one[0]] = one[1] }) } catch (e) {} if (isWebpack2) { config.plugins.push(new webpack.LoaderOptionsPlugin({ vuxVariableMap: rs })) } else { config = merge(config, { vuxVariableMap: rs }) } } /** * ======== read vux locales and set globally ======== */ if (hasPlugin('vux-ui', vuxConfig.plugins)) { let vuxLocalesPath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/locales/all.yml') if (vuxConfig.options.vuxDev) { vuxLocalesPath = path.resolve(vuxConfig.options.projectRoot, 'src/locales/all.yml') } try { const vuxLocalesContent = fs.readFileSync(vuxLocalesPath, 'utf-8') let vuxLocalesJson = yaml.safeLoad(vuxLocalesContent) if (isWebpack2) { config.plugins.push(new webpack.LoaderOptionsPlugin({ vuxLocales: vuxLocalesJson })) } else { config = merge(config, { vuxLocales: vuxLocalesJson }) } } catch (e) {} } /** * ======== append vux-loader ======== */ let loaderString = vuxConfig.options.loaderString || 'vux-loader!vue-loader' const rewriteConfig = vuxConfig.options.rewriteLoaderString if (typeof rewriteConfig === 'undefined' || rewriteConfig === true) { let hasAppendVuxLoader = false config.module.rules.forEach(function (rule) { const hasVueLoader = typeof rule.use === 'object' && rule.use.filter(one => { return one.loader === 'vue-loader' }).length > 0 }) } /** * ======== append js-loader for ts-loader ======== */ config.module[loaderKey].forEach(function (rule) { if (rule.use && (rule.use[0] === 'ts-loader' || (typeof rule.use[0] === 'object' && rule.use[0].loader === 'ts-loader'))) { rule.use.push(jsLoader) } else { if (rule.loader === 'ts' || rule.loader === 'ts-loader' || (/\bts\b/.test(rule.loader) && !/!/.test(rule.loader))) { if (isWebpack2 && (rule.query || rule.options)) { let options if(rule.options){ options = rule.options delete rule.options }else{ options = rule.query delete rule.query } rule.use = [{ loader: 'ts-loader', options: options }, jsLoader] delete rule.loader } else { rule.loader = 'ts-loader!' + jsLoader } } } }) /** * ======== append js-loader ======== */ config.module[loaderKey].forEach(function (rule) { if (Array.isArray(rule.use)) { if (rule.use.filter(one => one.loader === 'babel-loader').length) { rule.use.push({ loader: jsLoader }) } } else { if (rule.loader === 'babel' || rule.loader === 'babel-loader' || (/babel/.test(rule.loader) && !/!/.test(rule.loader))) { if (isWebpack2 && (rule.query || rule.options)) { let options if(rule.options){ options = rule.options delete rule.options }else{ options = rule.query delete rule.query } rule.use = [{ loader: 'babel-loader', options: options }, jsLoader] delete rule.loader } else { rule.loader = 'babel-loader!' + jsLoader } } } }) config.module.rules.forEach(function (rule) { if (Array.isArray(rule.oneOf)) { rule.oneOf.forEach(function (one) { one.use.forEach(function (line) { if (line.loader === 'less-loader') { line.options = Object.assign(line.options || {}, { modifyVars: variables }) } }) }) } }) /** * ======== set compiling vux js source ======== */ if (hasPlugin('vux-ui', vuxConfig.plugins)) { // if (typeof vuxConfig.options.vuxSetBabel === 'undefined' || vuxConfig.options.vuxSetBabel === true) { // config.module[loaderKey].push(getBabelLoader(vuxConfig.options.projectRoot, 'vux', vuxConfig.options.vuxDev)) // } } // set done plugin if (hasPlugin('build-done-callback', vuxConfig.plugins)) { const callbacks = vuxConfig.plugins.filter(function (one) { return one.name === 'build-done-callback' }).map(function (one) { return one.fn }) config.plugins.push(new DonePlugin(callbacks)) } config.plugins.push(new DonePlugin([function () { if (global.reportInterval) { clearInterval(global.reportInterval) global.reportInterval = null } }])) // duplicate styles if (hasPlugin('duplicate-style', vuxConfig.plugins)) { let plugin = getFirstPlugin('duplicate-style', vuxConfig.plugins) let options = plugin.options || {} config.plugins.push(new DuplicateStyle(options)) } if (hasPlugin('build-emit-callback', vuxConfig.plugins)) { config.plugins = config.plugins || [] const callbacks = vuxConfig.plugins.filter(function (one) { return one.name === 'build-emit-callback' }).map(function (one) { return one.fn }) if (callbacks.length) { config.plugins.push(new EmitPlugin(callbacks[0])) } } if (hasPlugin('html-build-callback', vuxConfig.plugins)) { let pluginConfig = getFirstPlugin('html-build-callback', vuxConfig.plugins) config.plugins.push(new htmlBuildCallbackPlugin(pluginConfig)) } /** *======== global variable V_LOCALE ======== */ let locale = '' if (hasPlugin('i18n', vuxConfig.plugins)) { const config = getFirstPlugin('i18n', vuxConfig.plugins) if (config.vuxStaticReplace && config.vuxLocale) { locale = config.vuxLocale } else if (config.vuxStaticReplace === false) { locale = 'MULTI' } } else { locale = 'zh-CN' } /** *======== global variable V_SSR ======== */ let ssr = false if (vuxConfig.options.ssr) { ssr = true } // check if already defined V_LOCALE let matchLocale = config.plugins.filter(one => { if (one.constructor.name === 'DefinePlugin') { if (one.definitions && one.definitions.V_LOCALE) { return true } } return false }) if (!matchLocale.length) { config.plugins.push(new webpack.DefinePlugin({ V_LOCALE: JSON.stringify(locale), V_SSR: JSON.stringify(ssr), SUPPORT_SSR_TAG: JSON.stringify(true) })) } // save to file so it can be read when using thread-loader const cacheFile = require.resolve('vux').replace('index.js', '.config.cache.json') fs.writeFileSync(cacheFile, JSON.stringify(vuxConfig, null, 2)) oldConfig = merge(oldConfig, config) return config } /** * use babel so component's js can be compiled */ function getBabelLoader(projectRoot, name, isDev) { name = name || 'vux' if (!projectRoot) { projectRoot = path.resolve(__dirname, '../../../') if (/\.npm/.test(projectRoot)) { projectRoot = path.resolve(projectRoot, '../../../') } } let componentPath let regex if (!isDev) { componentPath = fs.realpathSync(projectRoot + `/node_modules/${name}/`) // https://github.com/webpack/webpack/issues/1643 regex = new RegExp(`node_modules.*${name}.src.*?js$`) } else { componentPath = projectRoot regex = new RegExp(`${projectRoot}.src.*?js$`) } return { test: regex, loader: 'babel-loader', include: componentPath } } function setWebpackConfig(oriConfig, appendConfig, isWebpack2) { if (isWebpack2) { oriConfig.plugins.push(new webpack.LoaderOptionsPlugin(appendConfig)) } else { oriConfig = merge(oriConfig, appendConfig) } return oriConfig } function getOnePlugin(name, plugins) { const matches = plugins.filter(function (one) { return one.name === name }) return matches.length ? matches[0] : null } ================================================ FILE: packages/loader/src/js-loader.js ================================================ 'use strict' const utils = require('loader-utils') const path = require('path') const pkg = require('../package') const parser = require('./libs/import-parser-v2') const fs = require('fs') module.exports = function (source) { if (!/(\.jsx?|\.tsx?|\.vue)$/.test(this.resourcePath) || /node_modules/.test(this.resourcePath)) { return source } this.cacheable() const _this = this const vuxConfig = this.vux || utils.getLoaderConfig(this, 'vux') // 打包时无法获取选项,默认为使用 vux ui if (/}\s+from(.*?)('|")vux/.test(source)) { const maps = this.vuxMaps || utils.getLoaderConfig(this, 'vuxMaps') // source = parser(source, function (opts) { // let str = '' // opts.components.forEach(function (component) { // let file = `vux/${maps[component.originalName]}` // if (vuxConfig.options.vuxDev) { // if (vuxConfig.options.resolveVuxDir) { // file = file.replace('vux/src/', vuxConfig.options.resolveVuxDir) // } else { // file = file.replace('vux/src/', './') // } // } // str += `import ${component.newName} from '${file}'\n` // }) // return str // }, 'vux') } source = parser(source, this.resourcePath) if(/** vuxConfig.options.vuxDev && **/ /main\.js/.test(this.resourcePath)) { source = source.replace(/!vux\/src/g, '!.') } if (vuxConfig && vuxConfig.plugins && vuxConfig.plugins.length) { vuxConfig.plugins.forEach(function (plugin) { // js-parser if (plugin.name === 'js-parser') { if (plugin.fn) { if (plugin.test && plugin.test.test(_this.resourcePath)) { source = plugin.fn.call(_this, source) } else if (!plugin.test) { source = plugin.fn.call(_this, source) } } } }) } /** if (/main\.js/.test(this.resourcePath) && process.env.NODE_ENV === 'development') { if (this.options && this.options.context) { const pkgPath = vuxConfig.options.vuxDev ? path.join(this.options.context, 'package.json') : path.join(this.options.context, 'node_modules/vux/package.json') const vuxPkg = require(pkgPath) const webpackPath = path.join(this.options.context, 'node_modules/webpack/package.json') const webpackPkg = require(webpackPath) const nodeVersion = process.version.match(/^v(\d+\.\d+)/)[1] const style = 'background: #35495e; color: yellow;' if (typeof vuxConfig.options.showVuxVersionInfo === 'undefined' || vuxConfig.options.showVuxVersionInfo === true) { source += `\n;console.info('[VUX] %cvux@${vuxPkg.version}, vux-loader@${pkg.version}, webpack@${webpackPkg.version}, node@${nodeVersion}\\n%c[VUX] 建议反馈请访问 https://github.com/airyland/vux/issues \\n[VUX] 关闭该提示请在 vux-loader 配置 options: { showVuxVersionInfo: false }', '${style}', '')` } } } **/ return source } ================================================ FILE: packages/loader/src/libs/babel-transform-imports/.babelrc ================================================ { "presets": ["es2015"] } ================================================ FILE: packages/loader/src/libs/babel-transform-imports/.gitignore ================================================ node_modules npm-debug.log ================================================ FILE: packages/loader/src/libs/babel-transform-imports/README.md ================================================ # babel-plugin-transform-imports This a fork of https://bitbucket.org/amctheatres/babel-transform-imports.git Transforms member style imports: ```javascript import { Row, Grid as MyGrid } from 'react-bootstrap'; import { merge } from 'lodash'; ``` ...into default style imports: ```javascript import Row from 'react-bootstrap/lib/Row'; import MyGrid from 'react-bootstrap/lib/Grid'; import merge from 'lodash/merge'; ``` *Note: this plugin is not restricted to the react-bootstrap and lodash libraries. You may use it with any library.* ## Why? When Babel encounters a member style import such as: ```javascript import { Grid, Row, Col } from 'react-bootstrap'; ``` it will generate something similarish to: ```javascript var reactBootstrap = require('react-bootstrap'); var Grid = reactBootstrap.Grid; var Row = reactBootstrap.Row; var Col = reactBootstrap.Col; ``` Which causes the entire library to be loaded, even though only some components are needed. Some libraries are rather large and pulling in the entire module would cause unnecessary bloat to your client optimized (webpack etc.) bundle. The only way around this is to use default style imports: ```javascript import Grid from 'react-bootstrap/lib/Grid'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; ``` But, the more pieces we need, the more this sucks. This plugin will allow you to pull in just the pieces you need, without a separate import for each item. Additionally, it can be configured to throw when somebody accidentally writes an import which would cause the entire module to resolve, such as: ```javascript import Bootstrap, { Grid } from 'react-bootstrap'; // -- or -- import * as Bootstrap from 'react-bootstrap'; ``` ## Installation ``` npm install --save-dev babel-plugin-transform-imports ``` ## Usage *In .babelrc:* ```json { "plugins": [ ["transform-imports", { "react-bootstrap": { "transform": "react-bootstrap/lib/${member}", "preventFullImport": true }, "lodash": { "transform": "lodash/${member}", "preventFullImport": true } }] ] } ``` ## Advanced Transformations ### Using regular expressions Sometimes, you may wish to use regular expressions in your transformation (for example, to enforce the same convention in all folder levels on the structure of your library). .babelrc: ```json { "plugins": [ ["transform-imports", { "my-library\/?(((\\w*)?\/?)*)": { "transform": "my-library/${1}/${member}", "preventFullImport": true } }] ] } ``` Causes this code: ```javascript import { MyModule } from 'my-library'; import { App } from 'my-library/components'; import { Header, Footer } from 'my-library/components/App'; ``` to become: ```javascript import MyModule from 'my-library/MyModule'; import App from 'my-library/components/App'; import Header from 'my-library/components/App/Header'; import Footer from 'my-library/components/App/Footer'; ``` ### Using a function as the transformer If you need more advanced or more specific transformation logic, and are using Babel 7+ with a `.babelrc.js` file, you may provide a function instead of a string for the `transform` option: .babelrc.js: ```javascript module.exports = { presets: ['@babel/env'], plugins: [ ['transform-imports', { 'my-library': { transform: function(importName, matches) { return `my-library/etc/${importName.toUpperCase()}`; }, preventFullImport: true, } }] ] }; ``` ### Using a transformation file If you need the above flexibility of using a function as a transformer, but are still on Babel 6 or cannot use a `.babelrc.js` file for some reason, you may provide a path to a .js file which exports a function to run instead. Keep in mind that the .js file will be `require`d relative from this plugin's path, likely located in `/node_modules/babel-plugin-transform-imports`. You may provide any filename, as long as it ends with `.js`. .babelrc: ```json { "plugins": [ ["transform-imports", { "my-library": { "transform": "../../path/to/transform.js", "preventFullImport": true } }] ] } ``` /path/to/transform.js: ```javascript module.exports = function(importName, matches) { return 'my-library/etc/' + importName.toUpperCase(); }; ``` ## Webpack This can be used as a plugin with babel-loader. webpack.config.js: ```javascript module: { rules: [{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', query: { plugins: [ [require('babel-plugin-transform-imports'), { 'my-library': { transform: function(importName, matches) { return 'my-library/etc/' + importName.toUpperCase(); }, preventFullImport: true } }] ] } } }] } ``` ## Options | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `transform` | `string or function` | yes | `undefined` | The library name to use instead of the one specified in the import statement. ${member} will be replaced with the import name, aka Grid/Row/Col/etc., and ${1-n} will be replaced by any matched regular expression groups. Alternatively, pass a path to a .js file which exports a function to process the transform, which is invoked with parameters: (importName, matches). If using Babel 7+, a function may be passed directly. (see Advanced Transformations) | | `preventFullImport` | `boolean` | no | `false` | Whether or not to throw when an import is encountered which would cause the entire module to be imported. | | `camelCase` | `boolean` | no | `false` | When set to true, runs ${member} through _.camelCase. | | `kebabCase` | `boolean` | no | `false` | When set to true, runs ${member} through _.kebabCase. | | `snakeCase` | `boolean` | no | `false` | When set to true, runs ${member} through _.snakeCase. | | `skipDefaultConversion` | `boolean` | no | `false` | When set to true, will preserve `import { X }` syntax instead of converting to `import X`. | ================================================ FILE: packages/loader/src/libs/babel-transform-imports/index.js ================================================ var types = require('babel-types'); var isValidPath = require('is-valid-path'); var camel = require('lodash.camelcase'); var findKey = require('lodash.findkey'); var kebab = require('lodash.kebabcase'); var snake = require('lodash.snakecase'); var pathLib = require('path'); function findOptionFromSource(source, state) { var opts = state.opts; if (opts[source]) return source; return var opt = findKey(opts, function (o, _opt) { return !isValidPath(_opt) && new RegExp(_opt).test(source); }); if (opt) return opt; var isRelativePath = source.match(/^\.{0,2}\//); // This block handles relative paths, such as ./components, ../../components, etc. if (isRelativePath) { var _source = pathLib.resolve(pathLib.join( source[0] === '/' ? '' : pathLib.dirname(state.file.opts.filename), source )); if (opts[_source]) { return _source; } } } function getMatchesFromSource(opt, source) { var regex = new RegExp(opt, 'g'); var matches = []; var m; while ((m = regex.exec(source)) !== null) { if (m.index === regex.lastIndex) regex.lastIndex++; m.forEach(function(match) { matches.push(match); }); } return matches; } function barf(msg) { throw new Error('babel-plugin-transform-imports: ' + msg); } function transform(transformOption, importName, matches) { var isFunction = typeof transformOption === 'function'; if (/\.js$/i.test(transformOption) || isFunction) { var transformFn; try { transformFn = isFunction ? transformOption : require(transformOption); } catch (error) { barf('failed to require transform file ' + transformOption); } if (typeof transformFn !== 'function') { barf('expected transform function to be exported from ' + transformOption); } return transformFn(importName, matches); } return transformOption.replace(/\$\{\s?([\w\d]*)\s?\}/ig, function(str, g1) { if (g1 === 'member') return importName; return matches[g1]; }); } module.exports = function() { return { visitor: { ImportDeclaration: function (path, state) { // https://github.com/babel/babel/tree/master/packages/babel-types#timportdeclarationspecifiers-source // path.node has properties 'source' and 'specifiers' attached. // path.node.source is the library/module name, aka 'react-bootstrap'. // path.node.specifiers is an array of ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier var source = path.node.source.value; var opt = findOptionFromSource(source, state); var isRegexp = opt && !isValidPath(opt); var opts = state.opts[opt]; var hasOpts = !!opts; if (hasOpts) { if (!opts.transform) { barf('transform option is required for module ' + source); } var transforms = []; var fullImports = path.node.specifiers.filter(function(specifier) { return specifier.type !== 'ImportSpecifier' }); var memberImports = path.node.specifiers.filter(function(specifier) { return specifier.type === 'ImportSpecifier' }); if (fullImports.length > 0) { // Examples of "full" imports: // import * as name from 'module'; (ImportNamespaceSpecifier) // import name from 'module'; (ImportDefaultSpecifier) if (opts.preventFullImport) { barf('import of entire module ' + source + ' not allowed due to preventFullImport setting'); } if (memberImports.length > 0) { // Swap out the import with one that doesn't include member imports. Member imports should each get their own import line // transform this: // import Bootstrap, { Grid } from 'react-bootstrap'; // into this: // import Bootstrap from 'react-bootstrap'; transforms.push(types.importDeclaration(fullImports, types.stringLiteral(source))); } } var matches = isRegexp ? getMatchesFromSource(opt, source) : []; memberImports.forEach(function(memberImport) { // Examples of member imports: // import { member } from 'module'; (ImportSpecifier) // import { member as alias } from 'module' (ImportSpecifier) // transform this: // import { Grid as gird } from 'react-bootstrap'; // into this: // import gird from 'react-bootstrap/lib/Grid'; // or this, if skipDefaultConversion = true: // import { Grid as gird } from 'react-bootstrap/lib/Grid'; var importName = memberImport.imported.name; if (opts.camelCase) importName = camel(importName); if (opts.kebabCase) importName = kebab(importName); if (opts.snakeCase) importName = snake(importName); var replace = transform(opts.transform, importName, matches); var newImportSpecifier = (opts.skipDefaultConversion) ? memberImport : types.importDefaultSpecifier(types.identifier(memberImport.local.name)); transforms.push(types.importDeclaration( [newImportSpecifier], types.stringLiteral(replace) )); }); if (transforms.length > 0) { path.replaceWithMultiple(transforms); } } } } } } ================================================ FILE: packages/loader/src/libs/babel-transform-imports/package.json ================================================ { "name": "babel-plugin-transform-imports", "version": "1.5.1", "description": "Transforms member style imports (import {x} from 'y') into default style imports (import x from 'y/lib/x')", "keywords": [ "babel", "transform", "import", "react-bootstrap", "lodash" ], "main": "index.js", "repository": { "type": "git", "url": "https://bitbucket.org/amctheatres/babel-transform-imports.git" }, "homepage": "https://bitbucket.org/amctheatres/babel-transform-imports", "scripts": { "test": "node_modules/.bin/mocha --compilers js:babel-register" }, "author": "AMC Theatres", "license": "ISC", "dependencies": { "babel-types": "^6.6.0", "is-valid-path": "^0.1.1", "lodash.camelcase": "^4.3.0", "lodash.findkey": "^4.6.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1" }, "devDependencies": { "babel-core": "^6.6.0", "babel-preset-es2015": "^6.6.0", "mocha": "^2.4.5" } } ================================================ FILE: packages/loader/src/libs/babel-transform-imports/test/invalidTransform.js ================================================ module.exports = 'this should be a function'; ================================================ FILE: packages/loader/src/libs/babel-transform-imports/test/tests.js ================================================ import assert from 'assert'; import * as babel from 'babel-core'; import path from 'path'; function createOptions({ preventFullImport = false, transform = 'react-bootstrap/lib/${member}', camelCase = false, kebabCase = false, snakeCase = false, skipDefaultConversion = false, libraryName = 'react-bootstrap' }) { return { [libraryName]: { transform, preventFullImport, camelCase, kebabCase, snakeCase, skipDefaultConversion } }; }; const fullImportRegex = /require\('react-bootstrap'\);$/gm; const memberImportRegex = /require\('react-bootstrap\/lib\/.+'\);$/gm; function occurrences(regex, test) { return (test.match(regex) || []).length; } function transform(code, options = createOptions({})) { return babel.transform(code, { presets: ['es2015'], plugins: [['./index', options]] }).code; } describe('import transformations', function() { it('should handle default imports', function() { const code = transform(`import Bootstrap from 'react-bootstrap';`); assert.equal(occurrences(fullImportRegex, code), 1, 'number of full imports should be 1'); assert.equal(occurrences(memberImportRegex, code), 0, 'number of member imports should be 0'); }); it('should handle namespace imports', function() { const code = transform(`import * as Bootstrap from 'react-bootstrap';`); assert.equal(occurrences(fullImportRegex, code), 1, 'number of full imports should be 1'); assert.equal(occurrences(memberImportRegex, code), 0, 'number of member imports should be 0'); }); it('should handle member imports', function() { const code = transform(`import { Grid, Row as row } from 'react-bootstrap';`); assert.equal(occurrences(fullImportRegex, code), 0, 'number of full imports should be 0'); assert.equal(occurrences(memberImportRegex, code), 2, 'number of member imports should be 2'); }); it('should handle a mix of member and default import styles', function() { const code = transform(`import Bootstrap, { Grid, Row as row } from 'react-bootstrap';`); assert.equal(occurrences(fullImportRegex, code), 1, 'number of full imports should be 1'); assert.equal(occurrences(memberImportRegex, code), 2, 'number of member imports should be 2'); }); it('should handle relative filenames', function() { const libraryName = path.join(__dirname, '../local/path'); const _transform = path.join(__dirname, '../local/path/${member}'); const options = createOptions({ libraryName, transform: _transform }) const code = transform(`import { LocalThing } from './local/path'`, options); assert.equal(/require\('.*LocalThing'\);$/m.test(code), true, 'LocalThing should be directly required'); }); it('should handle relative files with regex expressions', function() { const libraryName = '((\.{1,2}\/?)*)\/local\/path'; const _transform = '${1}/local/path/${member}'; const options = createOptions({ libraryName, transform: _transform }) const code = transform(`import { LocalThing } from '../../local/path'`, options); assert.equal(/require\('\.\.\/\.\.\/local\/path\/LocalThing'\);$/m.test(code), true, 'regex is transformed'); }); it('should handle regex expressions', function() { const libraryName = 'package-(\\w+)\/?(((\\w*)?\/?)*)'; const _transform = 'package-${1}/${2}/${member}'; const options = createOptions({ libraryName, transform: _transform }) const code = transform(`import { LocalThing } from 'package-one/local/path'`, options); assert.equal(/require\('package-one\/local\/path\/LocalThing'\);$/m.test(code), true, 'regex is transformed'); }); }); describe('camelCase plugin option', function() { it('should use camel casing when set', function() { const options = createOptions({ camelCase: true }); const code = transform(`import { CamelMe } from 'react-bootstrap';`, options); assert.notEqual(code.indexOf('camelMe'), -1, 'member name CamelMe should be transformed to camelMe'); }); }); describe('kebabCase plugin option', function() { it('should use kebab casing when set', function() { const options = createOptions({ kebabCase: true }); const code = transform(`import { KebabMe } from 'react-bootstrap';`, options); assert.notEqual(code.indexOf('kebab-me'), -1, 'member name KababMe should be transformed to kebab-me'); }); }); describe('snakeCase plugin option', function() { it('should use snake casing when set', function() { const options = createOptions({ snakeCase: true }); const code = transform(`import { SnakeMe } from 'react-bootstrap';`, options); assert.notEqual(code.indexOf('snake_me'), -1, 'member name SnakeMe should be transformed to snake_me'); }); }); describe('transform as function', function() { it('should throw when provided filename is invalid', function() { const options = createOptions({ transform: 'missingFile.js' }); assert.throws(() => {transform(`import { Row } from 'react-bootstrap';`, options)}); }); it('should throw when provided filename does not resolve to a function', function() { const options = createOptions({ transform: './test/invalidTransform.js' }); assert.throws(() => {transform(`import { Row } from 'react-bootstrap';`, options)}); }); it('should properly execute transform function when provided', function() { const options = createOptions({ transform: './test/transform.js' }); const code = transform(`import { upperCaseMe } from 'react-bootstrap';`, options); assert.notEqual(code.indexOf('UPPERCASEME'), -1, 'member name upperCaseMe should be transformed to UPPERCASEME'); }); it('should call the transform as a function when provided as so', function() { const options = createOptions({ transform: function(input) { return `path/${input}`; } }); const code = transform(`import { somePath } from 'react-bootstrap';`, options); assert.notEqual(code.indexOf('path/somePath'), -1, 'function should transform somePath to path/somePath'); }); }); describe('preventFullImport plugin option', function() { it('should throw on default imports when truthy', function() { const options = createOptions({ preventFullImport: true }); assert.throws(() => {transform(`import Bootstrap from 'react-bootstrap';`, options)}); }); it('should throw on namespace imports when truthy', function() { const options = createOptions({ preventFullImport: true }); assert.throws(() => {transform(`import * as Bootstrap from 'react-bootstrap';`, options)}); }); it('should not throw on member imports when truthy', function() { const options = createOptions({ preventFullImport: true }); assert.doesNotThrow(() => {transform(`import { Grid, Row as row } from 'react-bootstrap';`, options)}); }); }); describe('skipDefaultConversion plugin option', function() { it('should retain named import syntax when enabled', function() { const options = createOptions({ skipDefaultConversion: true }); const code = transform(`import { Grid, Row as row } from 'react-bootstrap';`, options); assert.equal(code.indexOf('_interopRequireDefault'), -1, 'skipDefaultConversion should not allow conversion to default import'); }) }); describe('edge cases', function() { it('should throw when transform plugin option is missing', function() { const options = createOptions({ transform: null }); assert.throws(() => {transform(`import Bootstrap from 'react-bootstrap';`, options)}); }); }); ================================================ FILE: packages/loader/src/libs/babel-transform-imports/test/transform.js ================================================ module.exports = function(importName) { return 'test/lib/' + importName.toUpperCase(); } ================================================ FILE: packages/loader/src/libs/get-less-variables.js ================================================ 'use strict' const fs = require('fs') const path = require('path') const stripeComments = require('strip-css-comments') module.exports = function getLessVariables(theme) { return getContent(theme) } function trim (str) { if (!str) { return '' } else { return str.replace(/^\s+|\s+$/g, '') } } function getContent (file) { let themeContent = fs.readFileSync(file, 'utf-8') themeContent = stripeComments(themeContent) var variables = {} themeContent.split('\n').forEach(function (item) { if (trim(item).indexOf('//') === 0 || trim(item).indexOf('/*') === 0) { return } // has comments if (item.indexOf('//') > 0) { item = trim(item.slice(0, item.indexOf('//'))) } if (item.indexOf('/*') > 0) { item = trim(item.slice(0, item.indexOf('/*'))) } var _pair = item.split(':') if (_pair.length < 2) { if (!/@import/.test(_pair[0])) { return } let partFile = _pair[0] .replace(/;/g, '') .replace('@import', '') .replace(/'/g, '') .replace(/"/g, '') .replace(/\s+/g, '').trim() const partPath = path.resolve(path.dirname(file), partFile) let partVariables = getContent(partPath) for (let i in partVariables) { variables[i] = partVariables[i] } } else { let key = _pair[0].replace('\r', '').replace('@', '') if (!key) return; key = key.trim() if (!/^[A-Za-z0-9_-]*$/.test(key)) { console.log(`[vux-loader] 疑似不合法命名,将被忽略:${key}`) return } var value = _pair[1].replace(';', '').replace('\r', '').replace(/^\s+|\s+$/g, '') variables[key] = value } }) return variables } ================================================ FILE: packages/loader/src/libs/getTag.js ================================================ function getTag(content, tag, action='remove') { const reg = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\/${tag}>`, 'g') content = content.replace(reg, function (tag, text) { if (action ==='remove') { return '' } else if (action === 'reserve') { return text } }) return content } function removeTagCode (content, tag) { return getTag(content, tag, 'remove') } function reserveTagCode (content, tag) { return getTag(content, tag, 'reserve') } module.exports = { removeTagCode: removeTagCode, reserveTagCode: reserveTagCode, getTag: getTag } ================================================ FILE: packages/loader/src/libs/import-parser-v2.js ================================================ const babel = require('@babel/core') const transformPlugin = require('./babel-transform-imports/index') const map = require('vux/src/components/map.json') function transform (code, filename) { const rs = babel.transform(code, { plugins: [[transformPlugin, { vux: { preventFullImport: true, libraryName: 'vux', camelCase: false, kebabCase: false, snakeCase: false, transform: function (importName, matches) { return `vux/${map[importName]}` } } }]], 'filename': filename }).code return `/* this file is transformed by vux-loader */ /* eslint-disable */ ${rs}` } module.exports = transform ================================================ FILE: packages/loader/src/libs/import-parser.js ================================================ 'use strict' const stripComments = require('strip-comments') function parse(source, fn, moduleName) { // fix no space between import and { // ref https://github.com/airyland/vux/issues/1365 source = source.replace(/import{/g, 'import {') source = source.replace(/\/\/\n/g, '') source = trimLine(source) moduleName = moduleName || 'vux' if ((moduleName && source.indexOf(moduleName) === -1) || source.indexOf('import') === -1) { return source } const reg = getReg(moduleName) let replaceList = [] removeComments(removeCommentLine(source)).replace(reg, function (match1, match2, match3) { // dirty way for the moment if(match1.indexOf('import') !== match1.lastIndexOf('import')) { match1 = match1.slice(match1.lastIndexOf('import'), match1.length) } const components = getNames(match1) if (fn) { const replaceString = fn({ components: components, match1: match1, match2: match2, match3: match3, source: source }) replaceList.push([match1, replaceString]) } }) source = removeCommentLine(source) replaceList.forEach(function (one) { source = source.replace(one[0], one[1]) }) return source } module.exports = parse function getReg(moduleName) { return new RegExp(`import\\s.*(\\n(?!import).*)*from(\\s)+('|")${moduleName}('|");*`, 'g') } function removeCommentLine (source) { const rs = source.split('\n').map(function (line) { line = line.replace(/^\s+|\s+$/g, '') try { line = stripComments.line(line) } catch (e) { } return line }).join('\n') return rs } function getNames(one) { const startIndex = one.indexOf('{') const endIndex = one.indexOf('}') const content = one.slice(startIndex + 1, endIndex) const list = content.split(',').map(one => { return one.replace(/^\s+|\s+$/g, '') .replace(/\n/g, '') }).filter(one => { return !!one }).map(one => { if (!/\s+/.test(one)) { return { originalName: one, newName: one } } else if (/\s+as/.test(one)) { let _list = one.split('as').map(function (one) { return one.replace(/^\s+|\s+$/g, '') }) return { originalName: _list[0], newName: _list[1] } } return one }) return list } function trimLine (str) { let isImport = false let list = str.split('\n') for (let i = 0; i < list.length; i++) { let currentLine = trim(list[i]) if (/import/.test(currentLine) && !/from\s+('|")vux('|")/.test(currentLine)) { isImport = true } else if (/from\s+('|")(.+)('|")/.test(currentLine)) { if (isImport) { isImport = false } } else { if (isImport) { list[i] = trim(list[i]) } } } return list.join('\n') } function trim (str) { return str.replace(/^\s+/, '').replace(/\s+$/, '') } // http://james.padolsey.com/javascript/removing-comments-in-javascript/ function removeComments(str) { str = ('__' + str + '__').split(''); var mode = { singleQuote: false, doubleQuote: false, regex: false, blockComment: false, lineComment: false, condComp: false }; for (var i = 0, l = str.length; i < l; i++) { if (mode.regex) { if (str[i] === '/' && str[i - 1] !== '\'') { mode.regex = false; } continue; } if (mode.singleQuote) { if (str[i] === "'" && str[i - 1] !== '\'') { mode.singleQuote = false; } continue; } if (mode.doubleQuote) { if (str[i] === '"' && str[i - 1] !== '\'') { mode.doubleQuote = false; } continue; } if (mode.blockComment) { if (str[i] === '*' && str[i + 1] === '/') { str[i + 1] = ''; mode.blockComment = false; } str[i] = ''; continue; } if (mode.lineComment) { if (str[i + 1] === 'n' || str[i + 1] === 'r') { mode.lineComment = false; } str[i] = ''; continue; } if (mode.condComp) { if (str[i - 2] === '@' && str[i - 1] === '*' && str[i] === '/') { mode.condComp = false; } continue; } mode.doubleQuote = str[i] === '"'; mode.singleQuote = str[i] === "'"; if (str[i] === '/') { if (str[i + 1] === '*' && str[i + 2] === '@') { mode.condComp = true; continue; } if (str[i + 1] === '*') { str[i] = ''; mode.blockComment = true; continue; } if (str[i + 1] === '/') { str[i] = ''; mode.lineComment = true; continue; } mode.regex = true; } } const rs = str.join('').slice(2, -2); return rs } ================================================ FILE: packages/loader/src/libs/parse-demo-code.js ================================================ 'use strict' const parse = function (source, lang) { } const code = ` Default: zh-CN: 默认 Quantity: zh-CN: 数量 hello: zh-CN: 你好 ` ================================================ FILE: packages/loader/src/libs/parse-virtual-component.js ================================================ 'use strict' module.exports = function (source, name, cb) { source = replaceEnd(source, name) const reg1 = new RegExp(`<${name}([\\s\\S]*?)>.*?`, 'g') source = source.replace(reg1, function (a, b) { let query = getAttributes(a) return cb(query, a) }) // for const reg2 = new RegExp(`<${name}([\\s\\S]*?)\/>`, 'g') source = source.replace(reg2, function (a, b) { let query = getAttributes(a) return cb(query, a) }) return source } function replaceEnd(str, name) { const list = str.split('') let start = false let end = false for (let i = 0; i < list.length; i++) { if (list[i] === '<' && list[i + 1] !== '/') { if (list.slice(i + 1, i + 1 + name.length).join('') === name) { start = true } else { start = false } } if (list[i] === '/' && list[i + 1] === '>') { if (start) { end = true list[i] = 'V' list[i + 1] = 'V' } } } return list.join('').replace(/VV/g, `>`) } function getAttributes (string) { let match = string.match(/\s+(.*?)="(.*?)"/g) let obj = {} let list = match.map(one => { return one.replace(/^\s+|\s+$/g, '').replace(/\.native/g, '') }) for (let i = 0; i < list.length; i++) { const pair = list[i].split('=').map(one => { return one.replace(/"/g, '') }) if (pair.length === 2) { obj[pair[0]] = pair[1] } else if (pair.length > 2) { obj[pair[0]] = pair.slice(1).join('=') } } return { stringList: list.join(' '), arrayList: list, objectList: obj } } ================================================ FILE: packages/loader/src/libs/parse-x-icon.js ================================================ 'use strict' const parseVirtualComponent = require('./parse-virtual-component') const path = require('path') const fs = require('fs') module.exports = function (source, config) { source = parseVirtualComponent(source, 'x-icon', function (query) { let size = query.objectList.size || 24 let type = query.objectList.type let svgPath = path.resolve(config.options.projectRoot, `node_modules/vux/src/icons/${type}.svg`) if (config.options.vuxDev) { svgPath = path.resolve(config.options.projectRoot, `src/icons/${type}.svg`) } // merge classname let className = `vux-x-icon vux-x-icon-${type}` if (query.objectList.class) { className += ` ${query.objectList.class}` } let props = '' for (let i in query.objectList) { if (i !== 'class') { props += ` ${i}="${query.objectList[i]}"` } } const content = fs.readFileSync(svgPath, 'utf-8') return content.replace('width="512"', `width="${size}"`) .replace('height="512"', `height="${size}"`) .replace('sdfdsf` a = a.replace(/\s+@mousedown="(.*?)"/g, ' ') a = a.replace(/\s+@mouseover="(.*?)"/g, ' ') a = a.replace(/\s+@mouseup="(.*?)"/g, ' ') console.log(a) ================================================ FILE: packages/loader/src/noop-loader.js ================================================ module.exports = function (source) { this.cacheable() return '' } ================================================ FILE: packages/loader/src/script-loader.js ================================================ 'use strict' const utils = require('loader-utils') const fs = require('fs') const i18nReplaceForScript = require('../libs/replace-i18n-for-script').replace const getI18nBlockWithLocale = require('../libs/get-i18n-block').getWithLocale const path = require('path') // const parser = require('./libs/import-parser-v2') module.exports = function (source) { this.cacheable() const _this = this let config = this.vux || utils.getLoaderConfig(this, 'vux') // no config found when using thread-loader if (this['thread-loader']) { const configFile = require.resolve('vux/.config.cache.json') config = require(configFile) } let i18nPlugin const i18nPluginsMatch = config.plugins.filter(function (one) { return one.name === 'i18n' }) if (i18nPluginsMatch.length) { i18nPlugin = i18nPluginsMatch[0] } let isVuxVueFile = this.resourcePath.replace(/\\/g, '/').indexOf('vux/src/components') > -1 if (config.options.vuxDev && this.resourcePath.replace(/\\/g, '/').indexOf('src/components') > -1) { isVuxVueFile = true } const isVuxComponent = this.resourcePath.replace(/\\/g, '/').indexOf('/vux/src/components') > -1 if (config.plugins.length) { config.plugins.forEach(function (plugin) { // script-parser if (plugin.name === 'script-parser') { if (plugin.fn) { source = plugin.fn.call(_this, source) } } }) } if (config.options.useVuxUI && /}\s+from(.*?)('|")vux/.test(source)) { const maps = this.vuxMaps || utils.getLoaderConfig(this, 'vuxMaps') // source = parser(source, function (opts) { // let str = '' // opts.components.forEach(function (component) { // let file = `vux/${maps[component.originalName]}` // if (config.options.vuxDev) { // if (/App\.vue/.test(_this.resourcePath)) { // file = file.replace(/vux\/src/g, '.') // } else { // let relative = '..' // // component file import other functions // if (isVuxComponent && !/components/.test(file)) { // relative = '../..' // } // if (/demos/.test(_this.resourcePath)) { // const splits = _this.resourcePath.split('demos')[1].split(path.sep).length - 1 // let dir = [] // for (let i = 0; i < splits; i++) { // dir.push('..') // } // relative = dir.join('/') // } // if (config.options.resolveVuxDir) { // relative = config.options.resolveVuxDir // } // file = file.replace(/vux\/src/g, relative) // } // } // str += `import ${component.newName} from '${file}'\n` // }) // return str // }, 'vux') // try { // source = parser(source) // } catch (e) { // console.log(e) // } } if (config.options.vuxWriteFile === true) { fs.writeFileSync(this.resourcePath + '.vux.js', source) } if (i18nPlugin && !isVuxVueFile && source.indexOf(`$t('`) > -1 && i18nPlugin.staticReplace === true) { const rs = getI18nBlockWithLocale({ code: _this.resourcePath, isFile: true, locale: i18nPlugin.vuxLocale || 'zh-CN' }) source = i18nReplaceForScript(source, rs) } return source } function camelCaseToDash(str) { return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase() } ================================================ FILE: packages/loader/src/style-loader.js ================================================ 'use strict' const utils = require('loader-utils') module.exports = function (source) { this.cacheable() const _this = this let config = this.vux || utils.getLoaderConfig(this, 'vux') if (this['thread-loader']) { const configFile = require.resolve('vux/.config.cache.json') config = require(configFile) } if (!config.plugins || !config.plugins.length) { return source } config.plugins.forEach(function (plugin) { // style-parser if (plugin.name === 'style-parser') { if (plugin.fn) { source = plugin.fn.call(_this, source) } } }) if (config.options.vuxDev) { if (/App\.vue$/.test(this.resourcePath)) { source = source.replace(/~vux\/src/g, '.') } else { if (config.options.resolveVuxDir) { // if (_this.resourcePath.includes('pages') && _this.resourcePath.includes('IconLoading') ) // source = source.replace(/~vux\/src/g, config.options.resolveVuxDir).replace('//', '/') // if (_this.resourcePath.includes('pages') && _this.resourcePath.includes('IconLoading') ) } else { source = source.replace(/~vux\/src/g, '..') } } } return source } ================================================ FILE: packages/loader/src/template-loader.js ================================================ 'use strict' const _ = require('lodash') const touch = require('touch') const utils = require('loader-utils') const yamlReader = require('js-yaml') const fs = require('fs') const path = require('path') const matchI18nReg = /\$t\('?(.*?)'?\)/g const parseVirtualComponent = require('./libs/parse-virtual-component') const parseXIcon = require('./libs/parse-x-icon') const removeTagCode = require('./libs/getTag').removeTagCode const reserveTagCode = require('./libs/getTag').reserveTagCode const compareVersions = require('compare-versions') const i18nParser = require('../libs/parse-i18n-function').parse const getI18nBlockWithLocale = require('../libs/get-i18n-block').getWithLocale const getName = function (path) { return path.replace(/\\/g, '/').split('components')[1].replace('index.vue', '').replace(/\//g, '') } module.exports = function (source) { const _this = this this.cacheable() const query = this.query ? utils.parseQuery(this.query) : {} let config = this.vux || utils.getLoaderConfig(this, 'vux') if (this['thread-loader']) { const configFile = require.resolve('vux/.config.cache.json') config = require(configFile) } const basename = path.basename(this.resourcePath) let isVuxVueFile = this.resourcePath.replace(/\\/g, '/').indexOf('vux/src/components') > -1 let isVuxVueDemo = this.resourcePath.replace(/\\/g, '/').indexOf('vux/src/demos') > -1 if (config.options.vuxDev && this.resourcePath.replace(/\\/g, '/').indexOf('src/components') > -1) { isVuxVueFile = true } if (config.options.vuxDev && this.resourcePath.replace(/\\/g, '/').indexOf('src/demos') > -1) { isVuxVueDemo = true } // x-icon if (config.options.useVuxUI && source.indexOf(' -1) { source = parseXIcon(source, config) } // v-no-ssr if (config.options.ssr) { source = removeTagCode(source, 'v-no-ssr') source = reserveTagCode(source, 'v-ssr') } else { source = removeTagCode(source, 'v-ssr') source = reserveTagCode(source, 'v-no-ssr') } const locales = this.vuxLocales || utils.getLoaderConfig(this, 'vuxLocales') /** * ======== i18n ======== */ let dynamic = false let locale = 'zh-CN' let vuxFunctionName = '$t' let functionName = '$t' let staticTranslations = {} let langs = ['en', 'zh-CN'] let staticReplace // 如果不设置, dynamic 为false, local 为 zh-CN const i18nPluginsMatch = config.plugins.filter(function (one) { return one.name === 'i18n' }) if (i18nPluginsMatch.length) { dynamic = !i18nPluginsMatch[0].vuxStaticReplace locale = i18nPluginsMatch[0].vuxLocale || 'zh-CN' vuxFunctionName = i18nPluginsMatch[0].vuxFunctionName || '$t' functionName = i18nPluginsMatch[0].functionName || '$t' langs = i18nPluginsMatch[0].localeList || langs staticTranslations = i18nPluginsMatch[0].staticTranslations || {} staticReplace = typeof i18nPluginsMatch[0].staticReplace === 'undefined' ? undefined : i18nPluginsMatch[0].staticReplace } else { dynamic = false locale = 'zh-CN' vuxFunctionName = '$t' } // 对于小于 vue@2.5.0 的版本,将 slot-scope 替换为 scope const vueVersion = config.options.vueVersion const isLt250 = compareVersions(vueVersion, '2.5.0') === -1 if (isLt250 && (isVuxVueFile || isVuxVueDemo) && source.indexOf('slot-scope=') > -1) { source = source.replace(/slot-scope=/g, 'scope=') } if ((isVuxVueFile) && source.indexOf("$t(") > -1) { const name = getName(this.resourcePath) if (!dynamic) { source = source.replace(matchI18nReg, function (a, b) { let key = `vux.${name}.${b}` if (a.indexOf('/*') > -1) { const start = a.indexOf('/*') const end = a.indexOf('*/') const str = a.slice(start + 2, end - 1) const map = {} const list = str.split(',').map(one => { one = one.trim() const pair = one.split(':').map(one => one.trim()) map[pair[0]] = pair[1] }) return map[locale] } if (a.indexOf("'") > -1) { // 用于翻译字符 return "'" + locales[locale][key] + "'" } else { // 用于翻译变量,如 $t(text) return b } }) } else { // dynamic 为 true, 则对于 vux 源码,把 key 加上 prefix source = source.replace(matchI18nReg, function (a, b) { if (a.indexOf("'") > -1) { return a.replace(b, `vux.${name}.${b}`).replace('$t', vuxFunctionName) } else { return a.replace('$t', vuxFunctionName) } }) } } else if (!isVuxVueFile && source.indexOf(`${functionName}(`) > -1 && staticTranslations && staticReplace === true) { // 对于项目文件进行静态替换 /** let matchI18nReg = new RegExp(`\$${functionName}\('?(.*?)'?\)`, 'g') source = source.replace(matchI18nReg, function (a, b) { if (a.indexOf("'") > -1) { return `${ staticTranslations[b] || b }` } }) */ const rs = getI18nBlockWithLocale({ code: _this.resourcePath, isFile: true, locale: locale }) source = i18nParser(source, rs) } config.plugins.forEach(function (plugin) { // template-feature-switch /** show show hide hide */ if (plugin.name === 'template-feature-switch') { // replace features if (plugin.features && source.indexOf('') > -1) { source = parseOnFeature(source, plugin.features) } if (plugin.features && source.indexOf('') > -1) { source = parseOffFeature(source, plugin.features) } } // 非 vux 组件才需要生成语言 if (!isVuxVueFile && plugin.name === 'i18n' && plugin.extractToFiles) { const savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles) let format = 'yml' if (/\.json$/.test(plugin.extractToFiles)) { format = 'json' } let fileMode = 'all' if (plugin.extractToFiles.indexOf('{lang}') !== -1) { fileMode = 'single' } const isDynamic = plugin.staticReplace === false if (isDynamic) { setTimeout(function () { const rawFileContent = fs.readFileSync(_this.resourcePath, 'utf-8') const results = rawFileContent.match(/]*>([\s\S]*?)<\/i18n>/) if (results) { let attrsMap = {} const attrs = results[0].split('\n')[0].replace('', '') .replace(/"/g, '') .replace(/\r/g, '') .split(' ') .filter(function (one) { return !!one }).forEach(function (one) { let tmp = one.split('=') attrsMap[tmp[0]] = tmp[1] }) try { const local = yamlReader.safeLoad(results[1]) const rs = parseI18n(local, langs) let finalConfig = {} // all and yml format if (fileMode === 'all' && format === 'yml') { touch.sync(savePath) let currentConfig = fs.readFileSync(savePath, 'utf-8') if (!currentConfig) { finalConfig = rs.translations } else { finalConfig = mergeLang(yamlReader.safeLoad(currentConfig), rs.translations) } if (!currentConfig || (currentConfig && JSON.stringify(yamlReader.safeLoad(currentConfig)) !== JSON.stringify(finalConfig))) { fs.writeFileSync(savePath, yamlReader.safeDump(finalConfig)) } } if (fileMode === 'all' && format === 'json') { touch.sync(savePath) let currentConfig = fs.readFileSync(savePath, 'utf-8') if (!currentConfig) { finalConfig = rs.translations } else { finalConfig = mergeLang(JSON.parse(currentConfig), rs.translations) } if (!currentConfig || (currentConfig && JSON.stringify(currentConfig) !== JSON.stringify(finalConfig))) { fs.writeFileSync(savePath, JSON.stringify(finalConfig, null, 2)) } } // single and yml if (fileMode === 'single' && format === 'yml') { for (let i = 0; i < langs.length; i++) { let lang = langs[i] let savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles).replace('{lang}', lang) touch.sync(savePath) let currentConfig = fs.readFileSync(savePath, 'utf-8') if (!currentConfig) { finalConfig = rs.translations[lang] } else { finalConfig = Object.assign(yamlReader.safeLoad(currentConfig), rs.translations[lang]) } if (!currentConfig || (currentConfig && JSON.stringify(yamlReader.safeLoad(currentConfig)) !== JSON.stringify(finalConfig))) { fs.writeFileSync(savePath, yamlReader.safeDump(finalConfig)) } } } if (fileMode === 'single' && format === 'json') { for (let i = 0; i < langs.length; i++) { let lang = langs[i] let savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles).replace('{lang}', lang) touch.sync(savePath) let currentConfig = fs.readFileSync(savePath, 'utf-8') if (!currentConfig) { finalConfig = rs.translations[lang] } else { finalConfig = Object.assign(JSON.parse(currentConfig), rs.translations[lang]) } if (!currentConfig || (currentConfig && JSON.stringify(currentConfig) !== JSON.stringify(finalConfig))) { fs.writeFileSync(savePath, JSON.stringify(finalConfig, null, 2)) } } } } catch (e) { console.log(e) console.log('yml 格式有误,请重新检查') } } }) } } // template-parser if (plugin.name === 'template-parser') { if (plugin.fn) { source = plugin.fn.call(_this, source) } if (plugin.replaceList && plugin.replaceList.length) { plugin.replaceList.forEach(function (replacer) { source = source.replace(replacer.test, replacer.replaceString) }) } } if (plugin.name === 'template-string-append') { if (new RegExp(plugin.test).test(_this.resourcePath)) { var componentName = basename.replace('.vue', '').toLowerCase() var string = plugin.fn({ resourcePath: _this.resourcePath, basename: basename }) if (string) { source = source.replace(/\s+$/g, '').replace(/\\n/g, '').replace(/<\/div>$/, string + '
    ') } } } }) if (config.options.vuxWriteFile === true) { fs.writeFileSync(this.resourcePath + '.vux.html', source) } source = removeTagCode(source, 'i18n') return source } function parseOnFeature(content, features) { content = content.replace(/]*>([\s\S]*?)<\/on>/g, function (tag, text) { const key = tag.split('\n')[0].replace('', '') .replace(/"/g, '') .replace(/\r/g, '') .split(' ') .filter(function (one) { return !!one }).map(function (one) { let tmp = one.split('=') return tmp[1] }) if (features[key] && features[key] === true) { // true return text } else { // false return '' } }) return content } function parseOffFeature(content, features) { content = content.replace(/]*>([\s\S]*?)<\/off>/g, function (tag, text) { const key = tag.split('\n')[0].replace('', '') .replace(/"/g, '') .replace(/\r/g, '') .split(' ') .filter(function (one) { return !!one }).map(function (one) { let tmp = one.split('=') return tmp[1] }) if (!features[key]) { // false return text } else { // true return '' } }) return content } function parseI18n(json, langs) { langs = langs || [] if (!langs || !langs.length) { for (let i in json) { langs = langs.concat(Object.keys(json[i])) } langs = _.uniq(langs) } let rs = {} for (let i = 0; i < langs.length; i++) { let lang = langs[i] if (!rs[lang]) { rs[lang] = {} } for (let j in json) { rs[lang][j] = json[j][lang] || j } } return { langs: langs, translations: rs } } function mergeLang(a, b) { for (let i in b) { for (let j in b[i]) { if (!a[i]) { a[i] = b[i] } a[i][j] = b[i][j] } } return a } ================================================ FILE: packages/loader/test/fixtures/basic.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/css-modules.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/es2015.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/extend.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/extract-css.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/inject.js ================================================ window.injector = require('!!../../lib/loader?inject!./inject.vue') ================================================ FILE: packages/loader/test/fixtures/inject.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/media-query.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/postcss.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/pre.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/resolve.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/scoped-css.vue ================================================
    ================================================ FILE: packages/loader/test/fixtures/script-import.js ================================================ export default { data () { return { msg: 'Hello from Component A!' } } }; ================================================ FILE: packages/loader/test/fixtures/script-import.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/service.js ================================================ module.exports = { msg: 'hi' } ================================================ FILE: packages/loader/test/fixtures/style-import-scoped.css ================================================ h1 { color: green; } ================================================ FILE: packages/loader/test/fixtures/style-import.css ================================================ h1 { color: red; } ================================================ FILE: packages/loader/test/fixtures/style-import.vue ================================================ ================================================ FILE: packages/loader/test/fixtures/template-import.pug ================================================ div h1 hello ================================================ FILE: packages/loader/test/fixtures/template-import.vue ================================================ ================================================ FILE: packages/loader/test/test.js ================================================ process.env.VUE_LOADER_TEST = true var path = require('path') var webpack = require('webpack') var MemoryFS = require('memory-fs') var jsdom = require('jsdom') var expect = require('chai').expect var rimraf = require('rimraf') var genId = require('vue-loader/lib/gen-id') var SourceMapConsumer = require('source-map').SourceMapConsumer var ExtractTextPlugin = require("extract-text-webpack-plugin") var compiler = require('vue-loader/lib/template-compiler') var normalizeNewline = require('normalize-newline') var vuxLoader = require('../src/index.js') var i18nParser = require('../libs/parse-i18n-function').parse const i18nParserForScript = require('../libs/replace-i18n-for-script').replace const getI18nBlock = require('../libs/get-i18n-block').get const getI18nBlockWithLocale = require('../libs/get-i18n-block').getWithLocale function getOptionsPlugin(config) { const match = config.plugins.filter(one => { return one.constructor.name === 'LoaderOptionsPlugin' }) return match[0] } // var loaderPath = 'expose-loader?vueModule!' + path.resolve(__dirname, '../node_modules/vue-loader/index.js') var loaderPath = 'expose-loader?vueModule!' + path.resolve(__dirname, '../src/index.js') + '!vue-loader' var mfs = new MemoryFS() var globalConfig = { output: { path: '/', filename: 'test.build.js' }, module: { rules: [ { test: /\.vue$/, loader: loaderPath } ] } } function bundle(options, vuxOptions, cb) { var vueOptions = options.vue delete options.vue var config = Object.assign(globalConfig, options) // assign vue Options if (vueOptions) { config.plugins = (config.plugins || []).concat(new webpack.LoaderOptionsPlugin({ vue: vueOptions })) } let basicVux = { options: { loaderString: loaderPath, rewriteLoaderString: false, isWebpack2: true, isTest: true } } if (vuxOptions.options) { for (let i in vuxOptions.options) { basicVux.options[i] = vuxOptions.options[i] } } if (vuxOptions.plugins) { basicVux.plugins = vuxOptions.plugins } config = vuxLoader.merge(config, basicVux) var webpackCompiler = webpack(config) webpackCompiler.outputFileSystem = mfs webpackCompiler.run(function (err, stats) { expect(err).to.be.null if (stats.compilation.errors.length) { stats.compilation.errors.forEach(function (err) { console.error(err.message) }) } expect(stats.compilation.errors).to.be.empty cb(mfs.readFileSync('/test.build.js').toString()) }) } function test(options, vuxOptions, assert) { bundle(options, vuxOptions, function (code) { jsdom.env({ html: '', src: [code], done: function (err, window) { if (err) { console.log(err[0].data.error.stack) expect(err).to.be.null } assert(window, interopDefault(window.vueModule), window.vueModule) } }) }) } function mockRender(options, data) { return options.render.call(Object.assign({ _v(val) { return val }, _self: {}, $createElement(tag, data, children) { if (Array.isArray(data)) { children = data data = null } return { tag: tag, data: data, children: children } }, _m(index) { return options.staticRenderFns[index].call(this) }, _s(str) { return String(str) } }, data)) } function interopDefault(module) { return module ? module.__esModule ? module.default : module : module } var parse = require('../src/libs/import-parser') const str = parse(` ================================================ FILE: packages/loader/test/vux-fixtures/less-theme-import.less ================================================ @import './less-theme-002.less'; @theme-p-color: red; @x: x; dsfdf ================================================ FILE: packages/loader/test/vux-fixtures/option-vux-dir.vue ================================================ ================================================ FILE: packages/loader/test/vux-fixtures/script-parser-fn.vue ================================================ ================================================ FILE: packages/loader/test/vux-fixtures/style-parser-basic.vue ================================================ ================================================ FILE: packages/loader/test/vux-fixtures/template-feature-switch-basic.vue ================================================ ================================================ FILE: packages/loader/test/vux-fixtures/template-parser-fn.vue ================================================ ================================================ FILE: packages/vue-cli-3-example/.browserslistrc ================================================ > 1% last 2 versions not ie <= 8 ================================================ FILE: packages/vue-cli-3-example/.editorconfig ================================================ [*.{js,jsx,ts,tsx,vue}] indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: packages/vue-cli-3-example/.eslintrc.js ================================================ module.exports = { root: true, env: { node: true }, 'extends': [ 'plugin:vue/essential', '@vue/standard' ], rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' }, parserOptions: { parser: 'babel-eslint' } } ================================================ FILE: packages/vue-cli-3-example/.gitignore ================================================ .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw* ================================================ FILE: packages/vue-cli-3-example/README.md ================================================ # vux-cli3 ## Project setup ``` yarn install ``` ### Compiles and hot-reloads for development ``` yarn run serve ``` ### Compiles and minifies for production ``` yarn run build ``` ### Run your tests ``` yarn run test ``` ### Lints and fixes files ``` yarn run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: packages/vue-cli-3-example/babel.config.js ================================================ module.exports = { presets: [ '@vue/app' ] } ================================================ FILE: packages/vue-cli-3-example/package.json ================================================ { "name": "vux-cli3", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "core-js": "^2.6.5", "vue": "^2.6.6", "vue-router": "^3.0.1", "vuex": "^3.0.1", "vux": "^2.9.2" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.5.0", "@vue/cli-plugin-eslint": "^3.5.0", "@vue/cli-service": "^3.5.0", "@vue/eslint-config-standard": "^4.0.0", "@vux/loader": "^2.0.0-rc4", "babel-eslint": "^10.0.1", "eslint": "^5.8.0", "eslint-plugin-vue": "^5.0.0", "less": "^3.0.4", "less-loader": "^4.1.0", "vue-template-compiler": "^2.5.21" } } ================================================ FILE: packages/vue-cli-3-example/postcss.config.js ================================================ module.exports = { plugins: { autoprefixer: {} } } ================================================ FILE: packages/vue-cli-3-example/public/index.html ================================================ vux-cli3
    ================================================ FILE: packages/vue-cli-3-example/src/App.vue ================================================ ================================================ FILE: packages/vue-cli-3-example/src/components/HelloWorld.js ================================================ import Group from 'vux/src/components/group/index' import { Cell } from 'vux' export default { components: { Group, Cell }, props: { msg: String }, methods: { alert () { this.$vux.alert.show('hello') } } } ================================================ FILE: packages/vue-cli-3-example/src/components/HelloWorld.vue ================================================ ================================================ FILE: packages/vue-cli-3-example/src/main.js ================================================ import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import { AlertPlugin } from 'vux' Vue.use(AlertPlugin) Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app') ================================================ FILE: packages/vue-cli-3-example/src/router.js ================================================ import Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'home', component: Home }, { path: '/about', name: 'about', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ './views/About.vue') } ] }) ================================================ FILE: packages/vue-cli-3-example/src/store.js ================================================ import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { } }) ================================================ FILE: packages/vue-cli-3-example/src/theme.less ================================================ @yellow: red; @theme-color: #FF9900; @button-tab-border-color: #ccc; @button-tab-active-background-color: @yellow; @button-tab-active-font-color: #000; @switch-checked-bg-color: @yellow; @switch-checked-border-color: @yellow; @number-button-font-color: #FF9900; @number-round-button-enabled-border-color: #FF9900; @swiper-indicator-active-color: #FF9900; @checklist-icon-active-color: #FF9900; @tab-text-active-color: #FF9900; @tab-bar-active-color: #FF9900; @dialog-button-text-primary-color: #FF9900; @form-preview-button-primary-color: #FF9900; @tabbar-text-active-color: #35495e; @toast-position-top-offset: 56px; @toast-position-bottom-offset: 60px; @search-cancel-font-color: #FF9900; @radio-checked-icon-color: #FF9900; @calendar-arrow-color: #c0c0c0; ================================================ FILE: packages/vue-cli-3-example/src/views/About.vue ================================================ ================================================ FILE: packages/vue-cli-3-example/src/views/Home.vue ================================================ ================================================ FILE: packages/vue-cli-3-example/vue.config.js ================================================ module.exports = { configureWebpack: config => { require('@vux/loader').merge(config, { plugins: ['vux-ui', { name: 'less-theme', path: 'src/theme.less' }] }) } } ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/components/actionsheet/index.vue ================================================ cancel: en: cancel zh-CN: 取消 ================================================ FILE: src/components/actionsheet/metas.yml ================================================ description: | Action Sheet是由用户操作后触发的一种特定的模态弹出框 ,呈现一组与当前情境相关的两个或多个选项。用户可以使用Action Sheet启动某个任务,或者确认是否开始执行某个可能具有破坏性的操作。 - 高亮危险操作。将危险操作用红色标注,为他们提供其它替代的安全选项。 - 提供清晰准确的描述。在页面有多个唤起Action Sheet的对象或选项本身不够明晰的情况下,提供清晰准确的描述是非常有必要的。 - 不要放置过多选项以至于需要滚动才能看完全部选项。 category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - actionsheet - dialog - action sheet - choose zh-CN: - 操作表 - 提示 after_extra: en: | From `v2.1.0`, `menus` supports type: Array. `label`: label name,you can use simple text or `html` `value`: event name,if not specified, no `on-click-menu` will be emitted `type`: - `primary` primary color - `warn` warn color - `disabled` gray color, the menu cannot be clicked - `info ` the menu item shows as a tip, the same function as `key.noop` when setting menus by `Object`. ``` js [{ label: 'Are you sure?
    Once deleted, you will never find it.', type: 'info' }, { label: 'Primary', type: 'primary', value: 'primary' }, { label: 'Warn', type: 'warn' }, { label: 'Disabled', type: 'disabled' }, { label: 'Default' }] ``` zh-CN: | 从`v2.1.0`开始支持数组类型的菜单。 `label`: 菜单名字,支持文字及`html`。 `value`: 英文字符,表示触发事件的名字,如果不设置不会触发`on-click-menu`事件。 `type`: 类型,可选值如下 - `primary` 主色 - `warn` 警告色 - `disabled` 不可操作,灰色。点击时不会关闭 - `info ` 信息提示,点击时不会关闭。作用同对象类型的`key.noop`。 ``` js [{ label: 'Are you sure?
    Once deleted, you will never find it.', type: 'info' }, { label: 'Primary', type: 'primary', value: 'primary' }, { label: 'Warn', type: 'warn' }, { label: 'Disabled', type: 'disabled' }, { label: 'Default' }] ``` props: value: type: Boolean default: false en: if showing the component; use v-model for binding zh-CN: 是否显示, 使用 v-model 绑定变量 show-cancel: type: Boolean default: false en: if showing the cancel menu; invalid for android theme zh-CN: 是否显示取消菜单,对安卓风格无效 cancel-text: type: String default: 'cancel(取消)' en: text of cancel menu zh-CN: 取消菜单的显示文字 theme: type: String default: 'ios' en: "theme of menus, can be in ['ios', 'android']" zh-CN: "菜单风格,可选值为['ios','android']" menus: type: Object,Array default: '{}' en: "menu items, for example: `{menu1: 'some text'}`, menu name with `.noop` will not trigger click events.
    `Menus` supports array since `v2.1.0`. Keys can be customized. More details below." zh-CN: "菜单项列表,举例:`{menu1: '删除'}`,如果名字上带有`.noop`表明这是纯文本(HTML)展示,不会触发事件,用于展示描述或者提醒。
    从`v2.1.0`开始支持数组类型的菜单,可自定义键值,见下面说明。" close-on-clicking-mask: type: Boolean default: true version: v2.0.0 en: if closing actionsheet when clicking on mask zh-CN: 点击遮罩时是否关闭菜单,适用于一些进入页面时需要强制选择的场景。 close-on-clicking-menu: type: Boolean default: true version: v2.3.8 en: if hide automatically when clicking on menus zh-CN: 点击菜单时是否自动隐藏 slots: header: version: v2.3.5 en: header zh-CN: 头部位置 events: on-click-menu: params: (menuKey, menuItem) en: fires when clicking on the menu zh-CN: 点击菜单时触发 on-click-menu-{menuKey}: params: (menuKey) en: shortcut event for easier listening, `menuKey` is related to `label`. For exapmple, you can listen on `on-click-menu-delete` if you have a menu named `delete` zh-CN: 点击事件的快捷方式, `menuKey`与`label`的值有关。举例:如果你有一个菜单名字为`delete`, 那么你可以监听 `on-click-menu-delete` on-click-menu-cancel: en: fires when clicking on the cancel menu zh-CN: 点击取消菜单时触发 on-click-mask: version: v2.3.4 en: fires when clicking on mask zh-CN: 点击遮罩时触发 on-after-show: version: v2.9.0 en: fires when show transition ends zh-CN: 显示动画结束时触发 on-after-hide: version: v2.9.0 en: fires when hide transition ends zh-CN: 隐藏动画结束时触发 changes: v2.9.0: en: - '[feature] add event:on-after-show event:on-after-hide #2465' zh-CN: - '[feature] 支持 事件 on-after-show on-after-hide #2465' v2.7.4: en: - '[fix] fix wrong event param when value is zero #2209' zh-CN: - '[fix] 修复值为 0 时事件参数为空的问题 #2209' v2.5.6: en: - '[fix] Fix json parsing issue for cancel menu #1782' zh-CN: - '[fix] 修复取消菜单上的 json 解析错误 #1782' v2.5.5: en: - '[feature] Add raw menu item for on-click-menu event #1772' zh-CN: - '[feature] 支持在 on-click-menu 参数里附带原始 menu 对象 #1772' v2.3.5: en: - '[feature] Support slot:header #1381' zh-CN: - '[feature] 支持 slot:header #1381' v2.3.4: en: - '[feature] Support event: on-click-mask #1496' zh-CN: - '[feature] 支持遮罩点击事件 on-click-mask #1496' v2.2.0: en: - '[enhance] Donot update handel z-index in Safari when using v-transfer-dom' zh-CN: - '[enhance] 在使用 v-transfer-dom 的情况下不再处理 Safari 下的 z-index 问题' v2.1.1-rc.11: en: - '[enhance] Better transition for mask' zh-CN: - '[enhance] 更加流畅的遮罩层动画' v2.1.0: en: - '[feature] support array type of menu #950 @wuchuguang' zh-CN: - '[feature] 支持数组定义菜单 #950 @wuchuguang' v2.0.0: en: - '[change] upgrade to vue@2.0, use `v-model` instead of `:show.sync`' - '[feature] add prop close-on-clicking-mask' zh-CN: - '[change] 更新到 vue@2.0,使用 `v-model` 而不是`:show.sync`进行显示属性绑定' - '[feature] 添加属性 `close-on-clicking-mask`, 适用于强制选择的场景' ================================================ FILE: src/components/actionsheet/test.js ================================================ import Actionsheet from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Actionsheet', () => { it('should render correct contents', () => { const wrapper = mount(Actionsheet) expect(wrapper.contains('div')).to.equal(true) }) }) ================================================ FILE: src/components/agree/index.vue ================================================ ================================================ FILE: src/components/agree/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Agree', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('agree') }) }) ================================================ FILE: src/components/alert/index.vue ================================================ button_text: en: OK zh-CN: 确定 ================================================ FILE: src/components/alert/metas.yml ================================================ icon: '' category: en: Feedback zh-CN: 弹窗提示 extra: zh-CN: | 该组件支持以`plugin`形式调用: ::: warning 以插件形式调用时,和`template`中使用不同,属性名请使用`小驼峰式`,比如`buttonText`而不是`button-text`。

    在 `ViewBox` 或者 `overflow-scrolling:touch` 的容器中使用时,请使用 `prop:transfer-dom` 来解决其引起的 `z-index` 失效问题。详细参考 demo。 ::: ``` js import { AlertPlugin } from 'vux' Vue.use(AlertPlugin) // 或者umd方式 // 引入构建的js文件 Vue.use(vuxAlertPlugin) ``` ``` js // 显示 this.$vux.alert.show({ title: 'Vux is Cool', content: 'Do you agree?', onShow () { console.log('Plugin: I\'m showing') }, onHide () { console.log('Plugin: I\'m hiding') } }) // 隐藏 this.$vux.alert.hide() // 获取显示状态 this.$vux.alert.isVisible() // v2.9.1 支持 ``` ::: tip 如果你想实现在`vue-router`的`beforeEach`或者`afterEach`的 hook 里关闭,可以使用 `Vue.$vux.alert.hide()` ::: ::: tip 该组件在 v2.7.2 以上支持单独调用,方法和插件使用一致。 ::: ``` js import { AlertModule } from 'vux' AlertModule.show({ title: 'VUX is Cool', content: this.$t('Do you agree?'), onShow () { console.log('Module: I\'m showing') }, onHide () { console.log('Module: I\'m hiding now') } }) ``` en: | This component can be imported as plugin: ::: warning When importing it as plugin, the usage is different from it in `template`. Please use `little camel-case` such as `buttonText` instead of `button-text`. ::: ``` js import { AlertPlugin } from 'vux' Vue.use(AlertPlugin) // or the way of umd // import built js file Vue.use(vuxAlertPlugin) ``` ``` js // show this.$vux.alert.show({ title: 'Vux is Cool', content: 'Do you agree?', onShow () { console.log('Plugin: I\'m showing') }, onHide () { console.log('Plugin: I\'m hiding') } }) // hide this.$vux.alert.hide() ``` tags: en: - alert - dialog - message zh-CN: - 警告 - 提示 - 弹窗 extends: - Dialog when: - - en: Show some information that user should pay attention to and should be closed only after user has clicked the close button - zh-CN: 显示一个用户必须注意到并且必须点击按钮确认才能关闭的信息 props: value: type: Boolean default: false en: visibility of the component, use v-model for binding zh-CN: 是否显示, 使用 v-model 绑定变量 title: type: String default: '' en: title zh-CN: 弹窗标题 content: version: v2.2.0 type: String default: '' en: alert content, and will be replaced if using slot:default zh-CN: 提示内容,作为 slot:default 的默认内容,如果使用 slot:default, 将会失效 button-text: type: String default: ok(确定) en: button text zh-CN: 按钮文字 hide-on-blur: type: Boolean default: false en: if closing dialog when clicking on mask zh-CN: 是否在点击遮罩时自动关闭弹窗 mask-transition: type: String default: vux-fade en: mask transition zh-CN: 遮罩动画 dialog-transition: type: String default: vux-dialog en: dialog transition zh-CN: 弹窗主体动画 mask-z-index: version: v2.6.4 type: Number, String default: 1000 en: zIndex of mask zh-CN: 遮罩层 z-index 值 slots: default: en: dialog content zh-CN: 提示内容 events: on-show: en: emits when dialog shows zh-CN: 弹窗显示时触发 on-hide: en: emits when dialog is closed zh-CN: 弹窗关闭时触发 changes: v2.9.1: en: - '[feature] add isVisible function for AlertPlugin #2704' zh-CN: - '[feature] 添加 isVisible 获取当前显示状态 #2704' v2.7.2: en: - "[feature] now we can use Alert as a module (import { AlertModule } from 'vux') #2173" zh-CN: - "[feature] 支持作为模块直接调用 (import { AlertModule } from 'vux') #2173" v2.6.4: en: - '[feature] support prop:mask-z-index' zh-CN: - '[feature] 支持属性 mask-z-index 设置遮罩 z-index' v2.5.5: en: - '[feature] Support hideOnBlur #1742' zh-CN: - '[feature] 支持 hideOnBlur #1742' v2.2.0: en: - '[feature] Add prop:content' zh-CN: - '[feature] 添加属性 prop:content' v2.1.1-rc.11: en: - '[enhance] Reset props data when used as plugin' zh-CN: - '[enhance] 作为插件使用时,每次都重置属性值' v2.1.1-rc.9: en: - '[fix] Fix onHide callback issue #1023 @jsonviewer' zh-CN: - '[fix] 修正 onHide 回调被错误调用 #1023 @jsonviewer' v2.0.0: en: - '[change] use `v-model` instead of `:show.sync`' - '[todo] fix animation' zh-CN: - '[change] 使用 `v-model` 而不是`:show.sync`进行显示属性绑定' - '[todo] 修复动画' ================================================ FILE: src/components/alert/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Alert', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('alert') }) }) ================================================ FILE: src/components/badge/index.vue ================================================ ================================================ FILE: src/components/badge/metas.yml ================================================ description: | Badge是指通常出现在图标或文字右上角的红色圆点、数字或者文字,表示有新内容或者待处理的信息。中文一般称呼为小红点、角标或徽标。 - 强提醒的使用场景。如果只需让用户了解有更新或新内容必然只有一条时,Badge无需标注具体数字,单纯的红点即可,例如微信公众号更新和App版本更新采用无数字类型。 - 弱提醒的使用场景。如果需要让用户精确了解有多少条更新且新内容有多条,Badge可标注具体的数字,例如IM的未读消息、邮箱的未处理邮件。有数字的Badge给用户带来的心理压力会更大,也会更吸引用户注意力。 - 注意数字的长度和上限。对于有数字的Badge,由于界面显示空间有限,因此要注意视场景和信息类型决定数字最长显示多少位,如果数字达到上限该如何显示。 - 谨慎在设计图标时内运用红色圆点元素。红色小圆点这个视觉元素已经成为一种社会性语义符号,即用户看到红色小圆点就会认为这代表着有新内容,因此图标设计应当谨慎使用红色圆点。 category: en: Data Display zh-CN: 数据展示 icon: '' color: '#f74c31' references: zh-CN: - title: 这个控件叫:Badge/徽标/小红点 link: https://zhuanlan.zhihu.com/p/26107887 props: text: type: String default: '' en: text of the Badge zh-CN: 显示的文字 changes: v2.3.3: en: - '[feature] Use dot styles if no text is specified' zh-CN: - '[feature] 支持没有指定文字时为 红点 样式' v2.2.1-rc.2: en: - '[fix] Fix className:vux-badge-single not work for number #1223' zh-CN: - '[fix] 修复个位数字样式不正确 #1223' ================================================ FILE: src/components/badge/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Badge', () => { it('render single character', () => { const wrapper = mount(Comp) wrapper.setProps({ text: 'x' }) expect(wrapper.hasClass('vux-badge-single')).to.equal(true) }) it('render multiple character', () => { const wrapper = mount(Comp) wrapper.setProps({ text: 'xx' }) expect(wrapper.hasClass('vux-badge-single')).to.equal(false) }) }) ================================================ FILE: src/components/blur/blur.js ================================================ /* Image Blur plugin, author @msurguy Usage: Create a set of elements that follows the following HTML structure:
    ...
    Add the following css: .container { overflow: hidden width: 100% position: relative } .container .bg-blur-overlay { z-index: -1 position: absolute width: 100% height: 100% background-image: url('data:image/svg+xmlbase64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSI0NiUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC4wOCIvPjxzdG9wIG9mZnNldD0iNTklIiBzdG9wLWNvbG9yPSIjMDAwMDAwIiBzdG9wLW9wYWNpdHk9IjAuMDgiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMC45Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g') background-size: 100% background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(46%, rgba(0, 0, 0, 0.08)), color-stop(59%, rgba(0, 0, 0, 0.08)), color-stop(100%, rgba(0, 0, 0, 0.9))) background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.08) 46%, rgba(0, 0, 0, 0.08) 59%, rgba(0, 0, 0, 0.9) 100%) background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.08) 46%, rgba(0, 0, 0, 0.08) 59%, rgba(0, 0, 0, 0.9) 100%) background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.08) 46%, rgba(0, 0, 0, 0.08) 59%, rgba(0, 0, 0, 0.9) 100%) } .container .bg-blur { z-index: -2 opacity: 0 position: absolute width: 100% min-height: 100% height: auto display: block top: 0 left: 0 } .container .content { z-index: 1 } */ import Eventor from '../../libs/eventor' // Random ID generator var randomID = function () { return '_' + Math.random().toString(36).substr(2, 9) } // micro lib that creates SVG elements and adds attributes to it var SVG = { // namespaces svgns: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink', // creating of SVG element createElement (name, attrs) { var element = document.createElementNS(SVG.svgns, name) if (attrs) { SVG.setAttr(element, attrs) } return element }, // setting attributes setAttr (element, attrs) { for (var i in attrs) { if (i === 'href') { // path of an image should be stored as xlink:href attribute element.setAttributeNS(SVG.xlink, i, attrs[i]) } else { // other common attribute element.setAttribute(i, attrs[i]) } } return element } } // backgroundBlur PUBLIC CLASS DEFINITION // ================================ var Blur = function (element, options) { this.internalID = randomID() this.element = element this.width = element.offsetWidth this.height = element.offsetHeight this.element = element this.parent = this.element.parentNode this.options = Object.assign({}, Blur.DEFAULTS, options) this.overlayEl = this.createOverlay() this.blurredImage = null this.attachListeners() this.generateBlurredImage(this.options.url) } Blur.VERSION = '0.0.1' Eventor.mixTo(Blur) Blur.DEFAULTS = { url: '', // URL to the image blurAmount: 10, // Amount of blurrines imageClass: '', // CSS class that will be applied to the image and to the SVG element, overlayClass: '', // CSS class of the element that will overlay the blur image duration: false, // If the image needs to be faded in, how long should that take opacity: 1 // Specify the final opacity } Blur.prototype.setBlurAmount = function (blurAmount) { this.options.blurAmount = blurAmount } Blur.prototype.attachListeners = function () { this.on('ui.blur.loaded', this.fadeIn.bind(this)) this.on('ui.blur.unload', this.fadeOut.bind(this)) } Blur.prototype.fadeIn = function () { } Blur.prototype.fadeOut = function () { } Blur.prototype.generateBlurredImage = function (url) { const previousImage = this.blurredImage this.internalID = randomID() if (previousImage) { previousImage.parentNode.removeChild(previousImage) } this.blurredImage = this.createSVG(url, this.width, this.height) } Blur.prototype.createOverlay = function () { if (this.options.overlayClass && this.options.overlayClass !== '') { const div = document.createElement('div') div.classList.add(this.options.overlayClass) this.parent.insertBefore(div, this.element) return div } return false } Blur.prototype.createSVG = function (url, width, height) { var that = this var svg = SVG.createElement('svg', { // our SVG element xmlns: SVG.svgns, version: '1.1', width: width, height: height, id: 'blurred' + this.internalID, 'class': this.options.imageClass, viewBox: '0 0 ' + width + ' ' + height, preserveAspectRatio: 'none' }) var filterId = 'blur' + this.internalID // id of the filter that is called by image element var filter = SVG.createElement('filter', { // filter id: filterId }) var gaussianBlur = SVG.createElement('feGaussianBlur', { // gaussian blur element 'in': 'SourceGraphic', // "in" is keyword. Opera generates an error if we don't put quotes stdDeviation: this.options.blurAmount // intensity of blur }) var image = SVG.createElement('image', { // The image that uses the filter of blur x: 0, y: 0, width: width, height: height, 'externalResourcesRequired': 'true', href: url, style: 'filter:url(#' + filterId + ')', // filter link preserveAspectRatio: 'none' }) image.addEventListener('load', function () { that.emit('ui.blur.loaded') }, true) image.addEventListener('SVGLoad', function () { that.emit('ui.blur.loaded') }, true) filter.appendChild(gaussianBlur) // adding the element of blur into the element of filter svg.appendChild(filter) // adding the filter into the SVG svg.appendChild(image) // adding an element of an image into the SVG // Ensure that the image is shown after duration + 100 msec in case the SVG load event didn't fire or took too long if (that.options.duration && that.options.duration > 0) { svg.style.opacity = 0 window.setTimeout(function () { if (getStyle(svg, 'opacity') === '0') { svg.style.opacity = 1 } }, this.options.duration + 100) } this.element.insertBefore(svg, this.element.firstChild) return svg } Blur.prototype.createIMG = function (url, width, height) { var that = this var originalImage = this.prependImage(url) var newBlurAmount = ((this.options.blurAmount * 2) > 100) ? 100 : (this.options.blurAmount * 2) // apply special CSS attributes to the image to blur it const styles = { // filter property here the intensity of blur multipied by two is around equal to the intensity in common browsers. filter: 'progid:DXImageTransform.Microsoft.Blur(pixelradius=' + newBlurAmount + ') ', // aligning of the blurred image by vertical and horizontal top: -this.options.blurAmount * 2.5, left: -this.options.blurAmount * 2.5, width: width + (this.options.blurAmount * 2.5), height: height + (this.options.blurAmount * 2.5) } for (var i in styles) { originalImage.style[i] = styles[i] } originalImage.setAttribute('id', this.internalID) originalImage.onload = function () { that.trigger('ui.blur.loaded') } // Ensure that the image is shown after duration + 100 msec in case the image load event didn't fire or took too long if (this.options.duration && this.options.duration > 0) { window.setTimeout(function () { if (getStyle(originalImage, 'opacity') === '0') { originalImage.style.opacity = 1 } }, this.options.duration + 100) } return originalImage } Blur.prototype.prependImage = function (url) { const img = document.createElement('img') img.url = url img.setAttribute('id', this.internalID) img.classList.add(this.options.imageClass) if (this.overlayEl) { this.parent.insertBefore(img, this.overlayEl) } else { this.parent.insertBefore(img, this.parent.firstChild) } return img } export default Blur function getStyle (ele, prop) { return window.getComputedStyle(ele, null).getPropertyValue(prop) } ================================================ FILE: src/components/blur/index.vue ================================================ ================================================ FILE: src/components/blur/metas.yml ================================================ category: en: Deprecated 'zh-CN': 不再维护 icon: '' extra: zh-CN: | ::: tip 当前组件使用`svg`实现模糊效果。如果在手机上渲染过慢,可以尝试直接使用css3的`blur filter`。 ::: en: | ::: tip This component uses `svg` to implement blur. If rendering it in mobile devices is too slow, you can try to use a css3 featrue -- `blur filter`. ::: tags: en: - image - blur zh-CN: - 图片 - 模糊 - 高斯模糊 props: blur-amount: type: Number default: 10 en: blur amount of the effect zh-CN: 模糊程度 url: type: String default: '' en: url of the image zh-CN: 图片地址 height: type: Number default: 200 en: height of the container zh-CN: 容器高度 slots: default: en: content of the container, above the blur image zh-CN: 容器内容,显示在模糊内容上面 ================================================ FILE: src/components/blur/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Blur', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('blur') }) }) ================================================ FILE: src/components/box/index.vue ================================================ ================================================ FILE: src/components/box/metas.yml ================================================ category: en: Deprecated 'zh-CN': 不再维护 intro: en: a small component for lazy persons who don't want to write less code zh-CN: 为div设置margin值,懒人才需要的组件 props: gap: en: margin value zh-CN: margin值 ================================================ FILE: src/components/box/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Box', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('box') }) }) ================================================ FILE: src/components/button-tab/button-tab-item.vue ================================================ ================================================ FILE: src/components/button-tab/button-tab.vue ================================================ ================================================ FILE: src/components/button-tab/index.js ================================================ import ButtonTab from './button-tab' import ButtonTabItem from './button-tab-item' export { ButtonTab, ButtonTabItem } ================================================ FILE: src/components/button-tab/metas.yml ================================================ category: en: Navigation 'zh-CN': 导航 icon: '' import_code: | import { ButtonTab, ButtonTabItem } from 'vux' items: - button-tab - button-tab-item tags: en: - button - tab zh-CN: - 按钮 - 选项卡 button-tab: props: value: type: Number default: 0 en: 'current index, start from 0, use `v-model` for binding' zh-CN: '当前选中索引值,从0开始,使用 `v-model` 绑定' height: type: Number default: 30 en: height in px zh-CN: 高度值, 单位为像素 button-tab-item: props: selected: type: Boolean default: false en: if selected zh-CN: 是否选中 events: on-item-click: params: (index) en: emits when item is clicked zh-CN: 当前按钮点击时触发 changes: 2.7.6: en: - '[fix] fix border disappear bug #2322' zh-CN: - '[fix] 修复分割线会消失的bug #2322' v2.2.0: en: - '[enhance] Update border style(1px)' zh-CN: - '[enhance] 更新 1px 边框' v2.1.0-rc.46: zh-CN: - '[feature] 添加更多`less`变量 #896 @erguotou520' en: - '[feature] add more `less` variables #896 @erguotou520' v2.0.0: en: - '[enhance] remove tap highlight' zh-CN: - '[enhance] 去除 Android 下点击可能出现的蓝色边框' ================================================ FILE: src/components/button-tab/test.js ================================================ import Comp from './button-tab.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('ButtonTab', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('button-tab') }) }) ================================================ FILE: src/components/calendar/index.vue ================================================ cancel_text: en: cancel zh-CN: 取消 confirm_text: en: done zh-CN: 确定 ================================================ FILE: src/components/calendar/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: zh-CN: | ```html ``` ::: tip `calendar`只能在`Group`中使用
    除了`title`和 `value`, 其他`props`和`inline-calendar`完全一致。 ::: ::: tip 当绑定值为数组时,日历将为多选模式 ::: en: | ```html ``` ::: tip `calendar` can be only used in `Group`
    Except `title` and `value`, other `props` are the same as those of `inline-calendar`. ::: tags: en: - calendar - time - date zh-CN: - 日历 - 日期 extends: - inline-calendar - popup - cell intro: zh-CN: 扩展自inline-calendar, 相关属性可查看inline-calendar文档 en: extends from inline-calendar. Related props can be found in the document of inline-calendar props: value: type: String default: '' zh-CN: "表单值, v-model 绑定。当值为空时,为单选;当值为[]时,为多选;设置值为'TODAY'可快捷选取当前日期。`placeholder`只有在值为空或[]时显示。" en: form value, binding by v-model. Empty means single seleciton; [] means multiple selection. When the value is "TODAY", can quick pick current date. `placeholder` is valid only when the value is empty or []. title: type: String default: '' zh-CN: label文字 en: text of label placeholder: type: String en: placeholder zh-CN: 占位提示文字 show-popup-header: version: v2.6.0 type: Boolean en: whether show popup header. Show under multiple selection mode; hide under single selection mode. zh-CN: 是否显示弹窗头部,当为多选时强制显示,单选时默认不显示 popup-header-title: version: v2.6.0 type: String en: popup header title zh-CN: 弹窗头部文字 display-format: version: v2.6.4 type: Function params: '`(value, type)`' en: format value before displaying in cell zh-CN: 格式化显示值 readonly: version: v2.7.2 en: whether disable selecting zh-CN: 是否禁用弹窗选择 events: on-change: params: (value) en: emits when value changes zh-CN: 值改变时触发 on-show: en: emits when popup shows zh-CN: 弹窗显示时触发 on-hide: en: emits when popup hides zh-CN: 弹窗关闭时触发 changes: 2.7.6: en: - '[fix] fix no hiding after selecting the same value #2306' zh-CN: - '[fix] 修复选中同个日期时无法自动关闭的问题 #2306' v2.7.2: en: - '[feature] add prop:readonly #2178' - '[fix] fix event:on-change fires multiple times' zh-CN: - '[feature] 支持禁用选择属性 prop:readonly #2178' - '[fix] 修复 on-change 事件重复触发的问题' v2.7.0: en: - '[fix] fix top border #2104' zh-CN: - '[fix] 修复上边框 #2104' v2.6.4: en: - '[feature] support prop:display-format #2023' zh-CN: - '[feature] 支持格式化值显示 #2023' v2.6.0: en: - '[feature] support prop:show-popup-header' zh-CN: - '[feature] 支持显示弹窗头部(取消,确定)' v2.5.10: en: - '[feature] Support prop:placeholder' zh-CN: - '[feature] 支持 prop:placeholder' ================================================ FILE: src/components/calendar/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Calendar', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('calendar') }) }) ================================================ FILE: src/components/card/index.vue ================================================ ================================================ FILE: src/components/card/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' props: header.title: type: String default: '' zh-CN: 头部标题,不指定则不显示 en: title of header, not show without specify content footer.title: type: String default: '' zh-CN: 底部标题,不指定则不显示 en: title of footer, not show without specify content footer.link: type: String default: '' zh-CN: 底部链接,普通url或者v-link参数 en: link in the bottom, normal url or a v-link argument events: on-click-footer: zh-CN: 点击底部时触发 en: emits when clicking the footer on-click-header: zh-CN: 点击头部时触发 en: emits when clicking the header slots: header: zh-CN: 头部位置 en: header content: zh-CN: 中间主体位置 en: content, the main body part footer: zh-CN: 底部位置 en: footer ================================================ FILE: src/components/card/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Card', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('card') }) }) ================================================ FILE: src/components/cell/index.vue ================================================ ================================================ FILE: src/components/cell/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: zh-CN: | ```html ``` ::: tip Cell 组件只能在`Group`中使用 ::: tags: en: - cell - form zh-CN: - 表单 - 列表 props: title: type: String default: '' en: label text on the left zh-CN: 左边标题文字 value: type: String default: '' en: right text zh-CN: 右侧文字,复杂的样式布局请使用slot inline-desc: type: String default: '' en: the text below title zh-CN: 标题下面文字,一般为说明文字 link: type: String, Object en: redirect link. Can be http(s) protocol; can also be vue-router format zh-CN: 点击链接,可以为http(s)协议,也可以是 vue-router 支持的地址形式 is-link: type: Boolean default: false en: if this is a link; if true, there will be an arrow on the right zh-CN: 是否为链接,如果是,右侧将会出现指引点击的右箭头 primary: type: String default: 'title' en: "main content area, can be in ['title', 'content']" zh-CN: "可选值为 ['title', 'content'],对应的div会加上weui_cell_primary类名实现内容宽度自适应" is-loading: version: v2.2.0 type: Boolean default: false en: if show loading icon, good for asynchronous loading zh-CN: 是否显示加载图标,适用于异步加载数据的场景 value-align: version: v2.2.0 default: left en: value's text-align,one of left, right. prop:primary will be set to content if value-align value is right zh-CN: 文字值对齐方式,可选值为 left right。当设为 right 时,primary 值将会设为 content border-intent: version: v2.2.1-rc.1 type: Boolean default: true en: if set border intent on the left side zh-CN: 是否显示边框与左边的间隙 arrow-direction: version: v2.2.1-rc.1 type: String en: arrow direction, one of up, down zh-CN: 右侧箭头方向,可选有 up down disabled: version: v2.2.2 type: Boolean en: set disabled style for label and arrow (if is-link is true) zh-CN: 对 label 和箭头(如果使用 is-link )显示不可操作样式 align-items: version: v2.6.4 type: String default: center en: align-items value zh-CN: align-items 样式值 slots: default: en: "right area, you can use default slot instead of prop:value so you can use complicated layout" zh-CN: 右侧内容,相比于value的优点是可以用复杂的样式或者调用组件 value: en: "[deprecated] the same as default slot" zh-CN: "[废弃] 同默认slot" icon: en: icon area before title zh-CN: 标题左侧的图像位置 after-title: en: after title zh-CN: 标题右侧位置 child: en: the child element of the cell, you can add an element with absolute position zh-CN: cell的直接子元素,因此可以添加一个相对于cell绝对定位的元素 inline-desc: version: v2.2.1-rc.6 en: inline-desc slot, the same function as prop:inline-desc but can use html zh-CN: inline-desc 内容,和 prop:inline-desc 功能一样,但是可以使用 html title: version: v2.3.3 en: title slot zh-CN: title 插槽,方便自定义样式 changes: v2.9.0: en: - '[fix] fix weui-loading style' zh-CN: - '[fix] 修复 weui-loading 样式' v2.8.1: en: - '[fix] fix justify class #2545' zh-CN: - '[fix] 修复 justify 类名 #2545' v2.7.0: en: - '[fix] fix ssr rendering issue' zh-CN: - '[fix] 修复 ssr 渲染问题' v2.6.4: en: - '[feature] support prop:align-items' zh-CN: - '[feature] 支持属性 align-items' v2.3.6: en: - '[feature] Support arrow less variables' zh-CN: - '[feature] 支持多个箭头样式变量' v2.3.3: en: - '[feature] Support slot:title' zh-CN: - '[feature] 支持 slot:title' v2.2.2: en: - '[feature] Support prop:disabled' zh-CN: - '[feature] 支持 prop:disabled 显示禁用样式' v2.2.1-rc.6: en: - '[feature] Support slot:inline-desc' zh-CN: - '[feature] 支持 slot:inline-desc' v2.2.1-rc.1: en: - '[feature] Support prop:border-intent' - '[feature] Support prop:arrow-direction' zh-CN: - '[feature] 支持 prop:border-intent, 用以不显示边框与左边间隙' - '[feature] 支持 prop:arrow-direction, 用以在折叠场景下动态更改方向' v2.2.0: en: - '[feature] Support prop:is-loading' zh-CN: - '[feature] 支持 prop:is-loading 显示加载图标,适用于异步加载数据的场景' v2.1.1-rc.13: en: - '[fix] Support Group label styles #1110' zh-CN: - '[fix] 修复 label 宽度不受控于 Group #1110' v2.1.1-rc.3: en: - '[feature] Add font-size variable #990 @wg5945' zh-CN: - '[feature] 支持字体变量 #990 @wg5945' v2.1.0-rc.47: zh-CN: - '[enhance] 修复内容多时的箭头位置 #715 @greedying ' ================================================ FILE: src/components/cell/props.js ================================================ export default function () { return { title: [String, Number], value: [String, Number, Array], isLink: Boolean, isLoading: Boolean, inlineDesc: [String, Number], primary: { type: String, default: 'title' }, link: [String, Object], valueAlign: [String, Boolean, Number], borderIntent: { type: Boolean, default: true }, disabled: Boolean, arrowDirection: String, // down or up alignItems: String } } ================================================ FILE: src/components/cell/test/wrapper.vue ================================================ ================================================ FILE: src/components/cell/test.js ================================================ import CellWrapper from './test/wrapper' import Cell from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Cell.vue', () => { it('should render correct value', () => { const wrapper = mount(CellWrapper) wrapper.setProps({ value: 'hello' }) expect(wrapper.contains('div')).to.equal(true) }) it('prop:align-items', () => { const wrapper = mount(Cell, { propsData: { alignItems: 'flex-start' } }) expect(wrapper.hasStyle('-webkit-align-items', 'flex-start')).to.equal(true) }) it('prop:title', () => { const wrapper = mount(Cell, { propsData: { title: 'hello' } }) expect(wrapper.find('.vux-label').vnode.elm.innerText).to.equal('hello') }) }) ================================================ FILE: src/components/cell-box/index.vue ================================================ ================================================ FILE: src/components/cell-box/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: zh-CN: > ```html ``` ::: tip 与`cell`相比,`cell-box`更自由和灵活,只提供`is-link`和`link`属性,内容直接使用默认slot定义。
    `cell-box`同样只能在`Group`中使用 ::: tags: - cell - form props: is-link: type: Boolean default: false en: if this is a link; if true, there will be an arrow on the right zh-CN: 是否为链接,如果是,右侧将会出现指引点击的右箭头 link: type: String, Object en: redirect link. Can be http(s) protocol; can also be vue-router format zh-CN: 点击链接,可以为http(s)协议,也可以是 vue-router 支持的地址形式 border-intent: version: v2.2.1-rc.1 type: Boolean default: true en: if set border intent on the left side zh-CN: 是否显示边框与左边的间隙 align-items: version: v2.6.5 type: String default: center en: flexbox align-items setting zh-CN: flex 布局 align-items 设置 slots: default: en: content body zh-CN: 内容区域 changes: v2.6.5: en: - '[feature] add prop:align-items' zh-CN: - '[feature] 添加属性 align-items' v2.5.5: en: - "[change] Remove default slot's div wrapper #1769" zh-CN: - '[change] 移除默认 slot 的父级 div #1769' v2.2.1-rc.1: en: - '[feature] Support prop:border-intent' zh-CN: - '[feature] 支持 prop:border-intent, 用以不显示边框与左边间隙' v2.1.1-rc.1: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/cell-box/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('CellBox', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('cell-box') }) }) ================================================ FILE: src/components/cell-form-preview/index.vue ================================================ ================================================ FILE: src/components/cell-form-preview/metas.yml ================================================ category: en: Form 'zh-CN': 表单 name: cell-form-preview icon: '' tags: en: - form - cell-form-preview zh-CN: - 表单 - 预览 extra: | ::: warning `CellFormPreview` 需要在 `Group` 组件中使用。 ::: ``` html ``` ``` js import { CellFormPreview, Group, Cell } from 'vux' export default { components: { CellFormPreview, Group, Cell }, data () { return { list: [{ label: 'Apple', value: '3.29' }, { label: 'Banana', value: '1.04' }, { label: 'Fish', value: '8.00' }] } } } ``` slots: props: list: version: v2.2.0 type: Array en: label and value list zh-CN: 内容列表,键值包括`label`和`value`,可缺值 border-intent: version: v2.2.1-rc.1 type: Boolean default: true en: if set border intent on the left side zh-CN: 是否显示边框与左边的间隙 changes: v2.2.1-rc.1: en: - '[feature] Support prop:border-intent' zh-CN: - '[feature] 支持 prop:border-intent, 用以不显示边框与左边间隙' v2.2.0: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/cell-form-preview/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('CellFormPreview', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('cell-form-preview') }) }) ================================================ FILE: src/components/check-icon/index.vue ================================================ ================================================ FILE: src/components/check-icon/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - icon zh-CN: - 图标 props: value: type: Boolean version: v2.3.8 default: false en: if selected, use :value.sync for two-way data binding zh-CN: 是否选中,使用 :value.sync 双向绑定 type: type: String default: unknown en: "style of check icon, can be ['plain']" zh-CN: "check icon 样式,可选值为['plain']" ================================================ FILE: src/components/check-icon/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('CheckIcon', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('check-icon') }) }) ================================================ FILE: src/components/checker/checker-item.vue ================================================ ================================================ FILE: src/components/checker/checker.vue ================================================ ================================================ FILE: src/components/checker/index.js ================================================ import Checker from './checker' import CheckerItem from './checker-item' export { Checker, CheckerItem } ================================================ FILE: src/components/checker/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: en: | `Checker` is a more flexible component than `Radio` and `Checklist`. zh-CN: | `Checker` 是比`Radio`或者`Checklist`更加灵活的选择组件,可以自定义需要的布局样式。 items: - checker - checker-item tags: en: - form - checker - select zh-CN: - 表单 - 选择 checker: props: default-item-class: en: default checker-item classname zh-CN: 默认状态class selected-item-class: en: selected check-item classname zh-CN: 选中样式class disabled-item-class: en: disabled check-item classname zh-CN: 不可选样式class type: default: radio en: checker type, should be raido or checkbox zh-CN: 类型,单选为`radio`, 多选为`checkbox` value: type: String,Array en: selected value, use `v-model` for binding zh-CN: 表单值,使用`v-model`绑定 max: type: Number en: max selected number when type = checkbox zh-CN: 最多可选个数,多选时可用 radio-required: version: v2.6.3 type: Boolean default: false en: whether value is required in radio mode. if true, current selected item will always be selected even after clicking zh-CN: 在单选模式下是否必选一个值。设为 true 后点击当前选中项不会取消选中。 events: on-change: params: '`(value)`' en: emits when value is changed zh-CN: value值变化时触发 checker-item: props: value: en: value of current checker-item zh-CN: 当前项的值 disabled: type: Boolean default: false en: if current item is disabled zh-CN: 是否为不可选 events: on-item-click: params: '`(itemValue, itemDisabled)`' en: emits when current checker-item is clicked zh-CN: 当前项被点击时触发 changes: v2.6.3: en: - '[feature] support prop:radio-required #2004' zh-CN: - '[feature] 支持属性 radio-required 强制单选模式下必选一项 #2004' v2.6.0: en: - '[fix] Fix event:on-change do not fire when using :value binding instead of v-model #1945' zh-CN: - '[fix] 修复 on-change 事件 在使用 :value 绑定的情况下不触发 #1945' v2.3.8: en: - '[enhance] clear value when selected item is disabled #1479' zh-CN: - '[enhance] 当前选中 item 被 disabled 时清除值 #1479' v2.3.2: en: - '[enhance] Support unselecting current item when clicking again in radio mode #1468' zh-CN: - '[enhance] 支持在单选的情况下第二次点击时变成不选中当前项 #1468' v2.1.1-rc.14: en: - '[enhance] Now disabled item also emits event:on-item-click #1144' zh-CN: - '[enhance] 不可选项同样触发事件 on-item-click #1144' v2.0.0: en: - '[feature] Support Object value #705' zh-CN: - '[feature] 支持Object类型的值 #705' ================================================ FILE: src/components/checklist/index.vue ================================================ ================================================ FILE: src/components/checklist/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - checklist - select - form extra: | ::: tip 从 v2.6.2 开始,max=1即为单选模式,不会要求先取消上一个选中的才能选中下一个值。 ::: props: value: type: Array default: [] en: form value zh-CN: 表单值 title: type: String default: '' en: title zh-CN: 标题 required: type: Boolean default: false en: if required zh-CN: 是否为必选 options: type: Array default: [] en: options list zh-CN: 选项列表,可以为`[{key:'name',value:'value',inlineDesc:'inlineDesc'}]`的形式 max: type: Number default: '' en: max selected number zh-CN: 最多可选个数 min: type: Number default: '' en: min selected number zh-CN: 最少可选个数 random-order: type: Boolean default: false en: if place options in a random order zh-CN: 是否随机打乱选项顺序 check-disabled: version: v2.2.1-rc.1 type: Boolean default: true en: if disabled item when selectedItems.length === total.length, by using this prop:max no longer works zh-CN: 是否进行可选检测,默认情况下当选择个数等于可选个数(max)时,其他项不可选择。该选项主要适用于从多个选项列表中收集值的场景。注意的该选项设为 false 时 max 设置将失效。 label-position: version: v2.2.1-rc.4 type: String default: right en: label position, can be set to left or right zh-CN: label 位置,可以设置为 left 或者 right disabled: version: v2.3.8 en: if disable user from selecting zh-CN: 是否禁用操作 methods: getFullValue: version: v2.6.4 en: get current value with label zh-CN: 获取值和对应的显示文字 events: on-change: params: '(value, label)' en: emits when value changes, param:label is supported after v2.5.7 zh-CN: 值变化时触发,参数为 (value, label),其中 label 参数在 v2.5.7 后支持 changes: v2.7.0: en: - '[fix] fix default selected item missing issue #2122' zh-CN: - '[fix] 修复默认选中项缺失问题 #2122' v2.6.5: en: - '[fix] fix missing icon style when using checklist along #2041' - '[fix] initializing the tempValue when created #2059' zh-CN: - '[fix] 修复仅调用 checklist 组件时图标样式不存在 #2041' - '[fix] 在created时,初始化tempValue值 #2059' v2.6.4: en: - '[feature] support method:getFullValue #2022' zh-CN: - '[feature] 添加方法 getFullValue 获取值和对应的显示文字 #2022' v2.6.3: en: - '[fix] now on-change params has labels as second param #2018' zh-CN: - '[fix] on-change 参数支持 labels #2018' v2.6.2: en: - '[feature] Support radio mode(max = 1) #1996' zh-CN: - '[feature] 支持 max=1 交互为单选模式 #1996' v2.5.7: en: - '[feature] Support `label` param for on-change event #1783' zh-CN: - '[feature] 支持在 on-change 事件中附带 `label` 值 #1783' v2.3.8: en: - '[feature] Add prop:disabled #1254' zh-CN: - '[feature] 支持 prop:disabled 禁用操作 #1254' v2.2.1-rc.4: en: - '[feature] Support prop:label-position' zh-CN: - '[feature] 支持通过 prop:label-position 设置 label位置' v2.2.1-rc.1: en: - '[feature] Support prop:check-disabled' zh-CN: - '[feature] 支持 prop:check-disabled' v2.0.0: en: - '[change] it does not show errors by default, handle errors by on-error events' - '[feature] add slot:footer slot:after-title' - '[change] if value.length === max, the left items will be disabled' - '[change] default required value is false' zh-CN: - '[change] 默认不显示错误,你可以监听on-error事件结合slot进行处理和显示' - '[feature] 添加插槽 footer、after-title' - '[change] 如果已经达到max上限,没有选中的选项将不能选择,因此不再和之前版本一样会出现最多可选max个的error信息' - '[change] 默认 required 值为false, 与html规范一致' v2.5.2: en: - '[fix] emit event `input` before `on-change`' - '[feature] add attribute `inlineDesc` to `options`' zh-CN: - '[fix] 在 `on-change` 事件之前 emit `input` 事件' - '[feature] `options` 中增加 `inlineDesc` 属性' ================================================ FILE: src/components/checklist/object-filter.js ================================================ export const getValue = function (item) { return typeof item === 'object' ? item.value : item } export const getKey = function (item) { return typeof item === 'object' ? item.key : item } export const getInlineDesc = function (item) { return typeof item === 'object' ? item.inlineDesc : '' } export const getLabel = function (list = [], value) { if (!list.length) { return value } if (typeof list[0] === 'string') { return value } const match = list.filter(one => { return one.key === value }) if (match.length) { return match[0].value || match[0].label } return value } export const getLabels = function (list = [], values = []) { return values.map(value => getLabel(list, value)) } ================================================ FILE: src/components/checklist/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Checklist', () => { it('basic', () => { const wrapper = mount(Comp, { propsData: { options: [] } }) expect(wrapper.name()).to.equal('checklist') }) }) ================================================ FILE: src/components/clocker/clocker.js ================================================ import Eventor from '../../libs/eventor' // https://github.com/MoeKit/clocker var instances = [] var matchers = [] // Miliseconds matchers.push(/^[0-9]*$/.source) // Month/Day/Year [hours:minutes:seconds] matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/ .source) // Year/Day/Month [hours:minutes:seconds] and // Year-Day-Month [hours:minutes:seconds] matchers.push(/[0-9]{4}([/-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/ .source) // Cast the matchers to a regular expression object matchers = new RegExp(matchers.join('|')) // Parse a Date formatted has String to a native object function parseDateString (dateString) { // Pass through when a native object is sent if (dateString instanceof Date) { return dateString } // Caste string to date object if (String(dateString).match(matchers)) { // If looks like a milisecond value cast to number before // final casting (Thanks to @msigley) if (String(dateString).match(/^[0-9]*$/)) { dateString = Number(dateString) } // Replace dashes to slashes if (String(dateString).match(/-/)) { dateString = String(dateString).replace(/-/g, '/') } return new Date(dateString) } else { throw new Error('Couldn\'t cast `' + dateString + '` to a date object.') } } // Map to convert from a directive to offset object property var DIRECTIVE_KEY_MAP = { 'Y': 'years', 'm': 'months', 'w': 'weeks', 'D': 'days', 'H': 'hours', 'M': 'minutes', 'S': 'seconds' } // Returns an escaped regexp from the string function escapedRegExp (str) { var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1') return new RegExp(sanitize) } // Time string formatter function strftime (offsetObject) { return function (format) { var directives = format.match(/%(-|!)?[A-Z]{1}(:[^]+)?/gi) var d2h = false if (directives.indexOf('%D') < 0 && directives.indexOf('%H') >= 0) { d2h = true } if (directives) { for (var i = 0, len = directives.length; i < len; ++i) { var directive = directives[i].match(/%(-|!)?([a-zA-Z]{1})(:[^]+)?/) var regexp = escapedRegExp(directive[0]) var modifier = directive[1] || '' var plural = directive[3] || '' var value = null var key = null // Get the key directive = directive[2] // Swap shot-versions directives if (DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) { key = DIRECTIVE_KEY_MAP[directive] value = Number(offsetObject[key]) if (key === 'hours' && d2h) { value += Number(offsetObject['days']) * 24 } } if (value !== null) { // Pluralize if (modifier === '!') { value = pluralize(plural, value) } // Add zero-padding if (modifier === '') { if (value < 10) { value = '0' + value.toString() } } // Replace the directive format = format.replace(regexp, value.toString()) } } } format = format.replace('%_M1', offsetObject.minutes_1) .replace('%_M2', offsetObject.minutes_2) .replace('%_S1', offsetObject.seconds_1) .replace('%_S2', offsetObject.seconds_2) .replace('%_S3', offsetObject.seconds_3) .replace('%_H1', offsetObject.hours_1) .replace('%_H2', offsetObject.hours_2) .replace('%_H3', offsetObject.hours_3) .replace('%_D1', offsetObject.days_1) .replace('%_D2', offsetObject.days_2) .replace('%_D3', offsetObject.days_3) format = format.replace(/%%/, '%') return format } } // Pluralize function pluralize (format, count) { var plural = 's' var singular = '' if (format) { format = format.replace(/(:||\s)/gi, '').split(/,/) if (format.length === 1) { plural = format[0] } else { singular = format[0] plural = format[1] } } if (Math.abs(count) === 1) { return singular } else { return plural } } function splitNumber (number) { number = number + '' number = (number.length === 1 ? ('0' + number) : number) + '' return number.split('') } // The Final Countdown var Countdown = function (finalDate, option) { option = option || {} this.PRECISION = option.precision || 100 // 0.1 seconds, used to update the DOM this.interval = null this.offset = {} // Register this instance this.instanceNumber = instances.length instances.push(this) // Set the final date and start this.setFinalDate(finalDate) } Eventor.mixTo(Countdown) var pro = Countdown.prototype var fns = { start () { if (this.interval !== null) { clearInterval(this.interval) } var self = this this.update() this.interval = setInterval(function () { self.update() }, this.PRECISION) return this }, stop () { clearInterval(this.interval) this.interval = null this._dispatchEvent('stoped') return this }, toggle () { if (this.interval) { this.stop() } else { this.start() } return this }, pause () { return this.stop() }, resume () { return this.start() }, remove () { this.stop() instances[this.instanceNumber] = null }, setFinalDate (value) { this.finalDate = parseDateString(value) // Cast the given date return this }, getOffset () { this.totalSecsLeft = this.finalDate.getTime() - new Date().getTime() // In miliseconds this.totalSecsLeft = Math.ceil(this.totalSecsLeft / 1000) this.totalSecsLeft = this.totalSecsLeft < 0 ? 0 : this.totalSecsLeft // Calculate the offsets return { seconds: this.totalSecsLeft % 60, minutes: Math.floor(this.totalSecsLeft / 60) % 60, hours: Math.floor(this.totalSecsLeft / 60 / 60) % 24, days: Math.floor(this.totalSecsLeft / 60 / 60 / 24), weeks: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7), months: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30), years: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 365) } }, update () { // Calculate the offsets this.offset = this.getOffset() // split offset only for days, hours, minutes, seconds and two number like 45, do not support 100 var list = ['days', 'hours', 'minutes', 'seconds'] for (var i = 0; i < list.length; i++) { var key = list[i] var numbers = splitNumber(this.offset[key]) for (let n = 0; n < numbers.length; n++) { this.offset[`${key}_${n + 1}`] = numbers[n] } } // Dispatch an event if (this.totalSecsLeft === 0) { this.stop() this._dispatchEvent('finish') } else { this._dispatchEvent('update') } return this }, _dispatchEvent (eventName) { var event = {} event.finalDate = this.finalDate event.offset = this.offset event.strftime = strftime(this.offset) this.emit(eventName, event) this.emit('tick', event) } } for (var i in fns) { pro[i] = fns[i] } export default Countdown ================================================ FILE: src/components/clocker/index.vue ================================================ ================================================ FILE: src/components/clocker/metas.yml ================================================ category: en: Data Display 'zh-CN': 数据展示 icon: '' tags: en: - time - countdown zh-CN: - 倒计时 - 时间 props: time: type: String default: '' en: the end time zh-CN: 结束时间 format: type: String default: '%D 天 %H 小时 %M 分 %S 秒' en: the result format zh-CN: 显示格式 events: on-tick: en: triggers on time ticking zh-CN: 时间计算时触发,但非精确每1s触发 on-finish: en: triggers on time end zh-CN: 时间结束时触发 slots: default: en: if specified, will be the format for the result zh-CN: 若存在,则作为最终显示出来的格式模板 changes: v2.7.9: en: - '[fix] fix bug when initial value is empty string #2449' zh-CN: - '[fix] 修复更新值为非空时报错问题 #2449' v2.7.5: en: - '[fix] fix this.clocker maybe undefined on hook:beforeDestroy #2258' zh-CN: - '[fix] 修复 beforeDestroy 调用时 this.clocker 不存在 #2258' ================================================ FILE: src/components/clocker/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Clocker', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('clocker') }) }) ================================================ FILE: src/components/color-picker/index.vue ================================================ ================================================ FILE: src/components/color-picker/metas.yml ================================================ category: en: Deprecated 'zh-CN': 不再维护 category_order: 999 status: deprecated deprecated_info: en: use cell and slot instead zh-CN: 请参考源码直接使用cell+slot来实现 icon: '' color: '#f2c400' tags: - color - picker - 颜色 props: value: type: String default: '' en: form value zh-CN: 表单值 colors: type: Array default: [] en: color list zh-CN: 可选颜色列表 size: type: String default: large en: 'button size, enum: large, middle, small' zh-CN: 按钮大小,可选值 large, middle, small changes: v2.0.0: zh-CN: - '[deprecated] 废弃,当前版本后不再继续维护' ================================================ FILE: src/components/color-picker/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('ColorPicker', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('color-picker') }) }) ================================================ FILE: src/components/confirm/index.vue ================================================ confirm_text: en: confirm zh-CN: 确定 cancel_text: en: cancel zh-CN: 取消 ================================================ FILE: src/components/confirm/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - dialog - message - confirm zh-CN: - 弹窗 - 消息提示 - 确认 extra: | ::: tip 建议参照例子使用 `v-transfer-dom` 将弹窗插入 `body` 节点下,避免组件非 body 子节点导致的各种问题。 ::: 该组件支持以`plugin`形式调用: ::: warning 以插件形式调用时,和`template`中使用不同,属性名请使用`小驼峰式`,比如`confirmText`而不是`confirm-text`。 ::: ``` js import { ConfirmPlugin } from 'vux' Vue.use(ConfirmPlugin) ``` ``` js // 显示 this.$vux.confirm.show({ // 组件除show外的属性 onCancel : () => { console.log(this) //当前 vm }, onConfirm : () => {} }) // 隐藏 this.$vux.confirm.hide() // prompt形式调用 this.$vux.confirm.prompt('placeholder', { onCancel : () => {} onConfirm : () =>{} }) // 设置输入值 this.$vux.confirm.setInputValue('value') // 注意需要在 onShow 事件中执行 // 获取显示状态 this.$vux.confirm.isVisible() // v2.9.1 支持 ``` props: value: type: Boolean default: false en: visibility of the component zh-CN: 是否显示,使用`v-model`绑定 show-input: version: v2.5.0 type: Boolean default: false en: whether show input or not. If true, slot will not work zh-CN: 是否显示输入框,如果为true,slot会失效 placeholder: version: v2.5.0 type: String default: '' en: placeholder of prompt, valid when show-input is true zh-CN: 输入框的提示(仅在showInput为true的情况下有效) theme: type: String default: ios en: dialog's style, can be ios or android zh-CN: 弹窗风格,可以是ios或android enum: - ios - android hide-on-blur: type: Boolean default: false en: if closing dialog when clicking on mask zh-CN: 是否在点击遮罩时自动关闭弹窗 title: en: dialog title zh-CN: 弹窗标题 content: type: String en: dialog content, can use html. Invalid when using slot zh-CN: 弹窗内容,作为slot默认内容,可以是html片段,如果使用slot该字段会失效 confirm-text: default: 确认(confirm) en: text of confirm button zh-CN: 确认按钮的显示文字 cancel-text: default: 取消(cancel) en: text of cancel button zh-CN: 取消按钮的显示文字 mask-transition: default: 'vux-fade' en: mask's transition zh-CN: 遮罩动画 dialog-transition: default: 'vux-dialog' en: dialog's transition zh-CN: 弹窗动画 close-on-confirm: version: v2.5.0 type: Boolean default: true en: whether close automatically when confirm button is clicked zh-CN: 是否在点击确认按钮时自动关闭 input-attrs: version: v2.5.7 type: Object default: {type: 'text'} en: input attributes zh-CN: input 属性 mask-z-index: version: v2.6.4 type: Number, String default: 1000 en: zIndex of mask zh-CN: 遮罩层 z-index 值 show-cancel-button: version: v2.9.2 type: Boolean default: true en: if show cancel button zh-CN: 是否显示取消按钮 show-confirm-button: version: v2.9.2 type: Boolean default: true en: if show confirm button zh-CN: 是否显示确定按钮 slots: default: en: body content of the dialog zh-CN: 弹窗主体内容 events: on-cancel: en: triggers when the cancel button is clicked zh-CN: 点击取消按钮时触发 on-confirm: params: '(value)' en: triggers when the confirm button is clicked zh-CN: 点击确定按钮时触发, 参数为prompt中输入的值 on-show: en: triggers when the dialog shows zh-CN: 弹窗出现时触发 on-hide: en: triggers when the dialog hides zh-CN: 弹窗隐藏时触发 methods: setInputValue: version: v2.5.5 params: '(value)' en: set input value when show-input is true zh-CN: 设置输入值,当 show-input 为 true 时有效 changes: v2.9.2: en: - '[feature] Support prop: showCancelButton showConfirmButton #2795' zh-CN: - '[feature] 支持 prop: showCancelButton showConfirmButton #2795' v2.9.1: en: - '[feature] add isVisible function for ConfirmPlugin #2704' zh-CN: - '[feature] 添加 isVisible 获取当前显示状态 #2704' v2.9.0: en: - '[fix] fix inputAttrs issue with no-default-value #2618' - '[fix] fix focus auto-blur issue #2610' zh-CN: - '[fix] 修复 inputAttrs 无默认值导致的报错 #2618' - '[fix] 修复输入框获取焦点后又失去焦点 #2610' v2.8.0: en: - '[fix] fix hard to move cursor' zh-CN: - '[fix] 修复输入框难以移动光标' v2.7.3: en: - '[fix] fix hard to get focus on input(thanks to @Sapphire2k)' zh-CN: - '[fix] 修复输入框难以获取 focus 的问题(感谢 @Sapphire2k)' v2.6.5: en: - '[enhance] fix click events trigger twice when clicking fast #2048' zh-CN: - '[enhance] 修复快速点击按钮时触发多次的问题 #2048' v2.6.4: en: - '[feature] support prop:mask-z-index' zh-CN: - '[feature] 支持属性 mask-z-index 设置遮罩 z-index' v2.5.8: en: - '[feature] Support method:setInputValue for plugin #1846' zh-CN: - '[feature] 插件支持 setInputValue 方法 #1846' v2.5.7: en: - '[feature] Support prop:input-attrs #1799' zh-CN: - '[feature] 支持 prop:input-attrs 设置额外属性 #1799' v2.5.5: en: - '[feature] Add method:setInputValue #1746' zh-CN: - '[feature] 支持方法 setInputValue 设置输入值 #1746' v2.5.0: en: - '[feature] Support prop:close-on-confirm' - '[feature] Support prop:show-input' zh-CN: - '[feature] 支持 prop:close-on-confirm' - '[feature] 支持 prop:show-input' v2.1.1-rc.11: en: - '[fix] Fix plugin value and event watchers' - '[fix] Reset prop data when used as plugin' zh-CN: - '[fix] 修复插件代码里的值和事件监听器' - '[fix] 以插件方式显示时强制重置数据' ================================================ FILE: src/components/confirm/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Confirm', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('confirm') }) }) ================================================ FILE: src/components/countdown/index.vue ================================================ ================================================ FILE: src/components/countdown/metas.yml ================================================ category: en: Deprecated 'zh-CN': 不再维护 icon: '' status: 'deprecated' tags: en: - number - countdown zh-CN: - 数字 - 倒数 props: value: type: number en: total sections zh-CN: 时间,秒为单位 start: type: Boolean default: true en: if start counting down zh-CN: 是否开始计数 changes: v2.0.0: en: - '[deprecated] This component is no longer maintained' zh-CN: - '[deprecated] 下一版本开始不再维护' ================================================ FILE: src/components/countdown/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('CountDown', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('countdown') }) }) ================================================ FILE: src/components/countup/index.vue ================================================ ================================================ FILE: src/components/countup/metas.yml ================================================ category: en: Data Display 'zh-CN': 数据展示 icon: '' tags: en: - countup - digit - number zh-CN: - 数字 - 滚动 props: start-val: type: Number default: 0 en: min number zh-CN: 开始数字 end-val: type: Number en: max number zh-CN: 结束数字 decimals: type: Number default: 0 en: decimal length zh-CN: 小数点位数 duration: type: Number default: 2 en: duration value (second) zh-CN: 耗时(秒) options: type: 'Object' en: options for `countup.js` zh-CN: '`countup.js`的设置项' start: type: Boolean default: true en: if start counting automatically zh-CN: 是否自动开始计数 tag: version: v2.5.5 type: String default: span en: render tag zh-CN: 渲染标签 changes: v2.5.5: en: - '[feature] Add prop:tag for customize tag #1739' zh-CN: - '[feature] 添加属性 tag 用以定义渲染标签 #1739' v2.0.0: en: - '[feature] Add prop:start' - '[change] Update countup -> countup.js' zh-CN: - '[feature] 添加 prop:`start`, 现在你可以手动开始计数了' - '[change] 更新依赖`countup`为`countup.js`' ================================================ FILE: src/components/countup/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Countup', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('countup') }) }) ================================================ FILE: src/components/datetime/datetimepicker.js ================================================ import Scroller from '../picker/scroller' import { isToday, generateRange, each, trimZero, addZero, getMaxDay, parseRow, parseDate, getElement, toElement, removeElement } from './util' import { getYears, getMonths, getDays } from './makeData' const isBrowser = typeof window === 'object' const MASK_TEMPLATE = '
    ' const TEMPLATE = `
    cancel
    done
    ` const SHOW_ANIMATION_TIME = 200 const SHOW_CONTAINER_TIME = 300 const TYPE_MAP = { year: ['YYYY'], month: ['MM', 'M'], day: ['DD', 'D'], hour: ['HH', 'H'], minute: ['mm', 'm'], noon: ['A'] } let MASK = null let CURRENT_PICKER const NOW = new Date() const DEFAULT_CONFIG = { template: TEMPLATE, trigger: null, output: null, currentYear: NOW.getFullYear(), currentMonth: NOW.getMonth() + 1, minYear: 2000, maxYear: 2030, minHour: 0, maxHour: 23, hourList: null, minuteList: null, startDate: null, endDate: null, yearRow: '{value}', monthRow: '{value}', dayRow: '{value}', noonRow: '{value}', hourRow: '{value}', minuteRow: '{value}', format: 'YYYY-MM-DD', value: NOW.getFullYear() + '-' + (NOW.getMonth() + 1) + '-' + NOW.getDate(), onSelect () {}, onConfirm () {}, onClear () {}, onShow () {}, onHide () {}, confirmText: 'ok', clearText: '', cancelText: 'cancel', destroyOnHide: false, renderInline: false, computeHoursFunction: null, computeDaysFunction: null, isOneInstance: false, orderMap: {} } function renderScroller (el, data, value, fn) { data = data.map(one => { one.value = one.value + '' return one }) return new Scroller(el, { data, defaultValue: value + '', onSelect: fn }) } function showMask () { if (!isBrowser) { return } if (!MASK) { MASK = toElement(MASK_TEMPLATE) document.body.appendChild(MASK) MASK.addEventListener('click', function () { CURRENT_PICKER && CURRENT_PICKER.hide('cancel') }, false) MASK.addEventListener('touchmove', function (e) { e.preventDefault() }, false) } MASK.style.display = 'block' setTimeout(function () { MASK && (MASK.style.opacity = 0.5) }, 0) } function hideMask () { if (!MASK) { return } MASK.style.opacity = 0 setTimeout(function () { MASK && (MASK.style.display = 'none') }, SHOW_ANIMATION_TIME) } function DatetimePicker (config) { const self = this self.config = {} self.value = config.value || '' each(DEFAULT_CONFIG, function (key, val) { self.config[key] = config[key] || val }) this.renderInline = self.config.renderInline if (config.defaultSelectedValue && !config.value) { self.config.value = config.defaultSelectedValue } if (typeof this.config.startDate === 'string') { this.config.startDate = new Date(this.config.startDate.replace(/-/g, '/')) } if (typeof this.config.endDate === 'string') { this.config.endDate = new Date(this.config.endDate.replace(/-/g, '/')) } if (this.config.startDate && !this.config.endDate) { this.config.endDate = new Date('2030/12/31') } if (!this.config.startDate && this.config.endDate) { this.config.startDate = new Date(`${this.config.minYear}/01/01`) } this.reMakeData = !!this.config.startDate && !!this.config.endDate if (!this.renderInline) { let trigger = self.config.trigger this.triggerHandler = function (e) { e.preventDefault() self.show(self.value) } if (trigger && isBrowser) { trigger = self.trigger = getElement(trigger) this.trigger = trigger this.trigger && this.trigger.addEventListener('click', this.triggerHandler, false) } } } DatetimePicker.prototype = { _show (newValueMap) { const self = this self._setText() self.container.style.display = 'block' if (this.renderInline) { self.container.classList.add('vux-datetime-view') } each(TYPE_MAP, function (type) { self[type + 'Scroller'] && self[type + 'Scroller'].select(type === 'noon' ? newValueMap[type] : trimZero(newValueMap[type]), false) }) setTimeout(function () { self.container.style['-webkit-transform'] = 'translateY(0)' self.container.style.transform = 'translateY(0)' }, 0) }, show (value) { if (!isBrowser) { return } const self = this const config = self.config if (config.isOneInstance) { if (document.querySelector('#vux-datetime-instance')) { return } self.willShow = true } CURRENT_PICKER = self const valueMap = self.valueMap = parseDate(config.format, value || config.value) let newValueMap = {} each(TYPE_MAP, function (type, list) { newValueMap[type] = list.length === 1 ? valueMap[list[0]] : (valueMap[list[0]] || valueMap[list[1]]) }) if (self.container) { self._show(newValueMap) } else { let template = config.template for (let i in config.orderMap) { template = template.replace(`data-role="${i}"`, `data-role="${i}" style="order:${config.orderMap[i]}"`) } const container = self.container = toElement(template) if (config.isOneInstance) { container.id = 'vux-datetime-instance' } if (!self.renderInline) { document.body.appendChild(container) self.container.style.display = 'block' } else { document.querySelector(self.config.trigger).appendChild(container) } each(TYPE_MAP, function (type) { const div = self.find('[data-role=' + type + ']') if (newValueMap[type] === undefined) { removeElement(div) return } let data if (type === 'day') { data = self._makeData(type, trimZero(newValueMap.year), trimZero(newValueMap.month)) } else if (type === 'hour') { data = self._makeData(type, trimZero(newValueMap.year), trimZero(newValueMap.month), trimZero(newValueMap.day)) } else { data = self._makeData(type) } self[type + 'Scroller'] = renderScroller(div, data, trimZero(newValueMap[type]), function (currentValue) { setTimeout(function () { config.onSelect.call(self, type, currentValue, self.getValue()) }, 0) if (type === 'year' || type === 'month' || type === 'day') { self.hourScroller && self._setHourScroller(self.yearScroller.value, self.monthScroller.value, self.dayScroller.value, self.hourScroller.value) } let currentDay if (type === 'year') { const currentMonth = self.monthScroller ? self.monthScroller.value : config.currentMonth self._setMonthScroller(currentValue, currentMonth) if (self.dayScroller) { currentDay = self.dayScroller.value self._setDayScroller(currentValue, currentMonth, currentDay) } } else if (type === 'month') { const currentYear = self.yearScroller ? self.yearScroller.value : config.currentYear if (self.dayScroller) { currentDay = self.dayScroller.value self._setDayScroller(currentYear, currentValue, currentDay) } } }) }) if (!self.renderText && !self.renderInline) { if (self.config.confirmText) { self.find('[data-role=confirm]').innerText = self.config.confirmText } if (self.config.cancelText) { self.find('[data-role=cancel]').innerText = self.config.cancelText } if (self.config.clearText) { self.find('[data-role=clear]').innerText = self.config.clearText } self.renderText = true } this._show(newValueMap) self.find('[data-role=cancel]').addEventListener('click', function (e) { e.preventDefault() self.hide('cancel') }, false) self.find('[data-role=confirm]').addEventListener('click', function (e) { e.preventDefault() self.confirm() }, false) if (self.config.clearText) { self.find('[data-role=clear]').addEventListener('click', function (e) { e.preventDefault() self.clear() }, false) } } if (!this.renderInline) { showMask() config.onShow.call(self) } }, _setText () { if (typeof V_LOCALE !== 'undefined' && V_LOCALE === 'MULTI' && !this.config.renderInline) { // eslint-disable-line const trigger = this.trigger if (trigger) { const confirmText = trigger.getAttribute('data-confirm-text') const cancelText = trigger.getAttribute('data-cancel-text') this.find('[data-role=confirm]').innerText = confirmText this.find('[data-role=cancel]').innerText = cancelText } } }, _makeData (type, year, month, day) { const config = this.config const valueMap = this.valueMap const list = TYPE_MAP[type] let data = [] let min let max if (type === 'year') { min = config.minYear max = config.maxYear if (this.reMakeData) { const { minYear, maxYear } = getYears(this.config.startDate, this.config.endDate) min = minYear max = maxYear } } else if (type === 'month') { min = 1 max = 12 if (this.reMakeData) { const { minMonth, maxMonth } = getMonths(this.config.startDate, this.config.endDate, this.yearScroller.value * 1) min = Math.max(min, minMonth) max = Math.min(max, maxMonth) } } else if (type === 'day') { min = 1 max = getMaxDay(year, month) if (this.reMakeData) { const { minDay, maxDay } = getDays(this.config.startDate, this.config.endDate, this.yearScroller.value * 1, this.monthScroller.value * 1) min = Math.max(min, minDay) max = Math.min(max, maxDay) } } else if (type === 'hour') { min = this.config.minHour max = this.config.maxHour } else if (type === 'minute') { min = 0 max = 59 } for (let i = min; i <= max; i++) { let name if (type === 'year') { name = parseRow(config.yearRow, i) } else { const val = valueMap[list[0]] ? addZero(i) : i name = parseRow(config[type + 'Row'], val) } data.push({ name: name, value: i }) } if (type === 'noon') { data.push({ name: '上午', value: 'AM' }) data.push({ name: '下午', value: 'PM' }) } if (type === 'hour' && this.config.hourList) { data = this.config.hourList.map(hour => { return { name: parseRow(config['hourRow'], hour), value: Number(hour) } }) } if (type === 'day' && this.config.computeDaysFunction) { const rs = this.config.computeDaysFunction({ year, month, min, max }, generateRange) if (rs) { data = rs.map(day => { return { name: parseRow(config['dayRow'], addZero(day)), value: Number(day) } }) } } if (type === 'hour' && this.config.computeHoursFunction) { const isTodayVal = isToday(new Date(`${year}/${month}/${day}`), new Date()) const rs = this.config.computeHoursFunction(`${year}-${month}-${day}`, isTodayVal, generateRange) data = rs.map(hour => { // #2296 return { name: parseRow(config['hourRow'], hour), value: Number(hour) } }) } if (type === 'minute' && this.config.minuteList) { data = this.config.minuteList.map(minute => { return { name: parseRow(config['minuteRow'], minute), value: Number(minute) } }) } return data }, // after year change _setMonthScroller (currentValue, month) { if (!this.monthScroller) { return } const self = this this.monthScroller.destroy() const div = self.find('[data-role=month]') self.monthScroller = renderScroller(div, self._makeData('month'), month, function (currentValue) { self.config.onSelect.call(self, 'month', currentValue, self.getValue()) const currentYear = self.yearScroller ? self.yearScroller.value : self.config.currentYear if (self.dayScroller) { const currentDay = self.dayScroller.value self._setDayScroller(currentYear, currentValue, currentDay) } if (self.yearScroller && self.monthScroller && self.hourScroller) { self._setHourScroller(currentYear, currentValue, self.dayScroller.value, self.hourScroller.value) } }) }, _setDayScroller (year, month, day) { if (!this.dayScroller) { return } const self = this const maxDay = getMaxDay(year, month) if (day > maxDay) { day = maxDay } self.dayScroller.destroy() const div = self.find('[data-role=day]') self.dayScroller = renderScroller(div, self._makeData('day', year, month), day, function (currentValue) { self.config.onSelect.call(self, 'day', currentValue, self.getValue()) self.hourScroller && self._setHourScroller(year, month, currentValue, self.hourScroller.value) }) }, _setHourScroller (year, month, day, hour) { if (!this.hourScroller) { return } const self = this self.hourScroller.destroy() const div = self.find('[data-role=hour]') self.hourScroller = renderScroller(div, self._makeData('hour', year, month, day), hour || '', function (currentValue) { self.config.onSelect.call(self, 'hour', currentValue, self.getValue()) }) }, find (selector) { return this.container.querySelector(selector) }, hide (type) { if (!this.container) { return } const self = this self.container.style.removeProperty('transform') self.container.style.removeProperty('-webkit-transform') setTimeout(function () { self.container && (self.container.style.display = 'none') }, SHOW_CONTAINER_TIME) hideMask() self.config.onHide.call(self, type) if (self.config.destroyOnHide) { setTimeout(() => { self.destroy() }, 500) } }, select (type, value) { this[type + 'Scroller'].select(value, false) }, destroy () { const self = this this.trigger && this.trigger.removeEventListener('click', this.triggerHandler, false) if (!self.config.isOneInstance && !self.willShow) { removeElement(MASK) MASK = null } removeElement(self.container) self.container = null }, getValue () { const self = this const config = self.config let value = config.format function formatValue (scroller, expr1, expr2) { if (scroller) { const val = scroller.value if (expr1) { value = value.replace(new RegExp(expr1, 'g'), addZero(val)) } if (expr2) { value = value.replace(new RegExp(expr2, 'g'), trimZero(val)) } } } each(TYPE_MAP, function (key, list) { formatValue(self[key + 'Scroller'], list[0], list[1]) }) return value }, confirm () { const value = this.getValue() this.value = value if (this.config.onConfirm.call(this, value) === false) { return } this.hide('confirm') }, clear () { const value = this.getValue() if (this.config.onClear.call(this, value) === false) { return } this.hide('clear') } } export default DatetimePicker ================================================ FILE: src/components/datetime/format.js ================================================ export default function (date, fmt = 'YYYY-MM-DD HH:mm:ss') { var o = { 'M+': date.getMonth() + 1, 'D+': date.getDate(), 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, 'H+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), 'S': date.getMilliseconds() } var week = { '0': '\u65e5', '1': '\u4e00', '2': '\u4e8c', '3': '\u4e09', '4': '\u56db', '5': '\u4e94', '6': '\u516d' } if (/(Y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } if (/(E+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + week[date.getDay() + '']) } for (var k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) } } return fmt } ================================================ FILE: src/components/datetime/index.vue ================================================ cancel_text: en: cancel zh-CN: 取消 confirm_text: en: done zh-CN: 确定 ================================================ FILE: src/components/datetime/makeData.js ================================================ function isLeapYear (year) { return year % 100 !== 0 && year % 4 === 0 || year % 400 === 0 } function getMaxDay (year, month) { year = parseFloat(year) month = parseFloat(month) if (month === 2) { return isLeapYear(year) ? 29 : 28 } return [4, 6, 9, 11].indexOf(month) >= 0 ? 30 : 31 } function getYears (startDate, endDate) { let startYear = startDate.getFullYear() const endYear = endDate.getFullYear() let rs = [] while (startYear <= endYear) { rs.push(startYear) startYear++ } return { minYear: rs[0], maxYear: rs[rs.length - 1] } } function getMonths (startDate, endDate, year) { const startYear = startDate.getFullYear() const endYear = endDate.getFullYear() const startMonth = startDate.getMonth() + 1 const endMonth = endDate.getMonth() + 1 let start = 1 let end = 12 if (year === startYear) { start = startMonth } if (year === endYear) { end = endMonth } return { minMonth: start, maxMonth: end } } function getDays (startDate, endDate, year, month) { const startYear = startDate.getFullYear() const endYear = endDate.getFullYear() const startMonth = startDate.getMonth() + 1 const endMonth = endDate.getMonth() + 1 const startDay = startDate.getDate() const endDay = endDate.getDate() let start = 1 let end = getMaxDay(year, month) if (year === startYear && month === startMonth) { start = startDay } if (year === endYear && month === endMonth) { end = endDay } return { minDay: start, maxDay: end } } export { getYears, getMonths, getDays } ================================================ FILE: src/components/datetime/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: | ::: tip 需要在`Group`组件里使用 ::: 该组件支持以`plugin`形式调用: ``` js // 以 plugin 形式使用时,请在入口处引入: import { DatetimePlugin } from 'vux' Vue.use(DatetimePlugin) // 组件内使用 this.$vux.datetime.show({ value: '', // 其他参数同 props onHide () { const _this = this }, onShow () { const _this = this } }) this.$vux.datetime.hide() ``` tips: zh-CN: - q: 是否支持秒(ss) a: 不支持,目前常用需求场景并没有需要精确到秒。 - q: on-confirm 事件获取不到正确的值 a: | 在 `v2.9.0` 之前 on-confirm 不会附带当前值参数,你需要在 `this.$nextTick` 后获取。 相关 issue: [#2632](https://github.com/airyland/vux/issues/2632) tags: en: - date - datetime - form zh-CN: - 日期 - 选择 - 表单 props: format: type: String default: 'YYYY-MM-DD' en: date format. Special characters are not supported, for example, YYYY-MM-DD HH:mm(do not support ss) zh-CN: 时间格式,不支持特殊字符,只能类似 YYYY-MM-DD HH:mm 这样的格式(不支持秒 ss), 另外支持 YYYY-MM-DD A 这样的格式(A为上下午) title: type: String default: '' en: cell title zh-CN: 标题 value: type: String default: '' en: form's value, use `v-model` for binding zh-CN: 表单值,`v-model`绑定 inline-desc: type: String default: '' en: description of the cell zh-CN: 描述字符 placeholder: type: String default: '' en: placeholder, works when value is empty zh-CN: 提示文字,当`value`为空时显示 min-year: type: Number default: '' en: min-year of the form zh-CN: 可选择的最小年份 max-year: type: Number default: '' en: max-year of the form zh-CN: 可选择的最大年份 min-hour: type: Number default: 0 en: min hour zh-CN: 限定小时最小值 max-hour: type: Number default: 23 en: max hour zh-CN: 限定小时最大值 confirm-text: type: String default: ok(确认) en: confirm button's text zh-CN: 确认按钮文字 cancel-text: type: String default: cancel(取消) en: cancel button's text zh-CN: 取消按钮文字 clear-text: type: String default: '' en: custom button's text which shows in the middle of the header zh-CN: 显示在中间的自定义按钮的文字 year-row: type: String default: '{value}' en: render template for the year column zh-CN: 年份的渲染模板 month-row: type: String default: '{value}' en: render template for the month column zh-CN: 月份的渲染模板 day-row: type: String default: '{value}' en: render template for the day column zh-CN: 日期的渲染模板 hour-row: type: String default: '{value}' en: render template for the hour column zh-CN: 小时的渲染模板 minute-row: type: String default: '{value}' en: render template for the minute column zh-CN: 分钟的渲染模板 start-date: type: String en: 'start date, must be YYYY-MM-DD. Please use `min-hour` and `max-hour` to limit the range of hours' zh-CN: '限定最小日期,格式必须为 YYYY-MM-DD,注意该限制只能限定到日期,不能限定到小时分钟。小时限定请使用`min-hour`和`max-hour`' end-date: type: String en: 'end date, must be YYYY-MM-DD. Please use `min-hour` and `max-hour` to limit the range of hours' zh-CN: 限定最大日期,格式必须为 YYYY-MM-DD,注意该限制只能限定到日期,不能限定到小时分钟 required: type: Boolean default: false en: if required zh-CN: 是否必填 display-format: version: v2.1.1-rc.11 type: Function en: used to format display value zh-CN: 自定义显示值 readonly: version: v2.3.6 en: readonly mode, show like a cell zh-CN: 只读模式,显示类似于 cell show: version: v2.3.7 type: Boolean en: control visibility of datetime, require vue^2.3 zh-CN: 控制显示,要求 vue^2.3 minute-list: version: v2.3.7 type: Array en: specify minute list, for instance ['00', '15', '30', '45'] zh-CN: 定义分钟列表,比如 ['00', '15', '30', '45'] hour-list: version: v2.3.7 type: Array en: specify hour list, for instance ['09', '10', '11', '12'] zh-CN: 定义小时列表,比如 ['09', '10', '11', '12'] default-selected-value: version: v2.4.1 en: set default selected value, works only when value is empty zh-CN: 设置默认选中日期,当前 value 为空时有效 compute-hours-function: version: v2.5.5 type: Function en: 'dynamically set hours list, params `(value, isToday, generateRange)`' zh-CN: '动态设置小时列表,参数为 `(value, isToday, generateRange)`' compute-days-function: version: v2.6.1 type: Function en: 'dynamically set days list, params `({year, month, min, max}, generateRange)`' zh-CN: '动态设置日期列表,参数为 `({year, month, min, max}, generateRange)`' order-map: version: v2.9.0 type: Object en: 'set column order, `{year: 1, month: 2, day: 3, hour: 4, minute: 5, noon: 6}`' zh-CN: '自定义列顺序, 如 `{year: 1, month: 2, day: 3, hour: 4, minute: 5, noon: 6}`' slots: default: en: trigger element content zh-CN: 触发元素内容 title: version: v2.3.6 en: title slot zh-CN: title slot events: on-change: params: '`(value)`' en: $emits when value changes, `(newVal)` zh-CN: 表单值变化时触发, 参数 `(newVal)` on-clear: en: $emits when click the button in the middle of the header zh-CN: 点击显示在中间的自定义按钮时触发 on-show: en: fires when datetime shows zh-CN: 弹窗显示时触发 on-hide: params: '`(type)`, type is one of [cancel, confirm]' version: v2.7.4 en: fires when datetime hides zh-CN: 弹窗关闭时触发 on-cancel: version: v2.7.4 en: fires when cancel button is clicked or mask is clicked zh-CN: 点击取消按钮或者遮罩时触发,等同于事件 on-hide(cancel) on-confirm: version: v2.7.4 params: '`(value)` v2.9.0 支持该参数' en: fires when confirm button is clicked zh-CN: 点击确定按钮时触发,等同于事件 on-hide(confirm) changes: v2.9.0: en: - '[feature] support order-map to customize column orders #2300' - '[fix] fix mask cause body scroll in iOS #2593' - '[feature] add arg:value for event:on-confirm #2632' - '[fix] fire event:on-confirm after nextTick #2632' - '[feature] support format YYYY-MM-DD A #2627 @jack87918' zh-CN: - '[feature] 支持使用 order-map 自定义列顺序 #2300' - '[fix] 修复遮罩导致页面滚动 #2593' - '[feature] 事件 on-confirm 添加当前值 #2632' - '[fix] 在 nextTick 后触发 on-confirm 事件 #2632' - '[feature] 支持格式 YYYY-MM-DD A 选择上下午 #2627 @jack87918' v2.7.8: en: - '[fix] fix `on-hide` event trigger twice #2379' - '[fix] fix cannot set `show` to `true` initially' zh-CN: - '[fix] 修复 `on-hide` 事件触发两次的问题 #2379' - '[fix] 修复初始化时 `show` 值为 `true` 无效的问题' v2.7.6: en: - '[fix] column value use number type, fix binding value is not equal to datetimepicker value #2296' zh-CN: - '[fix] 列的值使用number类型,修正绑定值与datetimepicker值不相等 #2296' v2.7.4: en: - '[feature] add event:on-confirm event:on-cancel #2221' zh-CN: - '[feature] 支持事件 on-confirm on-cancel #2221' v2.7.2: en: - '[enhance] set default start-date if end-date is specified but start-date is not #2158' zh-CN: - '[enhance] 当指定结束日期但未指定开始日期时使用默认年第一天作为开始日期 #2158' v2.7.1: en: - '[fix] fix less variables do not work in plugin usage #2152' zh-CN: - '[fix] 修复插件使用时 less 变量不生效问题 #2152' v2.7.0: en: - '[fix] fix ssr i18n bug' - '[fix] fix datetime still shows when prop:readonly is true #2079' zh-CN: - '[fix] 修复服务端渲染多语言 bug' - '[fix] 修复 readonly 为 true 时点击还会显示弹窗的问题 #2079' v2.6.1: en: - '[feature] support prop:compute-days-function for dynamically setting days #1992' zh-CN: - '[feature] 支持属性 compute-days-function 用以动态设置日期 #1992 ' v2.6.0: en: - '[fix] fix i18n for cancel_text confirm_text' zh-CN: - '[fix] 修复取消文字、确认文字国际化显示问题' v2.5.11: en: - '[fix] fix :show.syc="false" do no work #1918' - '[fix] fix prop:compute-hours-function not working when month is changed' zh-CN: - '[fix] 修复 :show.sync 设为 false 无效 #1918' - '[fix] 修复特定情况下月份变化不会触发小时重新渲染的问题' v2.5.10: en: - '[feature] support @cell-value-color #1874' zh-CN: - '[feature] 值文字颜色受控于 @cell-value-color #1874' v2.5.9: en: - '[fix] fix error when format is YYYY #1861' zh-CN: - '[fix] 修复格式为 YYYY 时的报错 #1861' v2.5.5: en: - '[feature] support prop:compute-hours-function #1749' - '[enhance] better mask transition' zh-CN: - '[feature] 支持动态设置小时列表 #1749' - '[enhance] 更加流畅的遮罩层动画' v2.4.1: en: - '[fix] Re-render when readonly is changed #1593' - '[feature] Support prop:default-selected-value #1576' zh-CN: - '[fix] readonly 值变化时重新渲染 #1593' - '[feature] 支持通过 prop:default-selected-value 设置默认选中日期 #1576' v2.4.0: en: - '[enhance] use the same popup header style as popup-picker' zh-CN: - '[enhance] 统一弹窗头部样式,和 popup-picker 一致' v2.3.7: en: - '[feature] Add prop:show to control visibility #1538' - '[fix] Fix range error with format YYYY-MM #1358' - '[feature] Add prop:hourList prop:minuteList' - '[fix] startDate year should overwrite minYear #1358' zh-CN: - '[feature] 可以使用 :show.sync 来控制控件显示 #1358' - '[fix] 修复格式为 YYYY-MM 时的日期范围错误 #1528' - '[feature] 支持通过 prop:hourList prop:minuteList 自定义小时和分钟列表' - '[fix] 开始日期年份应该覆盖最小年份(minYear) #1358' v2.3.6: en: - '[feature] Add slot:title' - '[feature] Add prop:readonly' - '[fix] Fix a bug caused by scroller #1406' zh-CN: - '[feature] 添加 slot:title' - '[feature] 添加 prop:readonly' - '[fix] 修复 scroller 支持数字类型引入的 bug #1406' v2.3.4: en: - '[fix] do not trigger on-change on first-value-setting' zh-CN: - '[fix] 初始化时不触发 on-change 事件' v2.2.1-rc.8: en: - '[enhance] now changing prop:format will trigger re-rendering' zh-CN: - '[enhance] 修改 prop:format 会触发重新渲染' v2.2.0: en: - '[fix] render picker on nextTick #1180' zh-CN: - '[fix] 在 nextTick 回调渲染 picker 避免赋值报错 #1180' v2.1.1-rc.11: en: - '[feature] Support prop:display-format #1086 @greedying' zh-CN: - '[feature] 支持格式化显示 prop:display-format #1086 @greedying' v2.1.1-rc.7: en: - '[enhance] Support PC mouse drag #1039 @michael829' zh-CN: - '[enhance] 支持 PC 上鼠标选择 #1039 @michael829' v2.1.0: zh-CN: - '[fix] 修复`label`宽度没有受限于`group`设置' v2.1.0-rc.46: zh-CN: - '[feature] 支持配置`取消`和`确定`的文字颜色 #715 @greedying' ================================================ FILE: src/components/datetime/style.less ================================================ @import '../../styles/variable.less'; .dp-container { &.vux-datetime-view { position: static; transition: none; & .dp-header { display: none; } } } .vux-datetime-clear { text-align: center; } .scroller-component { display: block; position: relative; height: 238px; overflow: hidden; width: 100%; } .scroller-content { position: absolute; left: 0; top: 0; width: 100%; z-index: -1; } .scroller-mask { position: absolute; left: 0; top: 0; height: 100%; margin: 0 auto; width: 100%; z-index: 3; background-image: linear-gradient(to bottom, rgba(255,255,255,0.95), rgba(255,255,255,0.6)), linear-gradient(to top, rgba(255,255,255,0.95), rgba(255,255,255,0.6)); background-position: top, bottom; background-size: 100% 102px; background-repeat: no-repeat; } .scroller-item { text-align: center; font-size: 16px; height: 34px; line-height: 34px; color: #000; } .scroller-indicator { width: 100%; height: 34px; position: absolute; left: 0; top: 102px; z-index: 3; background-image: linear-gradient(to bottom, #d0d0d0, #d0d0d0, transparent, transparent), linear-gradient(to top, #d0d0d0, #d0d0d0, transparent, transparent); background-position: top, bottom; background-size: 100% 1px; background-repeat: no-repeat; } .dp-container { position: fixed; width: 100%; left: 0; bottom: 0; z-index: 10000; background-color: #fff; display: none; transition: transform 0.3s ease; transform: translateY(100%); } .dp-mask { z-index: 998; position: fixed; width: 100%; height: 100%; left: 0px; top: 0px; opacity: 0; transition: opacity 0.2s ease-in; background-color: #000; z-index: 9999; } .dp-header { display: flex; width: 100%; box-align: center; align-items: center; background-image: linear-gradient(to bottom, #e7e7e7, #e7e7e7, transparent, transparent); background-position: bottom; background-size: 100% 1px; background-repeat: no-repeat; } .dp-header .dp-item { color: @datetime-header-item-font-color; font-size: 16px; height: 44px; line-height: 44px; cursor: pointer; } .dp-header .dp-item.dp-left { color: @datetime-header-item-cancel-font-color; } .dp-header .dp-item.dp-right { color: @datetime-header-item-confirm-font-color; } .dp-content { display: flex; width: 100%; box-align: center; align-items: center; padding: 10px 0; } .dp-header .dp-item, .dp-content .dp-item { box-sizing: border-box; flex: 1; } .vux-datetime-cancel { text-align: left; padding-left: 15px; } .vux-datetime-confirm { text-align: right; padding-right: 15px; } .vux-datetime { color: #000; text-decoration: none; } .vux-datetime .vux-input-icon { float: right; } .vux-cell-primary { flex: 1; } ================================================ FILE: src/components/datetime/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Datetime', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('datetime') }) }) ================================================ FILE: src/components/datetime/util.js ================================================ import formater from './format' export function each (obj, fn) { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (fn.call(obj[key], key, obj[key]) === false) { break } } } } export function trimZero (val) { val = String(val) val = val ? parseFloat(val.replace(/^0+/g, '')) : '' val = val || 0 val = val + '' return val } export function generateRange (start = 0, end) { let results = [] for (let i = start; i <= end; i++) { results.push(addZero(i)) } return results } export function isToday (val1, val2) { return val1.getFullYear() === val2.getFullYear() && val1.getMonth() === val2.getMonth() && val1.getDate() === val2.getDate() } export function addZero (val) { val = String(val) return val.length < 2 ? '0' + val : val } export function isLeapYear (year) { return year % 100 !== 0 && year % 4 === 0 || year % 400 === 0 } export function getMaxDay (year, month) { year = parseFloat(year) month = parseFloat(month) if (month === 2) { return isLeapYear(year) ? 29 : 28 } return [4, 6, 9, 11].indexOf(month) >= 0 ? 30 : 31 } export function parseRow (tmpl, value) { return tmpl.replace(/\{value\}/g, value) } // parse Date String export function parseDate (format, value) { const formatParts = format.split(/[^A-Za-z]+/) let valueParts = value.replace(/\s/g, '-').replace(/:/g, '-').replace(/\//g, '-').split('-') if (formatParts.length !== valueParts.length) { // if it is error date, use current date const date = formater(new Date(), format) valueParts = date.split(/\D+/) } let result = {} for (let i = 0; i < formatParts.length; i++) { if (formatParts[i]) { result[formatParts[i]] = valueParts[i] } } return result } export function getElement (expr) { return (typeof expr === 'string') ? document.querySelector(expr) : expr } export function toElement (html) { const tempContainer = document.createElement('div') tempContainer.innerHTML = html return tempContainer.firstElementChild } export function removeElement (el) { el && el.parentNode.removeChild(el) } ================================================ FILE: src/components/datetime-range/index.vue ================================================ ================================================ FILE: src/components/datetime-range/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: | ::: tip 该组件和 `Datetime` 组件不同的地方是`年月日`集中显示在一栏,适合范围不大的日期选择。
    需要在`Group`组件里使用 ::: tags: en: - date - datetime - form zh-CN: - 日期 - 选择 - 表单 props: title: type: String default: '' en: label text zh-CN: 标题文字 value: type: Array default: '' en: "form's value, use `v-model` for binding. For example, ['2017-01-15', '03', '05']" zh-CN: "表单值,`v-model`绑定。比如,['2017-01-15', '03', '05']" inline-desc: type: String default: '' en: description of the cell zh-CN: 描述字符 placeholder: type: String default: '' en: placeholder, works when `value` is empty zh-CN: 提示文字,当`value`为空时显示 start-date: type: String en: start date (YYYY-MM-DD) zh-CN: '限定最小日期,注意该限制只能限定到日期,不能限定到小时分钟' end-date: type: String en: end date (YYYY-MM-DD) zh-CN: 限定最大日期,注意该限制只能限定到日期,不能限定到小时分钟 format: default: 'YYYY-MM-DD' en: display format for date column zh-CN: 日期栏的显示格式 events: on-change: params: '`(value)`' en: $emits when value changes, `(newVal)` zh-CN: 表单值变化时触发, 参数 `(newVal)` changes: v2.2.0: en: - '[enhance] Use better column width' zh-CN: - '[enhance] 优化宽度,避免日期栏和左边缘距离过近' v2.1.1-rc.9: en: - '[feature] new component #970 @327326724' zh-CN: - '[feature] 新组件 #970 @327326724' ================================================ FILE: src/components/datetime-range/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('DatetimeRange', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('datetime-range') }) }) ================================================ FILE: src/components/datetime-view/index.vue ================================================ ================================================ FILE: src/components/datetime-view/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' props: value: type: String default: '' en: form's value, use `v-model` for binding zh-CN: 表单值,`v-model`绑定 format: default: 'YYYY-MM-DD' en: display format for date column zh-CN: 日期栏的显示格式 methods: render: version: v2.5.0 en: re-render component zh-CN: 强制重新渲染组件,当主动修改值或者其他非响应属性时需要调用该方法 changes: v2.9.2: en: - '[fix] fix render error for DOM not found #2797' zh-CN: - '[fix] 修复render错误,未找到DOM元素 #2797' v2.5.0: en: - '[feature] new component' zh-CN: - '[feature] 新组件 DatetimeView' ================================================ FILE: src/components/datetime-view/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('DatetimeView', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('datetime-view') }) }) ================================================ FILE: src/components/dev-tip/index.vue ================================================ ================================================ FILE: src/components/divider/index.vue ================================================ ================================================ FILE: src/components/divider/metas.yml ================================================ category: en: Layout 'zh-CN': 布局 icon: '' extra: | ::: tip 不支持配置分割线颜色,因为线条是通过图片来实现的。好处是在任何背景颜色下都可以适应。如果需要配置颜色,请使用`load-more`组件。 ::: ``` html 我是有底线的 ``` tags: en: - layout - divider zh-CN: - 布局 - 分割线 slots: default: en: title zh-CN: 分隔线标题 references: - http://www.daqianduan.com/4258.html ================================================ FILE: src/components/divider/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Divider', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('divider') }) }) ================================================ FILE: src/components/drawer/index.vue ================================================ /** * this component is forked from: https://github.com/bajian/vue-drawer */ ================================================ FILE: src/components/drawer/metas.yml ================================================ icon: '' category: en: Navigation zh-CN: 导航 extra: en: 'this component is forked from: https://github.com/bajian/vue-drawer' zh-CN: | 当前组件 forked 自 https://github.com/bajian/vue-drawer drawer 设计是作为公用侧边栏使用,所以不支持直接在单页面里使用,而是配合 `router-view` 来使用。 ``` html ``` ::: tip 源码请参考 [App.vue](https://github.com/airyland/vux/blob/v2/src/App.vue) ::: props: show: version: v2.4.0 type: Boolean default: false en: visibility of the component, use :show.sync to control the visibility(vue@^2.3.3) zh-CN: 是否展开,使用 :show.sync 绑定(vue@^2.3.3) drawer-style: version: v2.4.0 en: menu container styles zh-CN: 菜单样式 show-mode: version: v2.4.0 default: overlay en: animation type, can be push or overlay zh-CN: 展示方式,push(推开内容区域)或者 overlay(在内容上显示) placement: version: v2.4.0 default: left en: placement, left or right zh-CN: 显示位置,可以为 left 或者 right slots: default: version: v2.4.0 en: page content container zh-CN: 主体内容插槽 drawer: version: v2.4.0 en: drawer menu container zh-CN: 侧边栏内容插槽 event: on-show: version: v2.4.0 en: emits when drawer is shown zh-CN: 侧边栏显示时触发 on-hidde: version: v2.4.0 en: emits when drawer is hidden zh-CN: 侧边栏关闭时触发 changes: v2.4.0: en: - '[feature] new component' zh-CN: - '[feature] 新组件:侧边栏(beta)' ================================================ FILE: src/components/drawer/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Drawer', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('drawer') }) }) ================================================ FILE: src/components/flexbox/flexbox-item.vue ================================================ ================================================ FILE: src/components/flexbox/flexbox.vue ================================================ ================================================ FILE: src/components/flexbox/index.js ================================================ import Flexbox from './flexbox' import FlexboxItem from './flexbox-item' export { Flexbox, FlexboxItem } ================================================ FILE: src/components/flexbox/metas.yml ================================================ icon: '' category: en: Layout 'zh-CN': 布局 category_order: 2 items: - flexbox - flexbox-item tags: en: - flex - flexbox zh-CN: - 布局 - 栅格 flexbox: props: gutter: type: Number default: 8 en: gap size (px) zh-CN: 间隙像素大小(px) orient: type: String default: horizontal en: "can be in ['horizontal', 'vertical']" zh-CN: "排布方向,可选['horizontal', 'vertical']" justify: en: '`justify-content` in `flex`' zh-CN: '`flex`的`justify-content`属性' align: en: '`align-items` in `flex`' zh-CN: '`flex`的`align-items`属性' wrap: en: '`flex-wrap` in `flex`' zh-CN: '`flex`的`flex-wrap`属性' direction: en: '`flex-direction` in `flex`' zh-CN: '`flex`的`flex-direction`属性' slots: default: en: 'content slot for `flexbox-item`' zh-CN: '`flexbox-item`的内容插槽' flexbox-item: props: span: type: Number en: occupied width. If not set, all flexbox-item will get the same width. zh-CN: 占用宽度,如果不设置,所有flexbox-item将平分 order: type: String en: '`order` in `flex`' zh-CN: '`flex`的`order`属性' slots: default: en: content slot zh-CN: '内容插槽' ================================================ FILE: src/components/flexbox/test.js ================================================ import Comp from './flexbox-item.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Blur', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('flexbox-item') }) }) ================================================ FILE: src/components/flow/flow-line.vue ================================================ ================================================ FILE: src/components/flow/flow-state.vue ================================================ ================================================ FILE: src/components/flow/flow.vue ================================================ ================================================ FILE: src/components/flow/metas.yml ================================================ category: en: Data Display 'zh-CN': 数据展示 icon: '' items: - flow - flow-state - flow-line flow: props: orientation: type: String default: 'horizontal' en: "orientation of flow, can be in ['horizontal', 'vertical']" zh-CN: "flow 方向,可选['horizontal', 'vertical']" flow-state: props: title: type: String default: '' en: title for state point zh-CN: 标题 state: type: String,Number default: '' en: content showed inside state point. zh-CN: 在节点中显示的内容 is-done: type: Boolean default: false en: if done zh-CN: 该节点是否完成 slots: title: version: v2.7.0 en: title for state point,default content is prop:title zh-CN: 标题插槽,默认内容为 prop:title flow-line: props: tip: type: String default: '' en: tip for flow line, invalid when is-done is true zh-CN: 流线的提示文字,当 is-done 为 true 时无效 tip-direction: type: String default: top for horizontal flow, left for vertical flow en: "orientation of tip, can be in ['top', 'right', 'bottom', 'left']. If horizontal flow, the default is top; if vertical flow, the default is left" zh-CN: "提示文字方向,可选['top', 'right', 'bottom', 'left']. 在横向 flow 中默认为 top,纵向默认则为 left" is-done: type: Boolean default: false en: if done zh-CN: 该流线是否完成 line-span: type: Number,String en: length of flow line. if not set, all flow-line will get the same length zh-CN: 流线的整体长度,如果不设置,所有 flow line 将平分 process-span: type: Number,String default: 50 en: percentage rate of progress which will be showed in flow line zh-CN: 在 flow line 上显示的进度比例 changes: v2.7.0: en: - '[feature] add slot:title for flow-state #2010' zh-CN: - '[feature] 为 flow-state 添加 slot:title #2010' v2.3.1: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/flow/test.js ================================================ import Comp from './flow.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Flow', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('flow') }) }) ================================================ FILE: src/components/form-preview/index.vue ================================================ ================================================ FILE: src/components/form-preview/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - form zh-CN: - 表单 props: header-label: type: String default: '' en: header label zh-CN: 头部标题 header-value: type: String default: '' en: header value zh-CN: 头部内容 body-items: type: Array default: '[]' en: a list of label and value pair zh-CN: 主体内容列表, [{label:'label',value:'value'}] footer-buttons: type: Array default: '[]' en: | button list. Default is a gray style, and the primary text is highlighted. [{style: 'primary', text: 'text', onButtonClick: fn(prop:name), link: '/path'}] zh-CN: '底部按钮列表,default 为灰色样式,primary 文字为高亮颜色, [{style: "primary", text: "text", onButtonClick: fn(prop:name), link: "/path"}]' changes: v2.5.12: en: - '[fix] Fix layout issue when header-value is empty #1935' zh-CN: - '[fix] 修复 header-value 为空时的错位问题 #1935' v2.1.1-rc.14: en: - '[fix] Fix variable: form-preview-button-primary-color' - '[feature] Support prop:name #1123' zh-CN: - '[fix] 修复 less 变量:form-preview-button-primary-color' - '[feature] 支持 prop:name 以方便函数调用 #1123' v2.1.0-rc.49: zh-CN: - '[fix] 修复出现滚动条 #920 @asingingfish' v2.0.0: zh-CN: - '[feature] 增加`form-preview`组件' ================================================ FILE: src/components/form-preview/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('FormPrview', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('form-preview') }) }) ================================================ FILE: src/components/fullpage/DemoBasic.vue ================================================ ================================================ FILE: src/components/fullpage/index.vue ================================================ ================================================ FILE: src/components/fullpage/lib.js ================================================ /*! * zepto.fullpage.js v0.5.0 (https://github.com/yanhaijing/zepto.fullpage) * API https://github.com/yanhaijing/zepto.fullpage/blob/master/doc/api.md * Copyright 2014 yanhaijing. All Rights Reserved * Licensed under MIT (https://github.com/yanhaijing/zepto.fullpage/blob/master/LICENSE) */ const BOX_CLASS = 'vux-fullpage-box' const ANIM_CLASS = 'vux-fullpage-box-anim' const ITEM_CLASS = 'vux-fullpage-item' const DIR_CLASS = 'vux-fullpage-dir' var d = { page: null, start: 0, duration: 500, loop: false, drag: false, dir: 'v', der: 0.1, change: function (data) {}, beforeChange: function (data) {}, afterChange: function (data) {}, orientationchange: function (orientation) {} } function touchmove (e) { e.preventDefault() } function fix (cur, pagesLength, loop) { if (cur < 0) { return loop ? pagesLength - 1 : 0 } if (cur >= pagesLength) { return loop ? 0 : pagesLength - 1 } return cur } function move (ele, dir, dist) { var xPx = '0px' var yPx = '0px' if (dir === 'v') yPx = dist + 'px' else xPx = dist + 'px' ele.style.cssText += ('-webkit-transform : translate3d(' + xPx + ', ' + yPx + ', 0px);' + 'transform : translate3d(' + xPx + ', ' + yPx + ', 0px)') } function init (option) { var o = option || {} for (var key in d) { if (!o.hasOwnProperty(key)) { o[key] = d[key] } } var that = this that.curIndex = -1 that.o = o that.startY = 0 that.movingFlag = false that.ele.classList.add(BOX_CLASS) that.parentEle = that.ele.parentNode var query = o.page if (query && query.indexOf('.') === 0) { query = query.substring(1, query.length) that.pageEles = that.ele.getElementsByClassName(query) } if (!query) { that.pageEles = that.ele.children } for (var i = 0; i < that.pageEles.length; i++) { var pageEle = that.pageEles[i] pageEle.classList.add(ITEM_CLASS) pageEle.classList.add(DIR_CLASS + o.dir) } that.pagesLength = that.pageEles.length that.update() that.initEvent() that.start() } function Fullpage (ele, option) { this.ele = ele init.call(this, option) } Fullpage.prototype.update = function () { let pageEle if (this.o.dir === 'h') { this.width = this.parentEle.offsetWidth for (let i = 0; i < this.pageEles.length; i++) { pageEle = this.pageEles[i] pageEle.style.width = this.width + 'px' } this.ele.style.width = (this.width * this.pagesLength) + 'px' } this.height = this.parentEle.offsetHeight for (let i = 0; i < this.pageEles.length; i++) { pageEle = this.pageEles[i] pageEle.style.height = this.height + 'px' } this.moveTo(this.curIndex < 0 ? this.o.start : this.curIndex) } Fullpage.prototype.initEvent = function () { var that = this var ele = that.ele ele.addEventListener('touchstart', function (e) { if (!that.status) { return 1 } // e.preventDefault() if (that.movingFlag) { return 0 } that.startX = e.targetTouches[0].pageX that.startY = e.targetTouches[0].pageY }) ele.addEventListener('touchend', function (e) { if (!that.status) { return 1 } // e.preventDefault() if (that.movingFlag) { return 0 } var sub = that.o.dir === 'v' ? (e.changedTouches[0].pageY - that.startY) / that.height : (e.changedTouches[0].pageX - that.startX) / that.width var der = (sub > that.o.der || sub < -that.o.der) ? sub > 0 ? -1 : 1 : 0 that.moveTo(that.curIndex + der, true) }) if (that.o.drag) { ele.addEventListener('touchmove', function (e) { if (!that.status) { return 1 } // e.preventDefault() if (that.movingFlag) { that.startX = e.targetTouches[0].pageX that.startY = e.targetTouches[0].pageY return 0 } var y = e.changedTouches[0].pageY - that.startY if ((that.curIndex === 0 && y > 0) || (that.curIndex === that.pagesLength - 1 && y < 0)) y /= 2 var x = e.changedTouches[0].pageX - that.startX if ((that.curIndex === 0 && x > 0) || (that.curIndex === that.pagesLength - 1 && x < 0)) x /= 2 var dist = (that.o.dir === 'v' ? (-that.curIndex * that.height + y) : (-that.curIndex * that.width + x)) ele.classList.remove(ANIM_CLASS) move(ele, that.o.dir, dist) }) } window.addEventListener('orientationchange', function () { if (window.orientation === 180 || window.orientation === 0) { that.o.orientationchange('portrait') } if (window.orientation === 90 || window.orientation === -90) { that.o.orientationchange('landscape') } }, false) window.addEventListener('resize', function () { that.update() }, false) } Fullpage.prototype.holdTouch = function () { document.addEventListener('touchmove', touchmove) } Fullpage.prototype.unholdTouch = function () { document.removeEventListener('touchmove', touchmove) } Fullpage.prototype.start = function () { this.status = 1 this.holdTouch() } Fullpage.prototype.stop = function () { this.status = 0 this.unholdTouch() } Fullpage.prototype.getCurIndex = function () { return this.curIndex } Fullpage.prototype.moveTo = function (next, anim) { var that = this var ele = that.ele var cur = that.curIndex next = fix(next, that.pagesLength, that.o.loop) if (anim) { ele.classList.add(ANIM_CLASS) } else { ele.classList.remove(ANIM_CLASS) } if (next !== cur) { var flag = that.o.beforeChange({ next: next, cur: cur }) // beforeChange return false to stop scrolling if (flag === false) { return 1 } } that.movingFlag = true that.curIndex = next move(ele, that.o.dir, -next * (that.o.dir === 'v' ? that.height : that.width)) if (next !== cur) { that.o.change({ prev: cur, cur: next }) } window.setTimeout(function () { that.movingFlag = false if (next !== cur) { that.o.afterChange({ prev: cur, cur: next }) for (var i = 0; i < that.pageEles.length; i++) { var pageEle = that.pageEles[i] if (i === next) { pageEle.classList.add('cur') } else { pageEle.classList.remove('cur') } } } }, that.o.duration) } Fullpage.prototype.movePrev = function (anim) { this.moveTo(this.curIndex - 1, anim) } Fullpage.prototype.moveNext = function (anim) { this.moveTo(this.curIndex + 1, anim) } export default Fullpage ================================================ FILE: src/components/grid/grid-item.vue ================================================ ================================================ FILE: src/components/grid/grid.vue ================================================ ================================================ FILE: src/components/grid/index.js ================================================ import Grid from './grid' import GridItem from './grid-item' export { Grid, GridItem } ================================================ FILE: src/components/grid/metas.yml ================================================ icon: '' import_code: import { Grid, GridItem } from 'vux' category: en: Layout 'zh-CN': 布局 tags: en: - grid - layout zh-CN: - 宫格 - 九宫格 - 布局 items: - grid - grid-item extra: | ``` html ``` grid: props: rows: type: Number version: v2.2.0 default: 3 en: (deprecated after v2.6.0) the number of rows. Less than 5 will be better zh-CN: (v2.6.0 之后废弃,使用 col 替代)宫格行数,建议少于`5` cols: type: Number version: v2.6.1 default: 3 en: the number of columns. if there is more than one row in grid, need to set cols value, otherwise all grid items will show in one row with the same width zh-CN: 列数。如果为非单行 Grid,需要设置 cols,否则所有 GridItem 会平均宽度显示在一行。 show-lr-borders: type: Boolean version: v2.8.1 default: true en: whether show left and right borders zh-CN: 是否显示左右边框 show-vertical-dividers: type: Boolean version: v2.8.1 default: true en: whether show vertical dividers zh-CN: 是否显示垂直分割线 slots: default: en: 'default slot for `grid-item` list' zh-CN: 用于`grid-item`的插槽 grid-item: props: icon: en: 'icon url. if using local resources, it is better to use `slot=icon`' zh-CN: '图标地址,如果是线上地址,推荐使用该prop。如果是本地图标资源,使用`slot=icon`可以保证资源被正确打包' label: en: '`label` text' zh-CN: '`label` 文字' link: en: vue-router's path zh-CN: vue-router 路径 slots: icon: en: 'icon slot, use tag:`img`' zh-CN: "图标内容,直接使用`img`标签" label: en: slot for label, the same func as prop:label zh-CN: label 文字的 slot,作用同 prop:label changes: v2.8.1: en: - '[feature] add prop:show-lr-borders and prop:show-vertical-dividers #2488' zh-CN: - '[feature] 支持 prop:show-lr-borders、prop:show-vertical-dividers 用以隐藏左右边线及垂直分割线 #2488' v2.6.1: en: - '[feature] add `cols` props' zh-CN: - '[feature] 添加 `cols` 属性,自定义列数' v2.6.0: en: - '[change] prop:rows is deprecated #1971' zh-CN: - '[change] rows 属性已经废弃,使用自动计算 #1971' v2.2.0: en: - '[feature] Support dynamic rows' - '[enhance] Hide slot:icon and slot:label if not being set' zh-CN: - '[feature] 支持定义列数' - '[enhance] 如果没有使用 icon 和 label,则直接隐藏 slot:icon 和 slot:label' v2.0.14: en: - '[feature] Add component `grid`' zh-CN: - '[feature] 增加`grid`九宫格组件' ================================================ FILE: src/components/group/index.vue ================================================ ================================================ FILE: src/components/group/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - form - group zh-CN: - 表单 - 分组 extra: en: '`Group` is a special component for wrapping form elements' zh-CN: > `Group`是一个特殊的表单`wrapper`组件,主要用于将表单分组,单个表单元素也算一组。常见的表单组件都`必须`作为`Group`的子组件。 属于`Group`子组件的有:Cell, XInput, XTextarea, XSwitch, Calendar, XNumber, Radio, XAddress, Datetime, Selector props: title: type: String default: '' en: group title zh-CN: 分组标题 title-color: type: String default: '' en: group title's color zh-CN: 分组标题文字颜色 label-width: type: String default: '' en: common label width for child components zh-CN: 为子元素设定统一label宽度 label-align: type: String default: '' en: common label text-align for child components zh-CN: 为子元素设定统一对齐方式 label-margin-right: type: String default: '' en: common margin right value for child components zh-CN: 为子元素设定统一的右边margin gutter: type: String default: '' en: set the marginTop value when there is no title zh-CN: 设定group的上边距,只能用于没有标题时 slots: default: en: content body for child components zh-CN: 子组件插槽 title: version: v2.5.1 en: title slot zh-CN: 标题插槽 changes: v2.5.4: en: - '[enhance] XSwitch:labelAlign support justify #1707' zh-CN: - '[enhance] XSwitch的labelAlign 支持 justify #1707' v2.5.3: en: - '[enhance] prop:labelAlign support justify #1574' zh-CN: - '[enhance] labelAlign 支持 justify #1574' v2.5.1: en: - '[feature] Add slot:title #1650' zh-CN: - '[feature] 添加 slot:title #1650' v2.3.2: en: - '[feature] prop:gutter could be a number' zh-CN: - '[feature] gutter 属性允许为数字类型' v2.1.1-rc.2: en: - '[feature] Support title margin-top variable' zh-CN: - '[feature] 支持 margin-top 等 less 变量' ================================================ FILE: src/components/group/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Group', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('group') }) }) ================================================ FILE: src/components/group-title/index.vue ================================================ ================================================ FILE: src/components/group-title/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('GroupTitle', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('group-title') }) }) ================================================ FILE: src/components/icon/index.vue ================================================ ================================================ FILE: src/components/icon/metas.yml ================================================ category: en: Basic zh-CN: 基础组件 icon: '' after_extra: | ``` html ``` tags: en: - icon zh-CN: - 图标 props: type: type: String default: '' en: icon name, optional values are shown in demo zh-CN: 图标名字,可选值见demo is-msg: type: Boolean default: false en: if used as msg icon. Icon's font-size will be bigger(93px) than default zh-CN: 是否用作消息提示页面图标,图标尺寸会使用93px changes: v2.1.1-rc.4: en: - '[change] No more built-in `icon-big` classname, use `is-msg` instead. #988 @greedying' zh-CN: - '[change] 不再内置类名 `icon-big`, 改用 prop:`is-msg` #988 @greedying' v2.1.0: zh-CN: - '[feature] 更新`WeUI`样式到最新版本' - '[change] `type`参数由下划线变为中划线,组件内部做了兼容保证升级时无影响' ================================================ FILE: src/components/icon/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Icon', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('icon') }) }) ================================================ FILE: src/components/inline-calendar/index.vue ================================================ week_day_0: en: Su zh-CN: 日 week_day_1: en: Mo zh-CN: 一 week_day_2: en: Tu zh-CN: 二 week_day_3: en: We zh-CN: 三 week_day_4: en: Th zh-CN: 四 week_day_5: en: Fr zh-CN: 五 week_day_6: en: Sa zh-CN: 六 ================================================ FILE: src/components/inline-calendar/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - form - calendar zh-CN: - 日历 - 表单 props: value: type: String, Array default: '' en: calendar value, use `v-modle` for binding. String (including empty) means single seleciton; array (including empty array) means multiple selection. zh-CN: 当前选中日期,使用`v-model`绑定。值为字符串(包括空字符串)时表示单选日期,为数组(包括空数组)时表示多选。 render-month: type: Array default: '' en: specify which date to render, for example [2018, 8] zh-CN: 指定渲染日期,如 [2018, 8] start-date: type: String default: '' en: 'start date with format `YYYY-MM-dd`' zh-CN: '起始日期,格式为 `YYYY-MM-dd`' end-date: type: String default: '' en: 'end date with format `YYYY-MM-dd`' zh-CN: '结束日期,格式为`YYYY-MM-dd`' show-last-month: type: Boolean default: true en: if show last month's days zh-CN: 是否显示上个月的日期 show-next-month: type: Boolean default: true en: if show next month's days zh-CN: 是否显示下个月的日期 highlight-weekend: type: Boolean default: false en: if highlight weekend days zh-CN: 是否高亮周末 return-six-rows: type: Boolean default: true en: if always show six rows zh-CN: 是否总是渲染6行日期 hide-header: type: Boolean default: false en: if hiding header zh-CN: 是否隐藏日历头部 hide-week-list: type: Boolean default: false en: if hiding week list zh-CN: 是否隐藏星期列表 replace-text-list: type: 'Object' default: '' en: replace list, for example, {'TODAY':'今'} zh-CN: "替换列表,可以将默认的日期换成文字,比如今天的日期替换成今,{'TODAY':'今'}" weeks-list: type: Array default: "['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']" en: 'week list, starting from `Sunday`' zh-CN: 星期列表,从周日开始 render-function: type: Function default: '' en: render function to add extra contents, params `(line, row, dateData)` zh-CN: 用于为特定日期添加额外的html内容,参数为(行index,列index,日期详细属性) render-on-value-change: type: Boolean default: true en: if re-render calendar if value changes, should set to false when showing multi months zh-CN: 当日期变化时是否重新渲染日历,如果是渲染了多个日历的话需要设为false disable-past: type: Boolean default: false en: if disable the past days, can work with start-date zh-CN: 禁止选择过去的日期,该选项可以与 start-date 同时使用 disable-future: type: Boolean default: false en: if disable future days, can work with end-date zh-CN: 禁止选择未来的日期,该选项可以 end-date 同时使用 marks: version: v2.6.0 type: Array en: (beta) style marks for specified date list zh-CN: (beta) 自定义日期标记 disable-weekend: version: v2.7.0 type: Boolean default: false en: whether disable weekends zh-CN: 是否禁用周六日 disable-date-function: version: v2.7.0 type: Function params: '`(date)`' en: check if a date should be disabled zh-CN: 自定义标记特定日期是否应该禁用,返回 true 表示禁用,false 表示不禁用,不返回表示和原有逻辑一致(这样不影响和 disable-weekend 等禁用属性同时使用) slots: each-day: version: v2.3.5 en: you can custom how to display each day zh-CN: 用以自定义每一天的显示渲染,推荐使用该 slot 来替代 render-function events: on-change: en: emits when value is changed zh-CN: 值变化时触发 on-view-change: version: v2.5.12 params: '(data, index)' en: fires when render month is changed zh-CN: 渲染月份变化时触发。初始化时会触发一次,如果不希望处理初始化时的触发,可以检查第二个参数是否为 0 on-select-single-date: version: 2.7.6 params: '(currentValue)' en: fires when date is selected in radio mode zh-CN: 单选模式下选中日期时触发 methods: getDates: version: v2.5.11 en: get current date list zh-CN: 获取当前日期列表 switchViewToToday: version: v2.5.12 en: render current month zh-CN: 渲染当天所在月份 switchViewToMonth: version: v2.5.12 params: (year, month) en: render specified year and month zh-CN: 渲染特定年月日期 switchViewToCurrentValue: version: v2.5.12 en: render with current value's month zh-CN: 渲染当前值所在月份 changes: v2.7.6: en: - '[feature] add event:on-select-single-date' zh-CN: - '[feature] 添加事件 on-select-single-date' v2.7.3: en: - '[fix] disable-date-function result conflicts with disable-weekend #2189' zh-CN: - '[fix] 修复 disable-date-function 取值与禁用周末逻辑冲突 #2189' v2.7.2: en: - '[fix] fix reset-value issue in multiple mode #2160' - '[fix] fix props missing in calendar #2105' - '[fix] fix disabled weekends still selectable #2178' zh-CN: - '[fix] 修复多选情况下重置值时没有正确渲染的问题 #2160' - '[fix] 修复部分属性没有继承 inline-calendar 的问题 #2105' - '[fix] 修复禁用周末日期仍能点击的问题 #2178' v2.7.0: en: - '[enhance] call render function on created hook instead of mounted(ssr)' - '[feature] add prop:disable-weekend #2105' - '[feature] add prop:disable-date-function #2105' - '[fix] remove useless re-render function #2066' zh-CN: - '[enhance] 在 created hook 执行渲染而不是 mounted(ssr)' - '[feature] 添加属性 prop:disable-weekend 来禁用周末 #2105' - '[feature] 添加属性 prop:disable-date-function 自定义标记禁用日期 #2105' - '[fix] 移除重复的多次重复渲染 #2066' v2.6.0: en: - '[feature] (beta) Support prop:marks to style specified date item' - '[fix] fix i18n for days' zh-CN: - '[feature] (beta) 支持属性 marks 用以自定义特定日期显示样式' - '[fix] 修复星期文字 i18n' v2.5.12: en: - '[feature] add method:switchViewToToday switchViewToMonth' - '[feature] support event:on-view-change' zh-CN: - '[feature] 支持 switchViewToToday, switchViewToMonth 等手动渲染方法' - '[feature] 支持事件 on-view-change,方便切换月份时请求数据' v2.5.11: en: - '[feature] support method:getDates' - '[feature] support less variable @calendar-bg-color' zh-CN: - '[feature] 支持方法 getDates 用于需要获取渲染日期列表的场合' - '[feature] 支持 less 变量 @calendar-bg-color' v2.5.9: en: - '[fix] donot set value to empty string when clicking on current selected item #1862' zh-CN: - '[fix] 点击当前日期项时不把值置为空 #1862' v2.5.8: en: - '[fix] fix on-change event fires twice #1847' zh-CN: - '[fix] 修复 on-change 事件触发两次问题 #1847' v2.5.5: en: - '[feature] support multi select #1446 #1467' zh-CN: - '[feature] 支持多选 #1446 #1467' v2.4.0: en: - '[enhance] re-render when render-month is changed' zh-CN: - '[enhance] 当 render-month 变化时重新渲染日历' v2.3.8: en: - '[enhance] prevent from clicking invisiable date #1564' zh-CN: - '[enhance] 不可见日期不可点击 #1564' v2.3.6: en: - '[change] render-function params day => date #1361' zh-CN: - '[change] render-function 参数 day => date(在 3.0 版本前不会影响目前使用)#1361' v2.3.5: en: - '[feature] Support slot:each-day' zh-CN: - '[feature] 支持自定义每一天的 html 渲染 slot:each-day' v2.3.1: en: - '[fix] Fix render-function-generated-content visibility issue #1409' zh-CN: - '[fix] 修复 render-function 生成的内容错误显示问题 #1409' v2.2.2: en: - "[enhance] when replace text of date is empty string, span of date will not show" zh-CN: - '[enhance] 当日期的替代文字为空字符串时,包含日期的 `span` 元素将不显示' v2.2.1-rc.2: en: - "[enhance] Now next month's day can be clicked and will automatically switch to next month's view #1192" - "[fix] fix start-date and end-date not reactive #1219" zh-CN: - '[enhance] 可以点击下个月日期并自动切换到下个月 #1192' - '[fix] 修复 start-date 和 end-date 变化后不重新渲染的问题 #1219' v2.1.1-rc.10: en: - '[feature] Add more less variables. #896' zh-CN: - '[feature] 支持多个 less 变量. #896' v2.1.1-rc.5: en: - '[fix] fix render-function not reactive' zh-CN: - '[fix] 修复 render-function 变化不重新渲染的问题' ================================================ FILE: src/components/inline-calendar/props.js ================================================ export default () => ({ value: { type: [String, Array], default: '' }, renderMonth: { type: Array, // [2018, 8] default () { return [null, null] } }, startDate: { type: String }, endDate: { type: String }, showLastMonth: { type: Boolean, default: true }, showNextMonth: { type: Boolean, default: true }, highlightWeekend: { type: Boolean, default: false }, returnSixRows: { type: Boolean, default: true }, hideHeader: { type: Boolean, default: false }, hideWeekList: { type: Boolean, default: false }, replaceTextList: { type: Object, default () { return {} } }, weeksList: { type: Array, validator (val) { if (val) { return val.length === 7 || val.length === 0 } return true } }, renderFunction: { type: Function, default: () => '' }, renderOnValueChange: { type: Boolean, default: true }, disablePast: { type: Boolean, default: false }, disableFuture: { type: Boolean, default: false }, disableWeekend: { type: Boolean, default: false }, disableDateFunction: Function, marks: { type: Array, default () { return [] } } }) ================================================ FILE: src/components/inline-calendar/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('InlineCalendar', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('inline-calendar') }) }) ================================================ FILE: src/components/inline-calendar/util.js ================================================ import format from '../datetime/format' export function zero (n) { return n < 10 ? '0' + n : n } export function splitValue (value) { let split = value.split('-') return { year: parseInt(split[0], 10), month: parseInt(split[1], 10) - 1, day: parseInt(split[2], 10) } } export function getPrevTime (year, month) { if (month === 0) { return { month: 11, year: year - 1 } } else { return { year, month: month - 1 } } } export function getNextTime (year, month) { if (month === 11) { return { month: 0, year: year + 1 } } else { return { year, month: month + 1 } } } function getTime (str) { if (typeof str === 'number') { return str } return typeof str === 'string' ? new Date(str.replace(/-/g, '/')).getTime() : str.getTime() } export function isBetween (value, disablePast, disableFuture, rangeBegin, rangeEnd) { const { start, end } = getRange(disablePast, disableFuture, rangeBegin, rangeEnd) value = getTime(value) let isGte = start ? value >= getTime(start) : true let isLte = end ? value <= getTime(end) : true return isGte && isLte } function getRange (disablePast = false, disableFuture = false, rangeBegin, rangeEnd) { let startOfToday = new Date() startOfToday.setHours(0, 0, 0, 0) if (disablePast) { if (!rangeBegin) { rangeBegin = startOfToday } else { rangeBegin = Math.max(startOfToday.getTime(), getTime(rangeBegin)) } } if (disableFuture) { if (!rangeEnd) { rangeEnd = startOfToday } else { rangeEnd = Math.min(startOfToday.getTime(), getTime(rangeEnd)) } } return { start: rangeBegin, end: rangeEnd } } export function getDays ({year, month, value, rangeBegin, rangeEnd, returnSixRows = true}) { let today = format(new Date(), 'YYYY-MM-DD') let _splitValue = splitValue(value || today) // if year or month is not specified, get them from value if (typeof year !== 'number' || typeof month !== 'number' || month < 0) { year = _splitValue.year month = _splitValue.month } var firstDayOfMonth = new Date(year, month, 1).getDay() var lastDateOfMonth = new Date(year, month + 1, 0).getDate() var lastDayOfLastMonth = new Date(year, month, 0).getDate() var i var line = 0 var temp = [] for (i = 1; i <= lastDateOfMonth; i++) { var dow = new Date(year, month, i).getDay() // 第一行 if (dow === 0) { temp[line] = [] } else if (i === 1) { temp[line] = [] var k = lastDayOfLastMonth - firstDayOfMonth + 1 for (let j = 0; j < firstDayOfMonth; j++) { let rs = getPrevTime(year, month) temp[line].push({ year: rs.year, month: rs.month, month_str: rs.month + 1, day: k, isLastMonth: true }) k++ } } let _format = format(new Date(year + '/' + (month + 1) + '/' + i), 'YYYY/MM/DD') let options = { year: year, month: month, month_str: month + 1, day: i, isCurrent: value && format(new Date(value), 'YYYY/MM/DD') === _format, isToday: format(new Date(), 'YYYY/MM/DD') === _format } temp[line].push(options) if (dow === 6) { line++ } else if (i === lastDateOfMonth) { let k = 1 for (dow; dow < 6; dow++) { let rs = getNextTime(year, month) temp[line].push({ year: rs.year, month: rs.month, month_str: rs.month + 1, day: k, isNextMonth: true }) k++ } } } if (returnSixRows && temp.length === 5) { let rs = getNextTime(year, month) let start = temp[4][6].isNextMonth ? temp[4][6].day : 0 temp[5] = [] for (let i = 0; i < 7; i++) { let day = ++start temp[5].push({ year: rs.year, month: rs.month, month_str: rs.month + 1, day: day, isNextMonth: true }) } } // 2026-02, there is only 4 lines if (returnSixRows && temp.length === 4) { let rs = getNextTime(year, month) let start = 0 temp[4] = [] temp[5] = [] for (let i = 0; i < 7; i++) { let day = ++start temp[4].push({ year: rs.year, month: rs.month, month_str: rs.month + 1, day: day, isNextMonth: true }) day = ++start temp[5].push({ year: rs.year, month: rs.month, month_str: rs.month + 1, day: day, isNextMonth: true }) } } return { year: year, month: month, month_str: month + 1, days: temp.map(line => { /** * https://github.com/airyland/vux/issues/1361 * @todo day will be changed to weekDay after v3.0 */ line.map((item, index) => { item.date = item.day item.weekDay = index item.isWeekend = index === 0 || index === 6 item.formatedDate = format(new Date(`${item.year}/${item.month_str}/${item.date}`), 'YYYY-MM-DD') return item }) return line }) } } ================================================ FILE: src/components/inline-desc/index.vue ================================================ ================================================ FILE: src/components/inline-desc/metas.yml ================================================ intro: en: it is the child component of cell, you cannot use it alone zh-CN: cell的子组件,不能单独使用 slots: default: en: text content zh-CN: 文字内容 ================================================ FILE: src/components/inline-desc/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('InlineDesc', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('inline-desc') }) }) ================================================ FILE: src/components/inline-loading/index.vue ================================================ ================================================ FILE: src/components/inline-loading/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' extra: en: 'require vux@^2.3.4' zh-CN: '版本要求 vux@^2.3.4' changes: v2.3.4: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/inline-loading/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('InlineLoading', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('inline-loading') }) }) ================================================ FILE: src/components/inline-x-number/index.vue ================================================ ================================================ FILE: src/components/inline-x-number/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' props: width: type: String default: 50px en: width for number zh-CN: 数字所占据的宽度 button-style: type: String default: square en: button style, can be 'round' zh-CN: 按钮样式,可选['round'] min: type: Number default: '' en: min value zh-CN: 最小值 max: type: number default: '' en: max value zh-CN: 最大值 changes: v2.5.2: en: - '[feature] now inline-x-number is available' zh-CN: - '[feature] 单独的 inline-x-number 组件可以正常使用了' ================================================ FILE: src/components/inline-x-number/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('InlineXNumber', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('inline-x-number') }) }) ================================================ FILE: src/components/inline-x-switch/index.vue ================================================ ================================================ FILE: src/components/inline-x-switch/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - switch - form zh-CN: - 表单 - 开关 props: disabled: type: Boolean default: false en: if disabled zh-CN: 是否不可点击 value: type: Boolean default: false en: "component's value, use `v-model` for binding" zh-CN: '表单值, 使用`v-model`绑定' value-map: type: Array default: '[false, true]' en: customize not-boolean values zh-CN: 用于自定义 false 和 true 映射的实际值,用于方便处理比如接口返回了 0 1 这类非 boolean 值的情况 events: on-change: params: '`(value)`' en: triggers when value change, params is (currentValue) zh-CN: 值变化时触发,参数为 (currentValue) changes: v2.9.0: en: - '[feature] new component #2624' zh-CN: - '[feature] 新组件 #2624' ================================================ FILE: src/components/inline-x-switch/style.less ================================================ @import '../../styles/weui/widget/weui_cell/weui_form/weui_form_common'; @import '../../styles/weui/widget/weui_cell/weui_switch'; .weui-cell_switch .weui-cell__ft { font-size: 0; position: relative; } input.weui-switch[disabled] { opacity: @switch-disabled-opacity; } .vux-x-switch.weui-cell_switch { padding-top: 6px; padding-bottom: 6px; } .vux-x-switch-overlay { width: 60px; height: 50px; position: absolute; right: 0; top: 0; opacity: 0; } ================================================ FILE: src/components/load-more/index.vue ================================================ ================================================ FILE: src/components/load-more/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' props: show-loading: type: Boolean default: true en: if show loading icon zh-CN: 是否显示 loading 图标 tip: en: tip text. Show dot if there is no text and loading icon zh-CN: 提示文字,如果没有显示图标也没有显示文字,则显示点 changes: 2.7.6: en: - "[feature] don't need to set the `background-color`" zh-CN: - '[feature] 不再需要设置 `background-color`' v2.6.0: en: - '[fix] fix `tip` is empty and `show-loading` is `false`, the background color is not correct' - '[feature] support less variable @load-more-tip-background-color' zh-CN: - '[fix] 修复 `tip` 为空,且 `show-loading` 为 `false` 时背景颜色不对的问题 ' - '[feature] 支持 @load-more-tip-background-color 全局设置文字背景颜色' v2.0.0: en: - '[feature] Add `load-more` component' zh-CN: - '[feature] 增加`load-more`组件' ================================================ FILE: src/components/load-more/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('LoadMore', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('load-more') }) }) ================================================ FILE: src/components/loading/index.vue ================================================ loading: en: loading zh-CN: 加载中 ================================================ FILE: src/components/loading/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - loading - dialog zh-CN: - 加载 - 弹窗 - 提示 extra: | 该组件支持以`plugin`形式调用: ``` js import { LoadingPlugin } from 'vux' Vue.use(LoadingPlugin) // 或者umd方式 // 引入构建的js文件 Vue.use(vuxLoadingPlugin) ``` ``` js // 显示 this.$vux.loading.show({ text: 'Loading' }) // 隐藏 this.$vux.loading.hide() // 获取显示状态 this.$vux.loading.isVisible() // true or false, v2.9.1 版本支持 ``` ::: tip loading同样支持在vue外直接使用,请参照 [https://github.com/airyland/vux/blob/v2/docs/examples/loading.html](https://github.com/airyland/vux/blob/v2/docs/examples/loading.html) ::: ::: tip 从v2.7.8版本开始以组件形式调用增加`delay`参数,从而实现延时显示. ::: props: show: type: Boolean default: false en: visibility of the component. Use `v-model` for binding before v2.5.7, otherwise use `:show` zh-CN: 显示状态,在 v2.5.7 前使用`v-model`绑定,后面直接使用 `:show` 绑定 text: type: String default: '加载中' en: loading text, use empty string to hide loading text zh-CN: 提示文字,值为空字符时隐藏提示文字 position: type: String default: 'fixed' en: position, default is `fixed`, you can use `absolute` zh-CN: 定位方式,默认为`fixed`,在100%的布局下用`absolute`可以避免抖动 transition: type: String default: vux-mask en: transition name zh-CN: 显示动画名字 slots: default: en: content area zh-CN: 提示文字区域 changes: v2.9.1: en: - '[feature] add isVisible function for LoadingPlugin #2704' - '[fix] fix covered by element when using scroll plugins' zh-CN: - '[feature] 添加 isVisible 获取当前显示状态 #2704' - '[fix] 修复使用类 scroll 组件时会被覆盖的问题' v2.9.0: en: - '[fix] add variable loading-z-index #2484' - '[enhance] support using empty text to hide loading text #2566' zh-CN: - '[fix] 添加变量 loading-z-index 避免与 toast-z-index 冲突 #2484' - '[enhance] 支持使用空字符隐藏提示文字 #2566' v2.5.7: en: - '[fix] fix vue@2.4 no setter warning, use :show instead of v-model #1798' zh-CN: - '[fix] 修复 vue@2.4 no setter 警告,可以直接用 :show 绑定 #1798' v2.5.0: en: - '[feature] Add prop:transition' zh-CN: - '[feature] 支持 prop:transition' v2.1.1-rc.11: en: - '[enhance] Reset prop data when used as plugin' - '[enhance] Add fade transition' zh-CN: - '[enhance] 以插件形式使用时强制重置属性值' - '[enhance] 添加渐现动画' v2.0.1: zh-CN: - '[fix] i18n 无配置' ================================================ FILE: src/components/loading/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Loading', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('loading') }) }) ================================================ FILE: src/components/map.json ================================================ { "NOTICE": "THIS FILE IS AUTOGENERATED BY npm run build-docs", "Actionsheet": "src/components/actionsheet/index.vue", "Agree": "src/components/agree/index.vue", "AjaxPlugin": "src/plugins/ajax/index.js", "Alert": "src/components/alert/index.vue", "AlertModule": "src/plugins/alert/module", "AlertPlugin": "src/plugins/alert/index.js", "AppPlugin": "src/plugins/app/index.js", "Array2stringFilter": "src/filters/array2String.js", "Badge": "src/components/badge/index.vue", "base64": "src/tools/base64/index.js", "Blur": "src/components/blur/index.vue", "Box": "src/components/box/index.vue", "BusPlugin": "src/plugins/bus/index.js", "ButtonTab": "src/components/button-tab/button-tab.vue", "ButtonTabItem": "src/components/button-tab/button-tab-item.vue", "Calendar": "src/components/calendar/index.vue", "Card": "src/components/card/index.vue", "Cell": "src/components/cell/index.vue", "CellBox": "src/components/cell-box/index.vue", "CellFormPreview": "src/components/cell-form-preview/index.vue", "Checker": "src/components/checker/checker.vue", "CheckerItem": "src/components/checker/checker-item.vue", "CheckIcon": "src/components/check-icon/index.vue", "Checklist": "src/components/checklist/index.vue", "ChinaAddressData": "src/datas/china_address.json", "ChinaAddressV1Data": "src/datas/china_address_v1.json", "ChinaAddressV2Data": "src/datas/china_address_v2.json", "ChinaAddressV3Data": "src/datas/china_address_v3.json", "ChinaAddressV4Data": "src/datas/china_address_v4.json", "ChinamobileTool": "src/tools/validator/chinaMobile.js", "ClickOutsideDirective": "src/directives/click-outside/index.js", "Clocker": "src/components/clocker/index.vue", "CloseDialogsPlugin": "src/plugins/close-dialogs/index.js", "ColorPicker": "src/components/color-picker/index.vue", "ConfigPlugin": "src/plugins/config/index.js", "Confirm": "src/components/confirm/index.vue", "ConfirmPlugin": "src/plugins/confirm/index.js", "cookie": "src/tools/cookie/index.js", "Countdown": "src/components/countdown/index.vue", "Countup": "src/components/countup/index.vue", "dateFormat": "src/tools/date/format.js", "dateRange": "src/tools/date/range.js", "Datetime": "src/components/datetime/index.vue", "DatetimePlugin": "src/plugins/datetime/index.js", "DatetimeRange": "src/components/datetime-range/index.vue", "DatetimeView": "src/components/datetime-view/index.vue", "debounce": "src/tools/debounce/index.js", "Demobasic": "src/components/fullpage/DemoBasic.vue", "Demoindex": "src/components/popover/DemoIndex.vue", "DevicePlugin": "src/plugins/device/index.js", "DevTip": "src/components/dev-tip/index.vue", "Divider": "src/components/divider/index.vue", "Drawer": "src/components/drawer/index.vue", "Field": "src/components/x-form/field.vue", "Flexbox": "src/components/flexbox/flexbox.vue", "FlexboxItem": "src/components/flexbox/flexbox-item.vue", "Flow": "src/components/flow/flow.vue", "FlowLine": "src/components/flow/flow-line.vue", "FlowState": "src/components/flow/flow-state.vue", "Form": "src/components/x-form/form.vue", "FormatTimeFilter": "src/filters/format-time.js", "FormPreview": "src/components/form-preview/index.vue", "FriendlyTimeFilter": "src/filters/friendly-time.js", "Fullpage": "src/components/fullpage/index.vue", "Grid": "src/components/grid/grid.vue", "GridItem": "src/components/grid/grid-item.vue", "Group": "src/components/group/index.vue", "GroupTitle": "src/components/group-title/index.vue", "Icon": "src/components/icon/index.vue", "InlineCalendar": "src/components/inline-calendar/index.vue", "InlineDesc": "src/components/inline-desc/index.vue", "InlineLoading": "src/components/inline-loading/index.vue", "InlineXNumber": "src/components/inline-x-number/index.vue", "InlineXSwitch": "src/components/inline-x-switch/index.vue", "InviewDirective": "src/directives/inview/index.js", "Loading": "src/components/loading/index.vue", "LoadingPlugin": "src/plugins/loading/index.js", "LoadMore": "src/components/load-more/index.vue", "LocalePlugin": "src/plugins/locale/index.js", "Marquee": "src/components/marquee/marquee.vue", "MarqueeItem": "src/components/marquee/marquee-item.vue", "Masker": "src/components/masker/index.vue", "md5": "src/tools/md5/index.js", "Msg": "src/components/msg/index.vue", "Name2valueFilter": "src/filters/name2value.js", "numberComma": "src/tools/number/comma.js", "numberPad": "src/tools/number/pad.js", "numberRandom": "src/tools/number/random.js", "numberRange": "src/tools/number/range.js", "NumberRoller": "src/components/number-roller/index.vue", "Panel": "src/components/panel/index.vue", "Picker": "src/components/picker/index.vue", "Popover": "src/components/popover/index.vue", "Popup": "src/components/popup/index.vue", "PopupHeader": "src/components/popup-header/index.vue", "PopupPicker": "src/components/popup-picker/index.vue", "PopupRadio": "src/components/popup-radio/index.vue", "Preview": "src/components/uploader/preview.vue", "Previewer": "src/components/previewer/index.vue", "Qrcode": "src/components/qrcode/index.vue", "querystring": "src/tools/querystring/index.js", "Radio": "src/components/radio/index.vue", "Range": "src/components/range/index.vue", "Rater": "src/components/rater/index.vue", "Scroller": "src/components/scroller/index.vue", "Search": "src/components/search/index.vue", "Selector": "src/components/selector/index.vue", "Shake": "src/components/shake/index.vue", "Spinner": "src/components/spinner/index.vue", "Step": "src/components/step/step.vue", "StepItem": "src/components/step/step-item.vue", "Sticky": "src/components/sticky/index.vue", "stringTrim": "src/tools/string/trim.js", "Swipeout": "src/components/swipeout/swipeout.vue", "SwipeoutButton": "src/components/swipeout/swipeout-button.vue", "SwipeoutItem": "src/components/swipeout/swipeout-item.vue", "Swiper": "src/components/swiper/swiper.vue", "SwiperItem": "src/components/swiper/swiper-item.vue", "Tab": "src/components/tab/tab.vue", "Tabbar": "src/components/tabbar/tabbar.vue", "TabbarItem": "src/components/tabbar/tabbar-item.vue", "TabItem": "src/components/tab/tab-item.vue", "throttle": "src/tools/throttle/index.js", "Timeline": "src/components/timeline/timeline.vue", "TimelineItem": "src/components/timeline/timeline-item.vue", "Tip": "src/components/tip/index.vue", "Toast": "src/components/toast/index.vue", "ToastPlugin": "src/plugins/toast/index.js", "TransferDom": "src/directives/transfer-dom/index.js", "TransferDomDirective": "src/directives/transfer-dom/index.js", "trim": "src/tools/string/trim", "Uploader": "src/components/uploader/index.vue", "Value2nameFilter": "src/filters/value2name.js", "VArea": "src/components/v-chart/v-area.vue", "VAxis": "src/components/v-chart/v-axis.vue", "VBar": "src/components/v-chart/v-bar.vue", "VChart": "src/components/v-chart/v-chart.vue", "VGuide": "src/components/v-chart/v-guide.vue", "Video": "src/components/video/index.vue", "ViewBox": "src/components/view-box/index.vue", "VLegend": "src/components/v-chart/v-legend.vue", "VLine": "src/components/v-chart/v-line.vue", "VPie": "src/components/v-chart/v-pie.vue", "VPoint": "src/components/v-chart/v-point.vue", "VScale": "src/components/v-chart/v-scale.vue", "VTooltip": "src/components/v-chart/v-tooltip.vue", "VuxComponentListData": "src/datas/vux_component_list.json", "WechatEmotion": "src/components/wechat-emotion/index.vue", "WechatPlugin": "src/plugins/wechat/index.js", "WeekCalendar": "src/components/week-calendar/index.vue", "WepayInput": "src/components/wepay-input/index.vue", "XAddress": "src/components/x-address/index.vue", "XButton": "src/components/x-button/index.vue", "XCircle": "src/components/x-circle/index.vue", "XDialog": "src/components/x-dialog/index.vue", "XHeader": "src/components/x-header/index.vue", "XHr": "src/components/x-hr/index.vue", "XImg": "src/components/x-img/index.vue", "XInput": "src/components/x-input/index.vue", "XNumber": "src/components/x-number/index.vue", "XProgress": "src/components/x-progress/index.vue", "XSwitch": "src/components/x-switch/index.vue", "XTable": "src/components/x-table/index.vue", "XTextarea": "src/components/x-textarea/index.vue" } ================================================ FILE: src/components/marquee/marquee-item.vue ================================================ ================================================ FILE: src/components/marquee/marquee.vue ================================================ ================================================ FILE: src/components/marquee/metas.yml ================================================ category: en: Data Display 'zh-CN': 数据展示 icon: '' import_code: "import { Marquee, MarqueeItem } from 'vux'" items: - marquee - marquee-item marquee: props: interval: type: Number default: 2000 en: switch interval time zh-CN: 切换时间间隙 duration: type: Number default: 300 en: animation time zh-CN: 切换动画时间 direction: default: up en: direction of animation, can be in ['up', 'down'] zh-CN: 切换方向,可选['up', 'down'] item-height: type: Number en: item height, set value if marquee initial state is hidden zh-CN: 条目高度,当默认状态为隐藏时你需要设置值,否则组件渲染时会获取不到正确高度 slots: default: en: container of marquee items zh-CN: 内容插槽 marquee-item: slots: default: en: content slot zh-CN: 内容插槽 changes: v2.5.10: en: - '[feature] Support prop:item-height #1885' zh-CN: - '[feature] 支持 prop:item-height #1885' v2.1.1-rc.12: en: - '[feature] Support async marquee-item' zh-CN: - '[feature] 支持异步数据渲染' v2.1.0: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/marquee/test.js ================================================ import Comp from './marquee.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Marquee', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('marquee') }) }) ================================================ FILE: src/components/masker/converter.js ================================================ /*! * HEX <=> RGB Conversion * Copyright(c) 2011 Daniel Lamb * MIT Licensed */ export function toRGB (color) { let num = parseInt(color, 16) return [num >> 16, num >> 8 & 255, num & 255] } export function toHex (red, green, blue) { return ((blue | green << 8 | red << 16) | 1 << 24).toString(16).slice(1) } ================================================ FILE: src/components/masker/index.vue ================================================ ================================================ FILE: src/components/masker/metas.yml ================================================ category: en: Deprecated zh-CN: 不再维护 icon: '' props: color: type: String default: '0, 0, 0' en: the mask's color in rgb format, for example, '0, 0, 0' zh-CN: 遮罩颜色,rgb值,'0, 0, 0' opacity: type: Number default: 0.5 en: the opacity of the mask zh-CN: 遮罩透明度 fullscreen: type: Boolean version: v2.1.1-rc.14 default: false en: if masker is fullscreen zh-CN: 遮罩是否全屏 slots: default: en: content below the mask zh-CN: 背景内容,位于遮罩下方,一般为图片 content: en: content above the mask zh-CN: 遮罩上方内容,一般显示标题消息 changes: v2.1.1-rc.14: en: - '[feature] Support prop:fullscreen' zh-CN: - '[feature] 支持通过prop设置是否全屏显示' ================================================ FILE: src/components/masker/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Masker', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('masker') }) }) ================================================ FILE: src/components/metas.yml ================================================ pullup: icon:  pulldown: icon:  comment: icon:  orientation: icon:  shake: icon:  wechat: icon:  yi: icon:  ================================================ FILE: src/components/msg/index.vue ================================================ ================================================ FILE: src/components/msg/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' extra: zh-CN: | `prop:buttons` 列表是 `slot:buttons` 插槽的默认显示内容,如果 `prop:buttons` 无法满足需求,可以使用 `slot:buttons` 引入 `x-button` 组件。 ``` html ``` props: title: en: text to show operation status zh-CN: 操作状态提示文字 description: en: description zh-CN: 描述文字 icon: default: success en: 'icon type. You can use `success`, `warn`, `info`, `waiting`' zh-CN: '图标类型,可选值有 `success`, `warn`, `info`, `waiting`' buttons: type: Array en: 'buttons list. A button item includes `text`, `type`(the same as x-button type), `link`, `onClick`' zh-CN: '操作按钮列表,一个按钮对象包含`text`, `type`(和x-button组件type一致), `link`, `onClick`' slots: buttons: en: used for custom buttons zh-CN: 自定义按钮区域元素 description: en: used for custom description zh-CN: 自定义描述文字内容 changes: v2.9.0: en: - '[change] prop:buttons displays as slot:buttons default content' zh-CN: - '[change] prop:buttons 数组作为 slot:buttons 默认内容' v2.1.0: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/msg/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Msg', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('msg') }) }) ================================================ FILE: src/components/number-roller/index.vue ================================================ ================================================ FILE: src/components/number-roller/lib.js ================================================ const Roller = class { constructor (opts) { this.container = typeof opts.container === 'string' ? document.querySelector(opts.container) : opts.container this.width = opts.width || 1 if (!this.container) { throw Error('no container') } this.container.style.overflow = 'hidden' this.rollHeight = parseInt(window.getComputedStyle(this.container).height) if (this.rollHeight < 1) { this.container.style.height = '20px' this.rollHeight = 20 } this.setWidth() } roll (n) { var self = this this.number = parseInt(n) + '' if (this.number.length < this.width) { this.number = new Array(this.width - this.number.length + 1).join('0') + this.number } else if (this.number.length > this.width) { this.width = this.number.length this.setWidth() } Array.prototype.forEach.call(this.container.querySelectorAll('.num'), function (item, i) { var currentNum = parseInt(item.querySelector('div:last-child').innerHTML) var goalNum = parseInt(self.number[i]) var gapNum = 0 var gapStr = '' if (currentNum === goalNum) { return } else if (currentNum < goalNum) { gapNum = goalNum - currentNum for (let j = currentNum; j < goalNum + 1; j++) { gapStr += '
    ' + j + '
    ' } } else { gapNum = 10 - currentNum + goalNum for (let j = currentNum; j < 10; j++) { gapStr += '
    ' + j + '
    ' } for (let j = 0; j < goalNum + 1; j++) { gapStr += '
    ' + j + '
    ' } } item.style.cssText += '-webkit-transition-duration:0;s-webkit-transform:translateY(0)' item.innerHTML = gapStr let time = gapNum * (1 / 9) setTimeout(() => { item.style.cssText += '-webkit-transition-duration:' + time + 's;-webkit-transform:translateY(-' + self.rollHeight * gapNum + 'px)' }, 50) }) } setWidth (n) { n = n || this.width var str = '' for (var i = 0; i < n; i++) { str += '
    0
    ' } this.container.innerHTML = str } } export default Roller ================================================ FILE: src/components/orientation/index.js ================================================ /** * v-orientaion="landscape" v-orientaion="portrait" */ import Orientation from './orientation' export default { bind () { const _this = this const value = this.expression const _value = value.toString()[0].toUpperCase() + value.toString().slice(1) if (Orientation['is' + _value]()) { this.el.style.display = 'block' } else { this.el.style.display = 'none' } Orientation.change(function (e) { let info = Orientation.getInfo() _this.el.style.display = info[value] ? 'block' : 'none' }) }, update () { }, unbind () { } } ================================================ FILE: src/components/orientation/orientation.js ================================================ /** * 横竖屏切换监听器 * @author ningzbruc@gmail.com * @gitbub http://gitbub.com/kingback/orientation/ * @date 2014-01-15 * @version 0.0.1 */ // 原生不支持用resize模拟 var ORIENTATION_CHANGE = 'orientationchange' var RESIZE = 'resize' var EVT_ORIENTATION_CHANGE = 'onorientationchange' in window ? ORIENTATION_CHANGE : RESIZE var FUNCTION = 'function' var ON = 'on' var AFTER = 'after' /** * 混入对象属性 * @method merge * @param {Object} s 提供对象 * @return {Object} r 接收对象 * @private */ function merge (s) { var r = {} var k if (s) { for (k in s) { if (s.hasOwnProperty(k)) { r[k] = s[k] } } } return r } /** * 绑定方法上下文 * @method bind * @param {Function} fn * @param {Object} context * @return {Function} * @private */ function bind (fn, context) { return fn.bind ? fn.bind(context) : function () { fn.apply(context, arguments) } } /** * 横竖屏切换监听器 * @constructor Orientation * @class */ function Orientation () { this.init.apply(this, arguments) } Orientation.prototype = { /** * 构造器 * @property constructor * @public */ constructor: Orientation, /** * 初始化 * @method init * @public */ init: function (cfg) { this._cfg = merge({ delay: 400 }, cfg) // 事件订阅数组 this._subs = { on: [], after: [] } // 当前信息 this.info = this.getInfo() // 绑定事件回调上下文 this._onWinOrientationChange = bind(this._onWinOrientationChange, this) // 绑定窗口切换事件 window.addEventListener(EVT_ORIENTATION_CHANGE, this._onWinOrientationChange, false) }, /** * 销毁 * @method destroy * @public */ destroy: function () { window.removeEventListener(EVT_ORIENTATION_CHANGE, this._onWinOrientationChange, false) delete this._subs }, /** * 创建新实例 * @method create * @return {Orientation} Orientation实例对象 * @public */ create: function (cfg) { return new Orientation(cfg) }, /** * 获取横竖屏信息 * @method getInfo * @return {Object} 横竖屏相关信息 * @public */ getInfo: function () { // 90度为横屏 return (EVT_ORIENTATION_CHANGE === ORIENTATION_CHANGE) ? { landscape: (window.orientation === 90 || window.orientation === -90), portrait: (window.orientation === 0 || window.orientation === -180), orientation: window.orientation } : { landscape: window.screen.width > window.screen.height, portrait: window.screen.width <= window.screen.height, orientation: window.screen.width > window.screen.height ? 90 : 0 } }, /** * 是否是横屏 * @method isLandscape * @return {Boolean} * @public */ isLandscape: function () { return this.info.landscape }, /** * 是否是竖屏 * @method isPortrait * @return {Boolean} * @public */ isPortrait: function () { return this.info.portrait }, /** * 添加change事件 * @method change * @param {Function} fn 回调函数 * @param {Boolean} after 时候绑定after事件 * @chainable */ change: function (fn, after) { if (typeof fn === FUNCTION) { this._subs[after ? AFTER : ON].push(fn) } return this }, /** * 触发横竖屏事件 * @method _fireChange * @param {EventFacade} e * @protected */ _fireChange: function (e) { var self = this var info = this.getInfo() var subs = this._subs var i var l // 如果不等于上次方向,则触发 if (info.landscape !== this.info.landscape) { this.info = merge(info) info.originEvent = e info.originType = e.type // 执行on for (i = 0, l = subs.on.length; i < l; i++) { subs.on[i].call(self, e) } // 执行after setTimeout(function () { for (i = 0, l = subs.after.length; i < l; i++) { subs.after[i].call(self, e) } }, 0) } }, /** * 检查旋转是否已经完成 * @method _checkChange * @param {EventFacade} e * @protected */ _checkChange: function (e) { var self = this if (self._cfg.delay) { // iPad打开键盘时旋转比较慢 clearTimeout(this._changeTimer) self._changeTimer = setTimeout(function () { self._fireChange(e) }, self._cfg.delay) } else { self._fireChange(e) } }, /** * 横竖屏事件回调 * @method _onWinOrientationChange * @param {EventFacade} e 事件对象 * @protected */ _onWinOrientationChange: function (e) { if (e.type === RESIZE) { this._fireChange(e) } else { this._checkChange(e) } } } // 返回实例 export default Orientation.prototype.create() ================================================ FILE: src/components/panel/index.vue ================================================ ================================================ FILE: src/components/panel/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' props: header: en: header title zh-CN: 头部文字 footer: type: 'Object' en: 'footer config, `{url: url, title: title}`' zh-CN: '尾部配置,`{url: url, title: title}`' list: type: 'Array' en: content list `[{title, desc, src, fallbackSrc, meta:{source,date,other}}]` zh-CN: 内容列表,`[{title, desc, src, fallbackSrc, meta:{source,date,other}}]` type: default: '1' en: one of '1', '2', '3', '4', '5' zh-CN: 布局类型,可选值 ['1', '2', '3', '4', '5'] events: on-click-header: en: emits when clicking header zh-CN: 点击头部时触发 on-click-item: en: emits when clicking item params: '`(item)`' zh-CN: 点击内容列表时触发 on-click-footer: en: emits when clicking footer zh-CN: 点击尾部时触发 slots: header: version: v2.7.5 zh-CN: 头部位置 en: header body: version: v2.7.5 zh-CN: 主体内容部分 en: the main body part changes: v2.7.9: en: - '[fix] hide footer when footer.title is empty #2476' zh-CN: - '[fix] footer.title 为空时隐藏 footer #2476' v2.7.5: en: - '[fix] remove duplicate top border' - '[feature] add slot:header slot:body #2270' zh-CN: - '[fix] 移除重复顶部边框样式' - '[feature] 添加 slot:header slot:body #2270' v2.6.0: en: - '[enhance] support list prop:fallbackSrc #1923' zh-CN: - '[enhance] 支持列表 fallbackSrc 属性在图片加载失败后显示 #1923' v2.5.7: en: - '[feature] use v-html binding #1784' zh-CN: - '[feature] 属性使用 v-html 绑定 #1784' v2.5.2: en: - '[feature] Add type(4,5) and info meta(source,date,other)' zh-CN: - '[feature] 增加type类型(4,5)和描述信息(来源,时间和其他自定义信息)' ================================================ FILE: src/components/panel/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Panel', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('panel') }) }) ================================================ FILE: src/components/picker/animate.js ================================================ const time = Date.now || function () { return +new Date() } let running = {} let counter = 1 let desiredFrames = 60 let millisecondsPerSecond = 1000 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller // fixes from Paul Irish and Tino Zijdel if (typeof window !== 'undefined') { ;(function () { var lastTime = 0 var vendors = ['ms', 'moz', 'webkit', 'o'] for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'] } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (callback, element) { var currTime = new Date().getTime() var timeToCall = Math.max(0, 16 - (currTime - lastTime)) var id = window.setTimeout(function () { callback(currTime + timeToCall) }, timeToCall) lastTime = currTime + timeToCall return id } } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (id) { clearTimeout(id) } } }()) } export default { // A requestAnimationFrame wrapper / polyfill. requestAnimationFrame: (function () { if (typeof window !== 'undefined') { var requestFrame = window.requestAnimationFrame return function (callback, root) { requestFrame(callback, root) } } })(), // Stops the given animation. stop (id) { var cleared = running[id] != null if (cleared) { running[id] = null } return cleared }, // Whether the given animation is still running. isRunning (id) { return running[id] != null }, // Start the animation. start (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { var _this = this var start = time() var lastFrame = start var percent = 0 var dropCounter = 0 var id = counter++ if (!root) { root = document.body } // Compacting running db automatically every few new animations if (id % 20 === 0) { var newRunning = {} for (var usedId in running) { newRunning[usedId] = true } running = newRunning } // This is the internal step method which is called every few milliseconds var step = function (virtual) { // Normalize virtual value var render = virtual !== true // Get current time var now = time() // Verification is executed before next animation step if (!running[id] || (verifyCallback && !verifyCallback(id))) { running[id] = null completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false) return } // For the current rendering to apply let's update omitted steps in memory. // This is important to bring internal state variables up-to-date with progress in time. if (render) { var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1 for (var j = 0; j < Math.min(droppedFrames, 4); j++) { step(true) dropCounter++ } } // Compute percent value if (duration) { percent = (now - start) / duration if (percent > 1) { percent = 1 } } // Execute step callback, then... var value = easingMethod ? easingMethod(percent) : percent if ((stepCallback(value, now, render) === false || percent === 1) && render) { running[id] = null completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null) } else if (render) { lastFrame = now _this.requestAnimationFrame(step, root) } } // Mark as running running[id] = true // Init first step _this.requestAnimationFrame(step, root) // Return unique animation ID return id } } ================================================ FILE: src/components/picker/chain.js ================================================ import filter from 'array-filter' const Manager = class { constructor (data, count, fixedColumns) { if (process.env.NODE_ENV === 'development') { const notStringList = data.filter(item => { return (item.parent && item.parent !== 0 && item.parent !== '0') && (typeof item.parent === 'number' || typeof item.value === 'number') }) if (notStringList.length) { console.warn(`[VUX] picker data's value and parent should be string:\n${JSON.stringify(notStringList, null, 2)}`) } } this.data = data this.count = count if (fixedColumns) { this.fixedColumns = fixedColumns } } getChildren (value) { return filter(this.data, one => { return one.parent === value }) } getFirstColumn () { return filter(this.data, one => { return !one.parent || one.parent === 0 || one.parent === '0' }) } getPure (obj) { return JSON.parse(JSON.stringify(obj)) } getColumns (value) { // check is data contains the values if (value.length > 0) { const matchCount = this.getPure(this.data).filter((item) => { return this.getPure(value).indexOf(item.value) > -1 }).length if (matchCount < this.getPure(value).length) { value = [] } } var datas = [] const max = this.fixedColumns || 8 for (var i = 0; i < max; i++) { if (i === 0) { datas.push(this.getFirstColumn()) } else { // 没有数据时,取得上一级的第一个 if (!value[i]) { if (typeof datas[i - 1][0] === 'undefined') { break } else { const topValue = datas[i - 1][0].value datas.push(this.getChildren(topValue)) } } else { datas.push(this.getChildren(value[i - 1])) } } } const list = datas.filter((item) => { return item.length > 0 }) // correct the column this.count = list.length return list } } export default Manager ================================================ FILE: src/components/picker/index.vue ================================================ ================================================ FILE: src/components/picker/metas.yml ================================================ description: | Picker是指提供多个选项集合供用户选择其中一项的控件。Picker展示区域有限,部分选项会被隐藏,最好是当用户对所有选项都比较熟悉、有预期的时候,才使用Picker。 - 合理的默认选项能让用户减少操作次数,提升效率。 - 选项的排列顺序要依据当前上下文情景而定,例如衣服尺码按从小到大的顺序排列,而不是根据衣服尺码的首字母在字母表的顺序排列。 - 滚轮选择器控制在五列以内。为了保证手机屏幕触控精度,以免发生误触,滚轮选择器建议控制在五列以内。 - 使用相对概念增强感知。比起绝对的“某年某月日”,用“今天”、“昨天”等相对概念,能更快的激发人对时间的感知。 - 如果选项非常多,而且选项本身比较复杂难理解需要辅助的解释,建议用容纳更多的选项的其他形式,例如日历或者新页面。 category: en: Form 'zh-CN': 表单 icon: '' tags: en: - picker - form zh-CN: - 选择 - 表单 - 联动 extra: | ::: warning 请确保列表项的`value`值是字符串,使用数字会出错。
    如果你的业务接口返回数字值为数字,需要你先处理成字符串;同样,获取到值时为字符串,你需要自己转换成数字。 ::: 非联动情况下,列表数据格式示例: ``` js // data [['小米', 'iPhone', '华为', '情怀', '三星', '其他', '不告诉你'], ['小米1', 'iPhone2', '华为3', '情怀4', '三星5', '其他6', '不告诉你7']] // 或者使用 name => value 的形式 [[{ name: '2019届5班', value: '1' }, { name: '2019届4班', value: '2' }]] // value ['小米', '小米1'] ``` 联动时,列表数据格式示例: ``` js // data [{ name: '中国', value: 'china', parent: '0' // 为一级时可以不写 parent,但是此时允许为数字 0、空字符串或者字符串 '0' }, { name: '美国', value: 'USA', parent: '0' }, { name: '广东', value: 'china001', parent: 'china' }, { name: '广西', value: 'china002', parent: 'china' }, { name: '美国001', value: 'usa001', parent: 'USA' }, { name: '美国002', value: 'usa002', parent: 'USA' }, { name: '广州', value: 'gz', parent: 'china001' }, { name: '深圳', value: 'sz', parent: 'china001' }, { name: '广西001', value: 'gx001', parent: 'china002' }, { name: '广西002', value: 'gx002', parent: 'china002' }, { name: '美国001_001', value: '0003', parent: 'usa001' }, { name: '美国001_002', value: '0004', parent: 'usa001' }, { name: '美国002_001', value: '0005', parent: 'usa002' }, { name: '美国002_002', value: '0006', parent: 'usa002' }] ``` props: value: type: Array default: '' en: 'picker value, use `v-model` for binding' zh-CN: '表单值,使用 `v-model` 绑定' data: type: Array default: '' en: array list zh-CN: 选项列表数据 columns: type: Number en: how many columns in chained-mode zh-CN: 指定联动模式下的列数,当不指定时表示非联动 fixed-columns: type: Number en: how many columns will show zh-CN: 指定显示多少列,隐藏多余的 column-width: type: Array version: v2.2.0 en: 'custom width for each picker column, for example for a 3-column picker: [1/2, 1/5] ' zh-CN: 定义每一列宽度,只需要定义除最后一列宽度,最后一列自动宽度, 比如对于3列选择,可以这样:[1/2, 1/5] events: on-change: params: '`(value)`' en: emits when value is changed zh-CN: 选择值变化时触发 methods: getNameValues: version: v2.3.1 en: get labels of current value zh-CN: 根据 value 获取字面值 changes: v2.9.4: en: - '[fix] Fix chain mode get value is wrong when quickly scroll multiple children.' zh-CN: - '[fix] 修复多级联动模式下,快速滚动多个项,取值不正确' 2.9.3: en: - '[fix] fix Uncaught TypeError: Cannot assign to read only property exports of object #3386' zh-CN: - '[fix] 修复 fix Uncaught TypeError: Cannot assign to read only property exports of object #3386' v2.9.0: en: - '[enhance] show warning when picker value is not a string #2568' zh-CN: - '[enhance] 开发模式下 picker 数据非字符串时显示提示 #2568' v2.7.9: en: - '[fix] fix typo item_class => itemClass #2457' zh-CN: - '[fix] 修正 itemClass 拼写错误 #2457' v2.7.8: en: - '[fix] Fix chain mode get value is wrong when the children is empty data.' zh-CN: - '[fix] 修复多级联动模式下,子代没有数据时,取值不正确。' v2.7.3: en: - '[enhance] add development tip for chained-mode picker without specifying prop:columns' zh-CN: - '[enhance] 渲染出错时提示是否联动数据忘了指定列数' v2.7.0: en: - '[change] picker move distance do not respect html[data-dpr] by default #2082 #1979' zh-CN: - '[change] picker 移动距离不再遵从 html[data-dpr],需使用 ConfigPlugin 配置 #2082 #1979' v2.6.2: en: - '[enhance] scroll distance respects html[data-dpr] #1979' zh-CN: - '[enhance] 滚动距离支持从 html[data-dpr] 换算 #1979' v2.3.1: en: - '[feature] Add getNameValues Method #1418' zh-CN: - '[feature] 增加获取对应文字方法 #1418' v2.2.1-rc.3: en: - '[fix] Fix no re-rendering when value is set to empty array. #1230' zh-CN: - '[fix] 修复当值设为空数组时不会重新渲染的问题. #1230' v2.1.1-rc.7: en: - '[enhance] Support PC mouse drag #1039 @michael829' zh-CN: - '[enhance] 支持 PC 上鼠标选择 #1039 @michael829' v2.4.1: en: - '[fix] Fix a bug caused by unexpected end of json value #1584' zh-CN: - '[fix] 修复json格式不正确引起的bug #1584' ================================================ FILE: src/components/picker/scroller.css ================================================ .scroller-component { display: block; position: relative; height: 238px; overflow: hidden; width: 100%; } .scroller-content { position: absolute; left: 0; top: 0; width: 100%; z-index: 1; } .scroller-mask { position: absolute; left: 0; top: 0; height: 100%; margin: 0 auto; width: 100%; z-index: 3; transform: translateZ(0px); background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.95), rgba(255,255,255,0.6)), -webkit-linear-gradient(bottom, rgba(255,255,255,0.95), rgba(255,255,255,0.6)); background-image: linear-gradient(to bottom, rgba(255,255,255,0.95), rgba(255,255,255,0.6)), linear-gradient(to top, rgba(255,255,255,0.95), rgba(255,255,255,0.6)); background-position: top, bottom; background-size: 100% 102px; background-repeat: no-repeat; } .scroller-item { text-align: center; font-size: 16px; height: 34px; line-height: 34px; color: #000; } .scroller-indicator { width: 100%; height: 34px; position: absolute; left: 0; top: 102px; z-index: 3; background-image: -webkit-linear-gradient(top, #d0d0d0, #d0d0d0, transparent, transparent), -webkit-linear-gradient(bottom, #d0d0d0, #d0d0d0, transparent, transparent); background-image: linear-gradient(to bottom, #d0d0d0, #d0d0d0, transparent, transparent), linear-gradient(to top, #d0d0d0, #d0d0d0, transparent, transparent); background-position: top, bottom; background-size: 100% 1px; background-repeat: no-repeat; } .scroller-item { line-clamp: 1; -webkit-line-clamp: 1; overflow: hidden; text-overflow: ellipsis; } ================================================ FILE: src/components/picker/scroller.js ================================================ /* * Anima Scroller * Based Zynga Scroller (http://github.com/zynga/scroller) * Copyright 2011, Zynga Inc. * Licensed under the MIT License. * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt */ const isBrowser = typeof window === 'object' const TEMPLATE = `
    ` import Animate from './animate' import { getElement, getComputedStyle, easeOutCubic, easeInOutCubic } from './util' import passiveSupported from '../../libs/passive_supported' const getDpr = function () { let dpr = 1 if (isBrowser) { if (window.VUX_CONFIG && window.VUX_CONFIG.$picker && window.VUX_CONFIG.$picker.respectHtmlDataDpr) { dpr = document.documentElement.getAttribute('data-dpr') || 1 } } return dpr } const Scroller = function (container, options) { const self = this self.isDestroy = false self.dpr = getDpr() options = options || {} self.options = { itemClass: 'scroller-item', onSelect () {}, defaultValue: 0, data: [] } for (var key in options) { if (options[key] !== undefined) { self.options[key] = options[key] } } self.__container = getElement(container) var tempContainer = document.createElement('div') tempContainer.innerHTML = options.template || TEMPLATE var component = self.__component = tempContainer.querySelector('[data-role=component]') var content = self.__content = component.querySelector('[data-role=content]') var indicator = component.querySelector('[data-role=indicator]') var data = self.options.data var html = '' if (data.length && data[0].constructor === Object) { data.forEach(function (row) { html += '
    ' + row.name + '
    ' }) } else { data.forEach(function (val) { html += '
    ' + val + '
    ' }) } content.innerHTML = html self.__container.appendChild(component) self.__itemHeight = parseFloat(getComputedStyle(indicator, 'height'), 10) self.__callback = options.callback || function (top) { const distance = -top * self.dpr content.style.webkitTransform = 'translate3d(0, ' + distance + 'px, 0)' content.style.transform = 'translate3d(0, ' + distance + 'px, 0)' } var rect = component.getBoundingClientRect() self.__clientTop = (rect.top + component.clientTop) || 0 self.__setDimensions(component.clientHeight, content.offsetHeight) if (component.clientHeight === 0) { self.__setDimensions(parseFloat(getComputedStyle(component, 'height'), 10), 204) } self.select(self.options.defaultValue, false) const touchStartHandler = function (e) { if (e.target.tagName.match(/input|textarea|select/i)) { return } e.preventDefault() self.__doTouchStart(e, e.timeStamp) } const touchMoveHandler = function (e) { self.__doTouchMove(e, e.timeStamp) } const touchEndHandler = function (e) { self.__doTouchEnd(e.timeStamp) } const willPreventDefault = passiveSupported ? { passive: false } : false const willNotPreventDefault = passiveSupported ? { passive: true } : false component.addEventListener('touchstart', touchStartHandler, willPreventDefault) component.addEventListener('mousedown', touchStartHandler, willPreventDefault) component.addEventListener('touchmove', touchMoveHandler, willNotPreventDefault) component.addEventListener('mousemove', touchMoveHandler, willNotPreventDefault) component.addEventListener('touchend', touchEndHandler, willNotPreventDefault) component.addEventListener('mouseup', touchEndHandler, willNotPreventDefault) } var members = { value: null, __prevValue: null, __isSingleTouch: false, __isTracking: false, __didDecelerationComplete: false, __isGesturing: false, __isDragging: false, __isDecelerating: false, __isAnimating: false, __clientTop: 0, __clientHeight: 0, __contentHeight: 0, __itemHeight: 0, __scrollTop: 0, __minScrollTop: 0, __maxScrollTop: 0, __scheduledTop: 0, __lastTouchTop: null, __lastTouchMove: null, __positions: null, __minDecelerationScrollTop: null, __maxDecelerationScrollTop: null, __decelerationVelocityY: null, __setDimensions (clientHeight, contentHeight) { var self = this self.__clientHeight = clientHeight self.__contentHeight = contentHeight var totalItemCount = self.options.data.length var clientItemCount = Math.round(self.__clientHeight / self.__itemHeight) self.__minScrollTop = -self.__itemHeight * (clientItemCount / 2) self.__maxScrollTop = self.__minScrollTop + totalItemCount * self.__itemHeight - 0.1 }, selectByIndex (index, animate) { var self = this if (index < 0 || index > self.__content.childElementCount - 1) { return } self.__scrollTop = self.__minScrollTop + index * self.__itemHeight self.scrollTo(self.__scrollTop, animate) self.__selectItem(self.__content.children[index]) }, select (value, animate) { var self = this var children = self.__content.children for (var i = 0, len = children.length; i < len; i++) { if (decodeURI(JSON.parse(children[i].dataset.value).value) === value) { self.selectByIndex(i, animate) return } } self.selectByIndex(0, animate) }, getValue () { return this.value }, scrollTo (top, animate) { var self = this animate = (animate === undefined) ? true : animate if (self.__isDecelerating) { Animate.stop(self.__isDecelerating) self.__isDecelerating = false } top = Math.round((top / self.__itemHeight).toFixed(5)) * self.__itemHeight top = Math.max(Math.min(self.__maxScrollTop, top), self.__minScrollTop) if (top === self.__scrollTop || !animate) { self.__publish(top) self.__scrollingComplete() return } self.__publish(top, 250) }, destroy () { this.isDestroy = true this.__component.parentNode && this.__component.parentNode.removeChild(this.__component) }, __selectItem (selectedItem) { var self = this var selectedItemClass = self.options.itemClass + '-selected' var lastSelectedElem = self.__content.querySelector('.' + selectedItemClass) if (lastSelectedElem) { lastSelectedElem.classList.remove(selectedItemClass) } selectedItem.classList.add(selectedItemClass) if (self.value !== null) { self.__prevValue = self.value } self.value = decodeURI(JSON.parse(selectedItem.dataset.value).value) }, __scrollingComplete () { var self = this var index = Math.round((self.__scrollTop - self.__minScrollTop - self.__itemHeight / 2) / self.__itemHeight) self.__selectItem(self.__content.children[index]) if (self.__prevValue !== null && self.__prevValue !== self.value && !self.isDestroy) { self.options.onSelect(self.value) } }, __doTouchStart (ev, timeStamp) { const touches = ev.touches const self = this const target = ev.touches ? ev.touches[0] : ev const isMobile = !!ev.touches if (ev.touches && touches.length == null) { throw new Error('Invalid touch list: ' + touches) } if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf() } if (typeof timeStamp !== 'number') { throw new Error('Invalid timestamp value: ' + timeStamp) } self.__interruptedAnimation = true if (self.__isDecelerating) { Animate.stop(self.__isDecelerating) self.__isDecelerating = false self.__interruptedAnimation = true } if (self.__isAnimating) { Animate.stop(self.__isAnimating) self.__isAnimating = false self.__interruptedAnimation = true } // Use center point when dealing with two fingers var currentTouchTop var isSingleTouch = (isMobile && touches.length === 1) || !isMobile if (isSingleTouch) { currentTouchTop = target.pageY } else { currentTouchTop = Math.abs(target.pageY + touches[1].pageY) / 2 } self.__initialTouchTop = currentTouchTop self.__lastTouchTop = currentTouchTop self.__lastTouchMove = timeStamp self.__lastScale = 1 self.__enableScrollY = !isSingleTouch self.__isTracking = true self.__didDecelerationComplete = false self.__isDragging = !isSingleTouch self.__isSingleTouch = isSingleTouch self.__positions = [] }, __doTouchMove (ev, timeStamp, scale) { const self = this const touches = ev.touches const target = ev.touches ? ev.touches[0] : ev const isMobile = !!ev.touches if (touches && touches.length == null) { throw new Error('Invalid touch list: ' + touches) } if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf() } if (typeof timeStamp !== 'number') { throw new Error('Invalid timestamp value: ' + timeStamp) } // Ignore event when tracking is not enabled (event might be outside of element) if (!self.__isTracking) { return } var currentTouchTop // Compute move based around of center of fingers if (isMobile && touches.length === 2) { currentTouchTop = Math.abs(target.pageY + touches[1].pageY) / 2 } else { currentTouchTop = target.pageY } var positions = self.__positions // Are we already is dragging mode? if (self.__isDragging) { var moveY = currentTouchTop - self.__lastTouchTop var scrollTop = self.__scrollTop if (self.__enableScrollY) { scrollTop -= moveY var minScrollTop = self.__minScrollTop var maxScrollTop = self.__maxScrollTop if (scrollTop > maxScrollTop || scrollTop < minScrollTop) { // Slow down on the edges if (scrollTop > maxScrollTop) { scrollTop = maxScrollTop } else { scrollTop = minScrollTop } } } // Keep list from growing infinitely (holding min 10, max 20 measure points) if (positions.length > 40) { positions.splice(0, 20) } // Track scroll movement for decleration positions.push(scrollTop, timeStamp) // Sync scroll position self.__publish(scrollTop) // Otherwise figure out whether we are switching into dragging mode now. } else { var minimumTrackingForScroll = 0 var minimumTrackingForDrag = 5 var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop) self.__enableScrollY = distanceY >= minimumTrackingForScroll positions.push(self.__scrollTop, timeStamp) self.__isDragging = self.__enableScrollY && (distanceY >= minimumTrackingForDrag) if (self.__isDragging) { self.__interruptedAnimation = false } } // Update last touch positions and time stamp for next event self.__lastTouchTop = currentTouchTop self.__lastTouchMove = timeStamp self.__lastScale = scale }, __doTouchEnd (timeStamp) { var self = this if (timeStamp instanceof Date) { timeStamp = timeStamp.valueOf() } if (typeof timeStamp !== 'number') { throw new Error('Invalid timestamp value: ' + timeStamp) } // Ignore event when tracking is not enabled (no touchstart event on element) // This is required as this listener ('touchmove') sits on the document and not on the element itself. if (!self.__isTracking) { return } // Not touching anymore (when two finger hit the screen there are two touch end events) self.__isTracking = false // Be sure to reset the dragging flag now. Here we also detect whether // the finger has moved fast enough to switch into a deceleration animation. if (self.__isDragging) { // Reset dragging flag self.__isDragging = false // Start deceleration // Verify that the last move detected was in some relevant time frame if (self.__isSingleTouch && (timeStamp - self.__lastTouchMove) <= 100) { // Then figure out what the scroll position was about 100ms ago var positions = self.__positions var endPos = positions.length - 1 var startPos = endPos // Move pointer to position measured 100ms ago for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 2) { startPos = i } // If start and stop position is identical in a 100ms timeframe, // we cannot compute any useful deceleration. if (startPos !== endPos) { // Compute relative movement between these two points var timeOffset = positions[endPos] - positions[startPos] var movedTop = self.__scrollTop - positions[startPos - 1] // Based on 50ms compute the movement to apply for each render step self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60) // How much velocity is required to start the deceleration var minVelocityToStartDeceleration = 4 // Verify that we have enough velocity to start deceleration if (Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) { self.__startDeceleration(timeStamp) } } } } if (!self.__isDecelerating) { self.scrollTo(self.__scrollTop) } // Fully cleanup list self.__positions.length = 0 }, // Applies the scroll position to the content element __publish (top, animationDuration) { var self = this // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation var wasAnimating = self.__isAnimating if (wasAnimating) { Animate.stop(wasAnimating) self.__isAnimating = false } if (animationDuration) { // Keep scheduled positions for scrollBy functionality self.__scheduledTop = top var oldTop = self.__scrollTop var diffTop = top - oldTop var step = function (percent, now, render) { self.__scrollTop = oldTop + (diffTop * percent) // Push values out if (self.__callback) { self.__callback(self.__scrollTop) } } var verify = function (id) { return self.__isAnimating === id } var completed = function (renderedFramesPerSecond, animationId, wasFinished) { if (animationId === self.__isAnimating) { self.__isAnimating = false } if (self.__didDecelerationComplete || wasFinished) { self.__scrollingComplete() } } // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out self.__isAnimating = Animate.start(step, verify, completed, animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic) } else { self.__scheduledTop = self.__scrollTop = top // Push values out if (self.__callback) { self.__callback(top) } } }, // Called when a touch sequence end and the speed of the finger was high enough to switch into deceleration mode. __startDeceleration (timeStamp) { var self = this self.__minDecelerationScrollTop = self.__minScrollTop self.__maxDecelerationScrollTop = self.__maxScrollTop // Wrap class method var step = function (percent, now, render) { self.__stepThroughDeceleration(render) } // How much velocity is required to keep the deceleration running var minVelocityToKeepDecelerating = 0.5 // Detect whether it's still worth to continue animating steps // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. var verify = function () { var shouldContinue = Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating if (!shouldContinue) { self.__didDecelerationComplete = true } return shouldContinue } var completed = function (renderedFramesPerSecond, animationId, wasFinished) { self.__isDecelerating = false if (self.__scrollTop <= self.__minScrollTop || self.__scrollTop >= self.__maxScrollTop) { self.scrollTo(self.__scrollTop) return } if (self.__didDecelerationComplete) { self.__scrollingComplete() } } // Start animation and switch on flag self.__isDecelerating = Animate.start(step, verify, completed) }, // Called on every step of the animation __stepThroughDeceleration (render) { var self = this var scrollTop = self.__scrollTop + self.__decelerationVelocityY var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop) if (scrollTopFixed !== scrollTop) { scrollTop = scrollTopFixed self.__decelerationVelocityY = 0 } if (Math.abs(self.__decelerationVelocityY) <= 1) { if (Math.abs(scrollTop % self.__itemHeight) < 1) { self.__decelerationVelocityY = 0 } } else { self.__decelerationVelocityY *= 0.95 } self.__publish(scrollTop) } } // Copy over members to prototype for (var key in members) { Scroller.prototype[key] = members[key] } export default Scroller ================================================ FILE: src/components/picker/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Picker', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('picker') }) }) ================================================ FILE: src/components/picker/util.js ================================================ export function getElement (expr) { return (typeof expr === 'string') ? document.querySelector(expr) : expr } export function getComputedStyle (el, key) { var computedStyle = window.getComputedStyle(el) return computedStyle[key] || '' } // Easing Equations (c) 2003 Robert Penner, all rights reserved. // Open source under the BSD License. export function easeOutCubic (pos) { return (Math.pow((pos - 1), 3) + 1) } export function easeInOutCubic (pos) { if ((pos /= 0.5) < 1) { return 0.5 * Math.pow(pos, 3) } return 0.5 * (Math.pow((pos - 2), 3) + 2) } ================================================ FILE: src/components/popover/DemoIndex.vue ================================================ ================================================ FILE: src/components/popover/index.vue ================================================ ================================================ FILE: src/components/popover/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - popover - float menu - tooltip zh-CN: - 弹窗 - 菜单 - 提示 props: content: type: String default: '' en: content of the popover zh-CN: 弹出窗口内容 placement: type: String default: '' en: position of the popover zh-CN: 弹出窗口位置 enum: - top - right - bottom - left gutter: type: Number default: 5 en: the gutter between trigger element and popover arrow zh-CN: 箭头和触发元素之间的距离 slots: default: en: trigger element zh-CN: 触发元素 content: en: content of the popover zh-CN: 弹窗内容 events: on-show: en: triggers when popover shows zh-CN: 弹窗显示时触发 on-hide: en: triggers when popover hides zh-CN: 弹窗隐藏时触发 changes: v2.9.0: en: - '[fix] fix position in fixed container #2590' zh-CN: - '[fix] 修复在 fixed 容器里定位不正确 #2590' v2.0.0: en: - '[todo] wrong position if changing locale' zh-CN: - '[todo] 当切换i18n语言时,位置不正确' ================================================ FILE: src/components/popover/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Popover', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('popover') }) }) ================================================ FILE: src/components/popup/index.vue ================================================ ================================================ FILE: src/components/popup/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - popup - dialog zh-CN: - 弹窗 props: value: type: Boolean en: visibility of popup, use `v-model` for binding zh-CN: 是否关闭,使用`v-model`绑定 height: type: String default: 'auto' en: popup height, works when position is top or bottom zh-CN: 高度,设置`100%`为整屏高度。当 position 为 top 或者 bottom 时有效。 hide-on-blur: type: Boolean default: true en: if closing popup when mask being clicked zh-CN: 点击遮罩时是否自动关闭 is-transparent: version: v2.1.1-rc.9 type: Boolean default: false en: if using transparent background zh-CN: 是否为透明背景 width: version: v2.2.1-rc.6 type: String default: auto en: if you want to set 100% width, you should use this prop. only works when position is left or right zh-CN: 设置 100% 宽度必须使用该属性。在 position 为 left 或者 right 时有效。 position: version: v2.2.1-rc.6 type: String default: bottom en: popup position, shoule be one of ['left', 'right', 'top', 'bottom'] zh-CN: 位置,可取值 ['left', 'right', 'top', 'bottom'] show-mask: version: v2.2.1-rc.6 type: Boolean default: true en: whether show mask zh-CN: 是否显示遮罩 popup-style: version: v2.5.2 type: Object en: popup style zh-CN: 弹窗样式,可以用于强制指定 z-index hide-on-deactivated: version: v2.5.5 type: Boolean default: true en: whether hide popup on deactived event when using keep-alive zh-CN: 是否在 deactived 事件触发时自动关闭,避免在路由切换时依然显示弹窗 should-rerender-on-show: version: v2.9.0 type: Boolean default: false en: whether rerender body on show zh-CN: 是否在显示时重新渲染内容区域(以及滚动到顶部),适用于每次显示弹窗需要重新获取数据初始化的场景 should-scroll-top-on-show: version: v2.9.0 type: Boolean default: false en: whether scroll top on show,add class vux-scrollable if you customize the scrollable div zh-CN: 是否在显示时自动滚动到顶部,当你自定义滚动容器时需要手动为该容器加上类名 vux-scrollable events: on-hide: en: emits when then popup is closed zh-CN: 关闭时触发 on-show: en: emits when the popup shows zh-CN: 显示时触发 on-first-show: en: emits at the first time the popup shows zh-CN: 第一次显示时触发,可以在该事件回调里初始化数据或者界面 slots: default: en: content body zh-CN: 弹窗主体内容 changes: v2.9.1: en: - '[fix] fixed popup has black border on the right #2651' zh-CN: - '[fix] 修正弹出框右边有黑边 #2651' v2.9.0: en: - '[feature] support prop:should-rerender-on-show for forcing rerendering #2598' - '[feature] support prop:should-scroll-top-on-show for forcing scrolling top #2598' zh-CN: - '[feature] 支持 should-rerender-on-show 实现每次打开重新渲染 #2598' - '[feature] 支持自动滚动到顶部 #2598' v2.5.12: en: - '[fix] clean vux-modal-open on beforeDestroy and deactivated #1921' zh-CN: - '[fix] 在 beforeDestroy 和 deactivated 事件中清除设置于 body 上的 vux-modal-open 类名 #1921' v2.5.11: en: - '[fix] add vux-modal-open only when global config:$layout is VIEW_BOX #1893' zh-CN: - '[fix] 仅在全局配置 $layout = VIEW_BOX 时用 fixed body 的形式阻止页面滚动 #1893' v2.5.10: en: - '[fix] Fix class helper typo issue #1893' zh-CN: - '[fix] 修复 class 工具函数拼写错误 #1893' v2.5.9: en: - '[fix] Fix layout errors when body not set 100% width #1867' zh-CN: - '[feature] 修复当body没有设置100%宽度时出现的布局错乱问题 #1867' v2.5.5: en: - '[feature] Auto hide popup on deactived #1774' zh-CN: - '[feature] 在 deactived 事件触发时自动关闭,避免在路由切换时依然显示弹窗,可以使用 prop:hide-on-deactivated 进行禁用 #1774' v2.5.2: en: - '[feature] Add prop:popup-style #1656' zh-CN: - '[feature] 支持弹窗样式设置 prop:popup-style #1656' v2.3.7: en: - '[fix] Fix missing mask when initial value is true #1555' zh-CN: - '[fix] 修复默认值为 true 时遮罩没有显示的问题 #1555' v2.3.3: en: - '[enhance] Set max-height:100% by default' - '[feature] Support prop:max-height' - '[fix] disable mask scrolling on mobile #1475 @xiaobinhong1' zh-CN: - '[enhance] 默认 max-height 设为 100%,避免不设置高度时内容过长无法滚动' - '[feature] 支持 prop:max-height' - '[fix] 禁止遮罩层在手机上滚动 #1475 @xiaobinhong1' v2.2.2: en: - '[fix] Fix mask closing issue in some situation. #1312' zh-CN: - '[fix] 修复在某些情况下遮罩被错误关闭的问题 #1312' v2.2.1-rc.6: en: - '[fix] Fix popup classname get lost' - '[feature] Support prop:show-mask' - '[feature] Support prop:position' - '[feature] Support prop:width' zh-CN: - '[fix] 修复 popup 类名丢失问题' - '[feature] 支持 prop:show-mask 隐藏遮罩' - '[feature] 支持 prop:position 设定位置' - '[feature] 支持 prop:width 设定左右位置时的宽度' v2.1.1-rc.11: en: - '[fix] Fix wrong overflowScrolling when mask is disabled' zh-CN: - '[fix] 修复遮罩禁用点击时错误设置 overflowScrolling 的问题' v2.1.1-rc.9: en: - '[enhance] Better mask transition' - '[feature] Support tranparent background' - '[feature] Support less variable: @popup-background-color' zh-CN: - '[enhance] 更流畅的遮罩层动画' - '[feature] 支持透明背景' - '[feature] 支持背影颜色变量 @popup-background-color' v2.1.1-rc.6: en: - '[fix] Fix wrong setting of overflow-scrolling when using multi popup on Safari #1015 @think2011' zh-CN: - '[fix] 修复 Safari 上关闭第二个popup导致的问题 #1015 @think2011' v2.1.1-rc.1: en: - '[fix] Fix overflow-scrolling setting in safari #311 @linhaobin' zh-CN: - '[fix] 修复 `overflow-scrolling` 设置逻辑遗漏问题 #311 @linhaobin' ================================================ FILE: src/components/popup/popup.js ================================================ import passiveSupported from '../../libs/passive_supported' const isBrowser = typeof window === 'object' // not a good way but works well if (isBrowser) { window.__$vuxPopups = window.__$vuxPopups || {} } const popupDialog = function (option) { if (!isBrowser) { return } this.uuid = Math.random().toString(36).substring(3, 8) this.params = {} if (Object.prototype.toString.call(option) === '[object Object]') { this.params = { hideOnBlur: option.hideOnBlur, onOpen: option.onOpen || function () {}, onClose: option.onClose || function () {}, showMask: option.showMask } } if (!!document.querySelectorAll('.vux-popup-mask').length <= 0) { this.divMask = document.createElement('a') this.divMask.className = 'vux-popup-mask' this.divMask.dataset.uuid = '' this.divMask.href = 'javascript:void(0)' document.body.appendChild(this.divMask) } let div if (!option.container) { div = document.createElement('div') } else { div = option.container } div.className += ` vux-popup-dialog vux-popup-dialog-${this.uuid}` if (!this.params.hideOnBlur) { div.className += ' vux-popup-mask-disabled' } this.div = div if (!option.container) { document.body.appendChild(div) } this.container = document.querySelector('.vux-popup-dialog-' + this.uuid) this.mask = document.querySelector('.vux-popup-mask') this.mask.dataset.uuid += `,${this.uuid}` this._bindEvents() option = null this.containerHandler = () => { this.mask && !/show/.test(this.mask.className) && setTimeout(() => { !/show/.test(this.mask.className) && (this.mask.style['zIndex'] = -1) }, 200) } this.container && this.container.addEventListener('webkitTransitionEnd', this.containerHandler) this.container && this.container.addEventListener('transitionend', this.containerHandler) } popupDialog.prototype.onClickMask = function () { this.params.hideOnBlur && this.params.onClose() } popupDialog.prototype._bindEvents = function () { if (this.params.hideOnBlur) { this.mask.addEventListener('click', this.onClickMask.bind(this), false) this.mask.addEventListener('touchmove', e => e.preventDefault(), passiveSupported ? {passive: false} : false) } } popupDialog.prototype.show = function () { if (this.params.showMask) { this.mask.classList.add('vux-popup-show') this.mask.style['zIndex'] = 500 } this.container.classList.add('vux-popup-show') this.params.onOpen && this.params.onOpen(this) if (isBrowser) { window.__$vuxPopups[this.uuid] = 1 } } popupDialog.prototype.hide = function (shouldCallback = true) { this.container.classList.remove('vux-popup-show') if (!document.querySelector('.vux-popup-dialog.vux-popup-show')) { this.mask.classList.remove('vux-popup-show') setTimeout(() => { this.mask && !/show/.test(this.mask.className) && (this.mask.style['zIndex'] = -1) }, 400) } shouldCallback === false && this.params.onClose && this.params.hideOnBlur && this.params.onClose(this) this.isShow = false if (isBrowser) { delete window.__$vuxPopups[this.uuid] } } popupDialog.prototype.destroy = function () { this.mask.dataset.uuid = this.mask.dataset.uuid.replace(new RegExp(`,${this.uuid}`, 'g'), '') if (!this.mask.dataset.uuid) { this.mask.removeEventListener('click', this.onClickMask.bind(this), false) this.mask && this.mask.parentNode && this.mask.parentNode.removeChild(this.mask) } else { this.hide() } this.container.removeEventListener('webkitTransitionEnd', this.containerHandler) this.container.removeEventListener('transitionend', this.containerHandler) if (isBrowser) { delete window.__$vuxPopups[this.uuid] } } export default popupDialog ================================================ FILE: src/components/popup/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Popup', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('popup') }) }) ================================================ FILE: src/components/popup-header/index.vue ================================================ ================================================ FILE: src/components/popup-header/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' props: left-text: version: v2.5.2 en: left text zh-CN: 左侧文字 right-text: version: v2.5.2 en: right text zh-CN: 右侧文字 title: version: v2.5.2 en: title zh-CN: 标题 show-bottom-border: version: v2.5.2 en: whether show bottom border zh-CN: 是否显示底部边框 events: on-click-left: version: v2.5.2 en: emits when left text is clicked zh-CN: 左侧文字点击时触发 on-click-right: version: v2.5.2 en: emits when right text is clicked zh-CN: 右侧文字点击时触发 changes: v2.5.9: en: - '[fix] fix wrong slot name' zh-CN: - '[fix] 修复错误的 slot 名字' v2.5.2: en: - '[feature] new component' zh-CN: - '[feature] 新组件 popup-header, 配合 popup 使用统一头部样式' ================================================ FILE: src/components/popup-header/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('PopupHeader', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('popup-header') }) }) ================================================ FILE: src/components/popup-picker/index.vue ================================================ cancel_text: en: cancel zh-CN: 取消 confirm_text: en: ok zh-CN: 完成 ================================================ FILE: src/components/popup-picker/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - popup - picker - form zh-CN: - 表单 - 选择 - 弹窗 extra: en: | ::: tip other props are the same as `Picker` ::: zh-CN: | ::: tip 其他选项与`picker`一致 ::: tips: zh-CN: - q: iPhone 上弹窗显示时遮罩层闪烁 a: | 未找到解决方式。在 iOS11 之前会出现,在 iOS11 后似乎已经不能重现,可能要归结于系统浏览器原因。 相关 issue: [#2239](https://github.com/airyland/vux/issues/2239) props: value: type: Array en: picker value, use `v-model` for binding zh-CN: '表单值,使用`v-model绑定`' title: en: label text zh-CN: 标题 cancel-text: en: popup cancel text zh-CN: 弹窗的取消文字 confirm-text: en: popup confirm text zh-CN: 弹窗的确认文字 placeholder: en: placeholder zh-CN: 提示文字 show-name: type: Boolean default: false en: if show names instead of keys zh-CN: 是否显示文字值而不是key inline-desc: en: description text below label zh-CN: Cell的描述文字 show: type: Boolean en: popup show (supports the .sync modifier next) zh-CN: 显示 (支持.sync修饰 next) value-text-align: type: String version: v2.1.0-rc.3 default: right en: value's text align zh-CN: 'value 对齐方式(text-align)' display-format: type: Function version: v2.1.1-rc.7 en: 'used to format display text on cell, param: (currentValue)' zh-CN: 自定义在cell上的显示格式,参数为当前 value,使用该属性时,show-name 属性将失效 popup-style: version: v2.5.2 type: Object en: popup style zh-CN: 弹窗样式,可以用于强制指定 z-index popup-title: version: v2.7.0 type: String en: popup title zh-CN: 弹窗标题 disabled: version: v2.9.0 type: Boolean default: false en: whether disable selecting zh-CN: 是否禁用选择 slots: title: version: v2.3.7 en: title slot, use scope.labelClass and scope.labelStyle to inherit label styles zh-CN: 标题插槽,使用 scope.labelClass 和 scope.labelStyle 继承原有样式(实现样式受控于 group label 设置) events: on-change: params: '`(value)`' en: emits when value is changed zh-CN: 值变化时触发 on-show: en: emits when popup shows zh-CN: 弹窗出现时触发 on-hide: params: '`(closeType)` true表示confirm(选择确认), false表示其他情况的关闭' en: emits when popup is closed zh-CN: 弹窗关闭时触发 on-shadow-change: version: v2.5.6 params: '`(Array ids, Array names)`' en: emits when picker value is changed zh-CN: picker 值变化时触发,即滑动 picker 时触发 changes: v2.9.0: en: - '[feature] add prop:disabled #2594' zh-CN: - '[feature] 支持属性 disabled #2594' v2.7.7: en: - '[fix] remove the first top line #2371' zh-CN: - '[fix] 去除第一个子元素的上边线 #2371' v2.7.0: en: - '[feature] add prop:popup-title(use component:popup-header) #1866' zh-CN: - '[feature] 增加属性 popup-title(使用 popup-header 组件) #1866' v2.5.10: en: - '[feature] support @cell-value-color #1874' zh-CN: - '[feature] 值文字颜色受控于 @cell-value-color #1874' v2.5.8: en: - '[fix] prevent background scrolling when popup is show #1830' zh-CN: - '[fix] popup显示时阻止背景滚动 #1830' v2.5.7: en: - '[fix] Fix render issue after changing value #1793' zh-CN: - '[fix] 修复修改值后第二次打开默认选中项渲染不正确的问题 #1793' v2.5.6: en: - '[feature] Support event:on-shadow-change #1778' zh-CN: - '[feature] 支持在滑动 picker 时触发事件 on-shadow-change #1778' v2.5.3: en: - '[fix] Fix label style being overwrite after importing x-textarea #1696' zh-CN: - '[fix] 修复在 x-textarea 后引入 popup-picker label 宽度被覆盖 #1696' v2.5.2: en: - '[feature] Add prop:popup-style #1656' zh-CN: - '[feature] 支持弹窗样式设置 prop:popup-style #1656' v2.4.1: en: - '[fix] popup-picker-header add touchmove.prevent event #1596' zh-CN: - '[fix] popup-picker头部区域屏蔽滑动事件 #1596' v2.3.8: en: - '[fix] Fix placeholder missing on iOS9 and iOS8 #1293' zh-CN: - '[fix] 修复 iOS 低版本下 placeholder 无法显示的问题 #1293' v2.3.7: en: - '[feature] Add slot:title' zh-CN: - '[feature] 添加 slot:title' v2.2.1-rc.7: en: - '[fix] Fix popup-picker filter issue on v2.2.1-rc.6' zh-CN: - '[fix] 修复 popup-picker 上个版本转换值的问题' v2.2.1-rc.6: en: - '[enhance] Add param names for display-format' zh-CN: - '[enhance] display-format 支持 names 作为第二个参数' v2.2.1-rc.1: en: - '[fix] Fix wrong closeType #1209' zh-CN: - '[fix] 修复错误的弹窗关闭类型 #1209' v2.1.1-rc.13: en: - '[enhance] Use gray for cancel text color' zh-CN: - '[enhance] 取消文字使用灰色' v2.1.1-rc.11: en: - '[enhance] Fix mask render slowly in Safari. #1083 @sixiakun' - '[enhance] Enable transfer-dom by default' zh-CN: - '[enhance] 修复 Safari 下遮罩层渲染慢的问题 #1083 @sixiakun' - '[enhance] 默认开启 transfer-dom,解决非 Safari 也会出现的 z-index 问题' v2.1.1-rc.8: en: - '[enhance] Update header style and add less variables' zh-CN: - '[enhance] 更新头部样式并新增 less 变量' v2.1.1-rc.7: en: - '[enhance] Support PC mouse drag #1039 @michael829' - '[enhance] Support prop:display-format' zh-CN: - '[enhance] 支持 PC 上鼠标选择 #1039 @michael829' - '[enhance] 支持根据值自定义显示格式' v2.1.1-rc.1: en: - '[fix] Fix labelMarginRight #977 @wg5945' zh-CN: - '[fix] 修复 label 未正确设置 labelMarginRight #977 @wg5945' v2.1.0-rc.50: en: - '[fix] Fix no on-change emits #934 @howyhuang' zh-CN: - '[fix] 修复`on-change`事件没有触发 #934 @howyhuang' ================================================ FILE: src/components/popup-picker/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('PopupPicker', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('popup-picker') }) }) ================================================ FILE: src/components/popup-radio/index.vue ================================================ ================================================ FILE: src/components/popup-radio/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: en: props are the same as cell's and radio's. Be aware that it doesnot support fillMode. zh-CN: 属性同 cell 和 Radio 的属性,需要注意的是不支持 fillMode。 tags: en: - radio - form zh-CN: - 表单 - 单选 props: readonly: version: v2.3.6 en: readonly, shown like a cell zh-CN: 只读样式,类似于 cell slots: popup-header: version: v2.3.3 en: popup header slot zh-CN: 弹窗顶部 each-item: version: v2.3.7 en: custom how label displays zh-CN: 自定义每个条目显示内容 events: on-show: version: v2.6.5 en: fires when popup shows zh-CN: 弹窗显示时触发 on-hide: version: v2.6.5 en: fires when popup hides zh-CN: 弹窗关闭时触发 changes: v2.7.8: en: - '[fix] fix popup-radio component does not support the border-intent property of the cell component.' zh-CN: - '[fix] 修复 popup-radio 组件不支持cell组件中 border-intent 的属性。' v2.7.0: en: - '[fix] fix vue@2.5.0 scope issue #2076' zh-CN: - '[fix] 兼容 vue@2.5.0 scope 更名为 slot-scope 的问题 #2076' v2.6.5: en: - '[feature] add event:on-hde event:on-show #2053' zh-CN: - '[feature] 支持事件 on-show on-hide #2053' v2.6.0: en: - '[fix] fix placeholder logic #1964' zh-CN: - '[fix] 修复 placeholder 显示逻辑 #1964' v2.5.10: en: - '[feature] support @cell-value-color #1874' zh-CN: - '[feature] 值文字颜色受控于 @cell-value-color #1874' v2.3.7: en: - '[feature] Add slot:each-item' zh-CN: - '[feature] 添加 slot:each-item' v2.3.6: en: - '[feature] Add prop:readonly' - '[feature] Add slot:icon' zh-CN: - '[feature] 添加 prop:readonly' - '[feature] 添加 slot:icon' v2.3.3: en: - '[feature] Support slot:popup-header' zh-CN: - '[feature] 支持 slot:popup-header' v2.3.1: en: - '[fix] fix cell display value' zh-CN: - '[fix] 修复文字值显示' v2.2.1-rc.8: en: - '[fix] fix label width' zh-CN: - '[fix] 修复 label 宽度' v2.2.1-rc.4: en: - '[feature] new component' zh-CN: - '[feature] 新组件' ================================================ FILE: src/components/popup-radio/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('PopupRadio', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('popup-radio') }) }) ================================================ FILE: src/components/previewer/index.vue ================================================ ================================================ FILE: src/components/previewer/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' tags: en: - preview - swipephoto - image zh-CN: - 预览 - 图片 tips: zh-CN: - q: 建议为所有图片定义尺寸 a: | PhotoSwipe 本身要求设置宽高,Previewer 组件会尝试对没有设置宽高的图片先加载再显示,可能会造成性能问题或者宽带浪费。 相关 issue:[#2321](https://github.com/airyland/vux/issues/2321) extra: en: | > please avoid using large pictures or they may crash your browser(especially on Android). example for list: ``` js [{ msrc: 'https://placekitten.com/500/250', src: 'https://placekitten.com/800/400', w: 600, h: 400 }, { src: 'https://placekitten.com/1200/900', msrc: 'https://placekitten.com/120/90', // thumbnail w: 1200, h: 900 }] ``` ::: warning before v2.4.0, `w` and `h` should be specified. ::: Prevewing an image by index: ``` js this.$refs.previewer.show(index) ``` zh-CN: | > 注意避免使用过大图片,否则可能会出现卡顿黑屏的情况(尤其是在 Android 机子上) #2514。 list的数据示例如图: ``` js [{ src: 'https://placekitten.com/800/400', w: 600, h: 400 }, { src: 'https://placekitten.com/1200/900', msrc: 'https://placekitten.com/120/90', // 缩略图 w: 1200, h: 900 }] ``` ::: warning 注意在 v2.4.0 之前 w 和 h 都必须填写 ::: 显示特定index的图片,使用`ref`: ``` js this.$refs.previewer.show(index) ```

    注意,当你自定义添加按钮时,需要在 photoswipe 选项设置 clickable 元素,否则点击将没有响应

    ``` html ``` ``` js options: { isClickableElement: function (el) { return /previewer-delete-icon/.test(el.className) } } ``` props: list: type: Array en: images list zh-CN: 图片列表 options: type: 'Object' en: options for `photoswipe` zh-CN: '`photoswipe`的设置' events: on-close: version: v2.2.1-rc.4 en: fires after closing zh-CN: 关闭时触发 on-index-change: version: v2.8.1 en: fires after index changes(shall not fire on first opening slide) zh-CN: 切换图片后触发(首次打开不会触发) methods: goTo: version: v2.5.10 params: '(index)' en: go to specified image zh-CN: 跳转到特定图片 prev: version: v2.5.10 en: go to previous image zh-CN: 跳转到上一张 next: version: v2.5.10 en: go to next image zh-CN: 跳转到下一张 getCurrentIndex: version: v2.6.3 en: get current index zh-CN: 获取当前图片索引 slots: button-after: version: v2.6.3 en: slot after control button list zh-CN: 操作按钮之后,可以添加自定义图标 button-before: version: v2.6.3 en: slot before control button list zh-CN: 操作按钮之前,可以添加自定义图标 changes: v2.8.1: en: - '[feature] add event:on-index-change #2505' zh-CN: - '[feature] 支持事件 on-index-change #2505' v2.6.4: en: - '[feature] automatically update view if one slide is deleted' zh-CN: - '[feature] 方便删除操作,绑定数据减少一张时自动更新示图,有更多图片时切换到下一张,没有时将自动关闭预览' v2.6.3: en: - '[fix] fix method name starts with _ will get vue(2.4.4) warning #2001' - '[feature] add method:getCurrentIndex' - '[feature] add slot:button-before slot:button-after' zh-CN: - '[fix] 修复组件函数名字以下划线开头将会被 Vue(2.4.4) 警告的问题 #2001' - '[feature] 添加 getCurrentIndex 方法' - '[feature] 添加 slot:button-before 和 slot:button-after' v2.5.10: en: - '[enhance] Support method:goTo #1888' zh-CN: - '[enhance] 支持 goTo 方法 #1888' v2.4.0: en: - '[enhance] Use src as msrc by default' - '[enhance] No more force setting width and height #1426' zh-CN: - '[enhance] 使用图片作为缩略图,防止先显示黑色区域' - '[enhance] 不再强制要求设置宽高 #1426' v2.2.1-rc.4: en: - '[enhance] Increase the closing event #1245' zh-CN: - '[enhance] 增加关闭事件 #1245' ================================================ FILE: src/components/previewer/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Previewer', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('previewer') }) }) ================================================ FILE: src/components/qrcode/index.vue ================================================ ================================================ FILE: src/components/qrcode/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' tags: - qr - qrcode - 二维码 props: value: type: String default: '' en: content, make sure to prefix with http(s) when value is a link zh-CN: 编码内容,如果为链接,请保证有http(s)协议名 size: type: Number default: 80 en: size zh-CN: 尺寸大小 bg-color: type: String default: '#FFFFFF' en: background color zh-CN: 背景颜色 fg-color: type: String default: '#000000' en: front color zh-CN: 二维码着色 type: type: String default: img en: 'render type, can be `img` or `canvas`' zh-CN: '渲染类型,可以为`img`(适合需要在微信需要长按识别的场景)和`canvas`' changes: v2.9.1: en: - '[fix] fix missing support for Chinese works #2652' zh-CN: - '[fix] 修复中文支持 #2652' v2.7.0: en: - '[fix] fix errors when value is undefined #2070' - '[change] use style to set width and height #2075' zh-CN: - '[fix] 修复 value 为 undefined 时报错的问题 #2070' - '[change] 使用 style 样式设置宽高 #2075' v2.1.0-rc.47: zh-CN: - '[feature] 支持渲染类型为图片 #900 @keepgoingwm' ================================================ FILE: src/components/qrcode/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Qrcode', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('qrcode') }) }) ================================================ FILE: src/components/radio/index.vue ================================================ ================================================ FILE: src/components/radio/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - radio - form zh-CN: - 表单 - 单选 extra: en: | ```html ``` `options` can be plain array, or object list with key=> value: ``` js const options = [ 'China', 'Japan' ] const options2 = [{ icon: 'http://dn-placeholder.qbox.me/110x110/FF2D55/000', key: '001', value: 'radio001' }, { icon: 'http://dn-placeholder.qbox.me/110x110/FF2D55/000', key: '002', value: 'radio002' }] ``` ::: tip `radio` should be used in `Group`. ::: zh-CN: | ```html ``` `options`可以为简单数组,也可以为key=>value形式键值对 ``` js const options = [ 'China', 'Japan' ] const options2 = [{ icon: 'http://dn-placeholder.qbox.me/110x110/FF2D55/000', key: '001', value: 'radio001' }, { icon: 'http://dn-placeholder.qbox.me/110x110/FF2D55/000', key: '002', value: 'radio002' }] ``` ::: tip `radio`只能在`Group`中使用 ::: props: value: en: radio value, use `v-model` for binding zh-CN: 表单值,使用`v-model`绑定 options: type: Array en: option list, can be plain array or array with `key=>value` zh-CN: 可选列表,可以用字符串组成的数组或者 `key=>value` 的形式 fill-mode: type: Boolean default: false en: if add an option user can fill zh-CN: 是否可填写 fill-placeholder: en: placeholder for fill input zh-CN: 可填写时的提示文字 fill-label: en: label for fill input zh-CN: 可填写时的label文字 disabled: type: Boolean version: v2.3.8 en: disable selecting zh-CN: 禁用操作 selected-label-style: type: Object version: v2.4.0 en: set selected label style zh-CN: 设置选中时的 label 样式,比如使用其他颜色更容易区分是否为选中项 slots: each-item: version: v2.3.5 en: custom how to display each item zh-CN: 自定义如何显示每一项 changes: v2.4.0: en: - '[feature] Add prop:selected-label-style' zh-CN: - '[feature] 添加属性 prop:selected-label-style 来支持定义选中项的文字样式' v2.3.8: en: - '[feature] Add prop:disabled #1254' - '[feature] Add less:@radio-checked-icon-color #896' zh-CN: - '[feature] 支持 prop:disabled 禁用操作 #1254' - '[feature] 支持 less 变量 @radio-checked-icon-color #896' v2.3.5: en: - '[feature] Support slot:each-item' zh-CN: - '[feature] 支持自定义渲染每一列 slot:each-item' v2.1.1-rc.13: en: - '[fix] Fix not support Number and not reactive #1115' zh-CN: - '[fix] 修复未响应数据变更的 bug #1115' v2.1.1-rc.1: en: - '[feature] Support icon' zh-CN: - '[feature] 支持左侧图标' ================================================ FILE: src/components/radio/props.js ================================================ // used by radio and popup-radio export default function () { return { options: { type: Array, required: true }, value: [String, Number], fillMode: { type: Boolean, default: false }, fillPlaceholder: { type: String, default: '其他' }, fillLabel: { type: String, default: '其他' }, disabled: Boolean, selectedLabelStyle: Object } } ================================================ FILE: src/components/radio/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Radio', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('radio') }) }) ================================================ FILE: src/components/range/index.vue ================================================ ================================================ FILE: src/components/range/lib/classes.js ================================================ /** * Module dependencies. */ import { indexof } from '../utils' /** * Whitespace regexp. */ var re = /\s+/ /** * toString reference. */ var toString = Object.prototype.toString /** * Wrap `el` in a `ClassList`. * * @param {Element} el * @return {ClassList} * @api public */ export default function (el) { return new ClassList(el) } /** * Initialize a new ClassList for `el`. * * @param {Element} el * @api private */ function ClassList (el) { if (!el || !el.nodeType) { throw new Error('A DOM element reference is required') } this.el = el this.list = el.classList } /** * Add class `name` if not already present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.add = function (name) { // classList if (this.list) { this.list.add(name) return this } // fallback var arr = this.array() var i = indexof(arr, name) if (!~i) arr.push(name) this.el.className = arr.join(' ') return this } /** * Remove class `name` when present, or * pass a regular expression to remove * any which match. * * @param {String|RegExp} name * @return {ClassList} * @api public */ ClassList.prototype.remove = function (name) { if (toString.call(name) === '[object RegExp]') { return this.removeMatching(name) } // classList if (this.list) { this.list.remove(name) return this } // fallback var arr = this.array() var i = indexof(arr, name) if (~i) arr.splice(i, 1) this.el.className = arr.join(' ') return this } /** * Remove all classes matching `re`. * * @param {RegExp} re * @return {ClassList} * @api private */ ClassList.prototype.removeMatching = function (re) { var arr = this.array() for (var i = 0; i < arr.length; i++) { if (re.test(arr[i])) { this.remove(arr[i]) } } return this } /** * Toggle class `name`, can force state via `force`. * * For browsers that support classList, but do not support `force` yet, * the mistake will be detected and corrected. * * @param {String} name * @param {Boolean} force * @return {ClassList} * @api public */ ClassList.prototype.toggle = function (name, force) { // classList if (this.list) { if (typeof force !== 'undefined') { if (force !== this.list.toggle(name, force)) { this.list.toggle(name) // toggle again to correct } } else { this.list.toggle(name) } return this } // fallback if (typeof force !== 'undefined') { if (!force) { this.remove(name) } else { this.add(name) } } else { if (this.has(name)) { this.remove(name) } else { this.add(name) } } return this } /** * Return an array of classes. * * @return {Array} * @api public */ ClassList.prototype.array = function () { var className = this.el.getAttribute('class') || '' var str = className.replace(/^\s+|\s+$/g, '') var arr = str.split(re) if (arr[0] === '') arr.shift() return arr } /** * Check if class `name` is present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.has = ClassList.prototype.contains = function (name) { return this.list ? this.list.contains(name) : !!~indexof(this.array(), name) } ================================================ FILE: src/components/range/lib/closest.js ================================================ /** * Module Dependencies */ import matches from './matches-selector' /** * Export `closest` */ export default closest /** * Closest * * @param {Element} el * @param {String} selector * @param {Element} scope (optional) */ function closest (el, selector, scope) { scope = scope || document.documentElement // walk up the dom while (el && el !== scope) { if (matches(el, selector)) return el el = el.parentNode } // check scope for match return matches(el, selector) ? el : null } ================================================ FILE: src/components/range/lib/delegate.js ================================================ /** * Module dependencies. */ import closest from './closest' import event from './event' /** * Delegate event `type` to `selector` * and invoke `fn(e)`. A callback function * is returned which may be passed to `.unbind()`. * * @param {Element} el * @param {String} selector * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ export const bind = function (el, selector, type, fn, capture) { return event.bind(el, type, function (e) { var target = e.target || e.srcElement e.delegateTarget = closest(target, selector, true, el) if (e.delegateTarget) fn.call(el, e) }, capture) } /** * Unbind event `type`'s callback `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @api public */ export const unbind = function (el, type, fn, capture) { event.unbind(el, type, fn, capture) } ================================================ FILE: src/components/range/lib/emitter.js ================================================ /** * Expose `Emitter`. */ export default Emitter /** * Initialize a new `Emitter`. * * @api public */ function Emitter (obj) { if (obj) return mixin(obj) } /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin (obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key] } return obj } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = Emitter.prototype.addEventListener = function (event, fn) { this._callbacks = this._callbacks || {} ;(this._callbacks['$' + event] = this._callbacks['$' + event] || []).push(fn) return this } /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function (event, fn) { function on () { this.off(event, on) fn.apply(this, arguments) } on.fn = fn this.on(event, on) return this } /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = Emitter.prototype.removeEventListener = function (event, fn) { this._callbacks = this._callbacks || {} // all if (!arguments.length) { this._callbacks = {} return this } // specific event var callbacks = this._callbacks['$' + event] if (!callbacks) return this // remove all handlers if (arguments.length === 1) { delete this._callbacks['$' + event] return this } // remove specific handler var cb for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i] if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1) break } } return this } /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function (event) { this._callbacks = this._callbacks || {} var args = [].slice.call(arguments, 1) var callbacks = this._callbacks['$' + event] if (callbacks) { callbacks = callbacks.slice(0) for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args) } } return this } /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function (event) { this._callbacks = this._callbacks || {} return this._callbacks['$' + event] || [] } /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function (event) { return !!this.listeners(event).length } ================================================ FILE: src/components/range/lib/event.js ================================================ /** * Bind `el` event `type` to `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ const bind = function (el, type, fn, capture) { var bind = window.addEventListener ? 'addEventListener' : 'attachEvent' var prefix = bind !== 'addEventListener' ? 'on' : '' el[bind](prefix + type, fn, capture || false) return fn } /** * Unbind `el` event `type`'s callback `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ const unbind = function (el, type, fn, capture) { var bind = window.addEventListener ? 'addEventListener' : 'attachEvent' var prefix = bind !== 'addEventListener' ? 'on' : '' var unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent' el[unbind](prefix + type, fn, capture || false) return fn } export default { bind, unbind } ================================================ FILE: src/components/range/lib/events.js ================================================ /** * Module dependencies. */ import events from './event' import {bind} from './delegate' /** * Expose `Events`. */ export default Events /** * Initialize an `Events` with the given * `el` object which events will be bound to, * and the `obj` which will receive method calls. * * @param {Object} el * @param {Object} obj * @api public */ function Events (el, obj) { if (!(this instanceof Events)) return new Events(el, obj) if (!el) throw new Error('element required') if (!obj) throw new Error('object required') this.el = el this.obj = obj this._events = {} } /** * Subscription helper. */ Events.prototype.sub = function (event, method, cb) { this._events[event] = this._events[event] || {} this._events[event][method] = cb } /** * Bind to `event` with optional `method` name. * When `method` is undefined it becomes `event` * with the "on" prefix. * * Examples: * * Direct event handling: * * events.bind('click') // implies "onclick" * events.bind('click', 'remove') * events.bind('click', 'sort', 'asc') * * Delegated event handling: * * events.bind('click li > a') * events.bind('click li > a', 'remove') * events.bind('click a.sort-ascending', 'sort', 'asc') * events.bind('click a.sort-descending', 'sort', 'desc') * * @param {String} event * @param {String|function} [method] * @return {Function} callback * @api public */ Events.prototype.bind = function (event, method) { var e = parse(event) var el = this.el var obj = this.obj var name = e.name method = method || 'on' + name var args = [].slice.call(arguments, 2) // callback var cb = function () { var a = [].slice.call(arguments).concat(args) obj[method].apply(obj, a) } // bind if (e.selector) { cb = bind(el, e.selector, name, cb) } else { events.bind(el, name, cb) } // subscription for unbinding this.sub(name, method, cb) return cb } /** * Unbind a single binding, all bindings for `event`, * or all bindings within the manager. * * Examples: * * Unbind direct handlers: * * events.unbind('click', 'remove') * events.unbind('click') * events.unbind() * * Unbind delegate handlers: * * events.unbind('click', 'remove') * events.unbind('click') * events.unbind() * * @param {String|Function} [event] * @param {String|Function} [method] * @api public */ Events.prototype.unbind = function (event, method) { if (arguments.length === 0) return this.unbindAll() if (arguments.length === 1) return this.unbindAllOf(event) // no bindings for this event var bindings = this._events[event] if (!bindings) return // no bindings for this method var cb = bindings[method] if (!cb) return events.unbind(this.el, event, cb) } /** * Unbind all events. * * @api private */ Events.prototype.unbindAll = function () { for (var event in this._events) { this.unbindAllOf(event) } } /** * Unbind all events for `event`. * * @param {String} event * @api private */ Events.prototype.unbindAllOf = function (event) { var bindings = this._events[event] if (!bindings) return for (var method in bindings) { this.unbind(event, method) } } /** * Parse `event`. * * @param {String} event * @return {Object} * @api private */ function parse (event) { var parts = event.split(/ +/) return { name: parts.shift(), selector: parts.join(' ') } } ================================================ FILE: src/components/range/lib/matches-selector.js ================================================ /** * Module dependencies. */ import { all } from './query' /** * Element prototype. */ let proto = {} if (typeof window !== 'undefined') { proto = window.Element.prototype } /** * Vendor function. */ var vendor = proto.matches || proto.webkitMatchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector /** * Expose `match()`. */ export default match /** * Match `el` to `selector`. * * @param {Element} el * @param {String} selector * @return {Boolean} * @api public */ function match (el, selector) { if (!el || el.nodeType !== 1) return false if (vendor) return vendor.call(el, selector) var nodes = all(selector, el.parentNode) for (var i = 0; i < nodes.length; ++i) { if (nodes[i] === el) return true } return false } ================================================ FILE: src/components/range/lib/mouse.js ================================================ /** * dependencies. */ import emitter from './emitter' import event from './event' /** * export `Mouse` */ export default function (el, obj) { return new Mouse(el, obj) } /** * initialize new `Mouse`. * * @param {Element} el * @param {Object} obj */ function Mouse (el, obj) { this.obj = obj || {} this.el = el } /** * mixin emitter. */ emitter(Mouse.prototype) /** * bind mouse. * * @return {Mouse} */ Mouse.prototype.bind = function () { var obj = this.obj var self = this // up function up (e) { obj.onmouseup && obj.onmouseup(e) event.unbind(document, 'mousemove', move) event.unbind(document, 'mouseup', up) self.emit('up', e) } // move function move (e) { obj.onmousemove && obj.onmousemove(e) self.emit('move', e) } // down self.down = function (e) { obj.onmousedown && obj.onmousedown(e) event.bind(document, 'mouseup', up) event.bind(document, 'mousemove', move) self.emit('down', e) } // bind all. event.bind(this.el, 'mousedown', self.down) return this } /** * unbind mouse. * * @return {Mouse} */ Mouse.prototype.unbind = function () { event.unbind(this.el, 'mousedown', this.down) this.down = null } ================================================ FILE: src/components/range/lib/query.js ================================================ function one (selector, el = document) { return el.querySelector(selector) } function all (selector, el = document) { return el.querySelectorAll(selector) } // function engine(obj) { // if (!obj.one) throw new Error('.one callback required') // if (!obj.all) throw new Error('.all callback required') // return exportJson // } export { one, all } // export default one ================================================ FILE: src/components/range/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - range - form zh-CN: - 选择 - 表单 props: value: type: Number default: 0 en: form value, use `v-model` for binding zh-CN: 表单值,使用`v-model`绑定 decimal: type: Boolean default: false en: if show decimals when value is changed zh-CN: 是否在变化时显示小数 min: type: Number default: 0 en: min number zh-CN: 可选最小值 max: type: Number default: 100 en: max number zh-CN: 可选最大值 step: type: Number default: 1 en: step value zh-CN: 步长 disabled: type: Boolean default: false en: if disabled zh-CN: 是否禁用 minHTML: en: custom html template for min value zh-CN: 最小值显示的html模板 maxHTML: en: custom html template for max value zh-CN: 最大值显示的html模板 disabled-opacity: type: Number en: opacity value when is disabled zh-CN: 禁用样式的透明度 range-bar-height: type: Number default: 1 en: range bar's height zh-CN: 高度 events: on-change: version: v2.2.2 params: '`(value)`' en: emits when value is changed zh-CN: 绑定值变化时触发事件 on-touchstart: version: v2.9.2 params: '`(event)`' en: trigger when finger is placed on element zh-CN: 手指放到元素上时触发 on-touchend: version: v2.9.2 params: '`(event)`' en: trigger when finger leaves the element zh-CN: 手指离开元素时触发 changes: v2.9.2: en: - '[feature] add on-touchstart and on-touchend events #2804' zh-CN: - '[feature] 新增on-touchstart 和 on-touchend事件 #2804' v2.4.0: en: - '[fix] Fix the situation cannot drag to max value #1556' zh-CN: - '[fix] 修复一些情况下无法拖到最大值的问题 #1556' v2.2.2: en: - '[feature] Support dynamically setting step #1394' zh-CN: - '[feature] 支持动态设置 step #1394' ================================================ FILE: src/components/range/powerange.js ================================================ import { findClosest, getWidth, percentage } from './utils' import classes from './lib/classes' import mouse from './lib/mouse' import events from './lib/events' function Horizontal (element, options) { this.element = element this.options = options || {} this.slider = this.create('span', 'range-bar') this.hasAppend = false if (this.element !== null && this.element.type === 'text') this.init() if (this.options.step) { this.step(this.slider.offsetWidth || this.options.initialBarWidth, getWidth(this.handle)) } this.setStart(this.options.start) } Horizontal.prototype.setStart = function (start) { var begin = (start === null) ? this.options.min : start var part = percentage.from(begin - this.options.min, this.options.max - this.options.min) || 0 var offset = percentage.of(part, this.slider.offsetWidth - this.handle.offsetWidth) var position = (this.options.step) ? findClosest(offset, this.steps) : offset this.setPosition(position) this.setValue(this.handle.style.left, this.slider.offsetWidth - this.handle.offsetWidth) } Horizontal.prototype.setStep = function () { this.step(getWidth(this.slider) || this.options.initialBarWidth, getWidth(this.handle)) } Horizontal.prototype.setPosition = function (val) { this.handle.style.left = val + 'px' this.slider.querySelector('.range-quantity').style.width = val + 'px' } Horizontal.prototype.onmousedown = function (e) { this.options.onTouchstart(e) if (e.touches) e = e.touches[0] this.startX = e.clientX this.handleOffsetX = this.handle.offsetLeft this.restrictHandleX = this.slider.offsetWidth - this.handle.offsetWidth this.unselectable(this.slider, true) } Horizontal.prototype.changeEvent = function (state) { if (typeof Event === 'function' || !document.fireEvent) { var event = document.createEvent('HTMLEvents') event.initEvent('change', false, true) this.element.dispatchEvent(event) } else { this.element.fireEvent('onchange') } } Horizontal.prototype.onmousemove = function (e) { e.preventDefault() if (e.touches) e = e.touches[0] var leftOffset = this.handleOffsetX + e.clientX - this.startX var position = (this.steps) ? findClosest(leftOffset, this.steps) : leftOffset if (leftOffset <= 0) { this.setPosition(0) } else if (leftOffset >= this.restrictHandleX) { this.setPosition(this.restrictHandleX) } else { this.setPosition(position) } this.setValue(this.handle.style.left, this.slider.offsetWidth - this.handle.offsetWidth) } Horizontal.prototype.unselectable = function (element, set) { if (!classes(this.slider).has('unselectable') && set === true) { classes(this.slider).add('unselectable') } else { classes(this.slider).remove('unselectable') } } Horizontal.prototype.onmouseup = function (e) { this.options.onTouchend(e) this.unselectable(this.slider, false) } Horizontal.prototype.disable = function (force) { if (this.options.disable || force) { this.mouse.unbind() this.touch.unbind() } if (this.options.disable) { if (this.options.disableOpacity) { this.slider.style.opacity = this.options.disableOpacity } classes(this.slider).add('range-bar-disabled') } } Horizontal.prototype.init = function () { this.hide() this.append() this.bindEvents() this.checkValues(this.options.start) this.setRange(this.options.min, this.options.max) this.disable() } Horizontal.prototype.reInit = function (opts) { this.options.start = opts.value this.options.min = opts.min this.options.max = opts.max this.options.step = opts.step this.disable(true) this.init() } Horizontal.prototype.checkStep = function (value) { if (value < 0) value = Math.abs(value) this.options.step = value return this.options.step } Horizontal.prototype.setValue = function (offset, size) { var part = percentage.from(parseFloat(offset), size) if (offset === '0px' || size === 0) { value = this.options.min } else { var value = percentage.of(part, this.options.max - this.options.min) + this.options.min value = (this.options.decimal) ? (Math.round(value * 100) / 100) : Math.round(value) if (value > this.options.max) { value = this.options.max } } var changed = false changed = this.element.value !== value this.element.value = value this.options.callback(value) if (changed) this.changeEvent() } Horizontal.prototype.checkValues = function (start) { if (start < this.options.min) this.options.start = this.options.min if (start > this.options.max) this.options.start = this.options.max if (this.options.min >= this.options.max) this.options.min = this.options.max } Horizontal.prototype.step = function (sliderSize, handleSize) { var dimension = sliderSize - handleSize var part = percentage.from(this.checkStep(this.options.step), this.options.max - this.options.min) var interval = percentage.of(part, dimension) var steps = [] for (let i = 0; i <= dimension; i += interval) { steps.push(i) } this.steps = steps for (let i = 10; i >= 0; i--) { this.steps[steps.length - i] = dimension - interval * i } return this.steps } Horizontal.prototype.create = function (type, name) { var elem = document.createElement(type) elem.className = name return elem } Horizontal.prototype.insertAfter = function (reference, target) { reference.parentNode.insertBefore(target, reference.nextSibling) } Horizontal.prototype.setRange = function (min, max) { if (typeof min === 'number' && typeof max === 'number' && !this.options.hideRange) { this.slider.querySelector('.range-min').innerHTML = this.options.minHTML || min this.slider.querySelector('.range-max').innerHTML = this.options.maxHTML || max } } Horizontal.prototype.generate = function () { var elems = { 'handle': { 'type': 'span', 'selector': 'range-handle' }, 'min': { 'type': 'span', 'selector': 'range-min' }, 'max': { 'type': 'span', 'selector': 'range-max' }, 'quantity': { 'type': 'span', 'selector': 'range-quantity' } } for (var key in elems) { if (elems.hasOwnProperty(key)) { var temp = this.create(elems[key].type, elems[key].selector) this.slider.appendChild(temp) } } return this.slider } Horizontal.prototype.append = function () { if (!this.hasAppend) { var slider = this.generate() this.insertAfter(this.element, slider) } this.hasAppend = true } Horizontal.prototype.hide = function () { this.element.style.display = 'none' } Horizontal.prototype.bindEvents = function () { this.handle = this.slider.querySelector('.range-handle') this.touch = events(this.handle, this) this.touch.bind('touchstart', 'onmousedown') this.touch.bind('touchmove', 'onmousemove') this.touch.bind('touchend', 'onmouseup') this.mouse = mouse(this.handle, this) this.mouse.bind() } var defaults = { callback () {}, decimal: false, disable: false, disableOpacity: null, hideRange: false, min: 0, max: 100, start: null, step: null, vertical: false } export default function (element, options) { options = options || {} for (let i in defaults) { if (options[i] == null) { options[i] = defaults[i] } } return new Horizontal(element, options) } ================================================ FILE: src/components/range/powerange.less ================================================ /** * * Main stylesheet for Powerange. * http://abpetkov.github.io/powerange/ * */ /** * Horizontal slider style (default). */ @import '../../styles/variable.less'; .range-bar { background-color: @range-bar-default-color; border-radius: 15px; display: block; height: 1px; position: relative; width: 100%; } .range-bar-disabled { opacity: @range-disabled-opacity; } .range-quantity { background-color: @range-bar-active-color; border-radius: 15px; display: block; height: 100%; width: 0; } .range-handle { background-color: #fff; border-radius: 100%; cursor: move; height: 30px; left: 0; top: -13px; position: absolute; width: 30px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); } .range-min, .range-max { color: #181819; font-size: 12px; position: absolute; text-align: center; top: 50%; transform: translateY(-50%); width: 24px; } .range-min { left: -30px; } .range-max { right: -30px; } /** * Style for disabling text selection on handle move. */ .unselectable { user-select: none; } /** * Style for handle cursor on disabled slider. */ .range-disabled { cursor: default; } ================================================ FILE: src/components/range/utils.js ================================================ const indexof = function (arr, obj) { if (arr.indexOf) return arr.indexOf(obj) for (let i = 0; i < arr.length; ++i) { if (arr[i] === obj) return i } return -1 } const findClosest = function (target, points) { var diff = null var current = null var closest = points[0] for (let i = 0; i < points.length; i++) { diff = Math.abs(target - closest) current = Math.abs(target - points[i]) if (current < diff) { closest = points[i] } } return closest } function getWidth (el) { let width = window.getComputedStyle(el, null)['width'] if (width === '100%' || width === 'auto') { return 0 } return parseInt(width, 10) } const percentage = { isNumber: function (num) { return typeof num === 'number' }, of: function (perc, num) { if (percentage.isNumber(perc) && percentage.isNumber(num)) return (perc / 100) * num }, from: function (part, target) { if (percentage.isNumber(part) && percentage.isNumber(target)) return (part / target) * 100 } } export { indexof, findClosest, getWidth, percentage } ================================================ FILE: src/components/rater/index.vue ================================================ ================================================ FILE: src/components/rater/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' color: '#FF9900' tags: en: - form - rate - rater zh-CN: - 表单 - 星级 - 评分 props: max: type: Number default: 5 en: total star number zh-CN: 最多可选个数 value: type: Number default: 0 en: selected value, use `v-model` for binding zh-CN: 值,使用 v-model 绑定 disabled: type: Boolean default: false en: if disabled zh-CN: 是否禁用 star: type: String default: '★' en: start's symbol text zh-CN: 字符 active-color: type: String default: '#fc6' en: symbol color when selected zh-CN: 选中时的颜色 margin: type: Number default: 2 en: margin value zh-CN: 间隙值 font-size: type: Number default: 25 en: font size zh-CN: 字体大小 min: type: Number version: v2.7.9 default: 0 en: min value zh-CN: 最小值 changes: v2.7.9: en: - '[feature] prop:min add min value #2439' zh-CN: - '[feature] prop:min 增加最小值 #2439' v2.7.3: en: - '[feature] prop:star support html #2182' zh-CN: - '[feature] prop:star 支持 html #2182' ================================================ FILE: src/components/scroller/index.vue ================================================ ================================================ FILE: src/components/scroller/metas.yml ================================================ category: en: Deprecated 'zh-CN': 不再维护 category_order: 999 icon: '' tags: en: - layout - scroll - scroller zh-CN: - 布局 - 滚动 extra: | ::: warning 该组件已经不再维护,也不建议使用,大部分情况下也不需要用到该组件。
    建议使用第三方相关组件,相关 issue 将不会处理。 ::: ::: warning `Scroller`的内容必须是一个`div`,并且只能有一个`div`
    `Scroller` 希望解决的是简单的列表问题而不是一个内嵌各种复杂标签交互的容器,很容易发生性能或者交互问题。

    默认高度为整个视口高度,如果你加了`x-header`,那么你需要减去一个`x-header`的高度: `height="-46"`

    请确保在你的数据更新后进行`reset`操作(参考下面文档),如果你做了`reset`还有问题再开`issue`并附上代码,否则将`绝对不会被处理`。 ::: after_extra: | pulldown 默认配置: ``` js { content: 'Pull Down To Refresh', height: 60, autoRefresh: false, downContent: 'Pull Down To Refresh', upContent: 'Release To Refresh', loadingContent: 'Loading...', clsPrefix: 'xs-plugin-pulldown-' } ``` pullup 默认配置: ``` js { content: 'Pull Up To Refresh', pullUpHeight: 60, height: 40, autoRefresh: false, downContent: 'Release To Refresh', upContent: 'Pull Up To Refresh', loadingContent: 'Loading...', clsPrefix: 'xs-plugin-pullup-' } ``` QA 如何更新数据 如果展示内容只是简单的增加或者减少,直接调用`reset`方法即可 ``` js this.$nextTick(() => { this.$refs.scroller.reset() }) ``` 如果展示内容完全重载,那么需要主动设置位置让其能正确回到顶部。适用于改变筛选条件后重载数据的情况 ``` js this.$nextTick(() => { this.$refs.scroller.reset({ top: 0 }) }) ``` QA 如何设置pullup完成 方法1,直接调用ref的`donePullup`方法 ``` js this.$refs.demo2.donePullup() ``` 方法2,绑定value, 重置状态 ::: tip 自定义pullup模板同样是用v-model来绑定以获取状态变化 ::: ``` html ``` ``` js data () { return { status: { pullupStatus: 'default' } } } ``` ``` // 重置状态为default this.status.pullupStatus = 'default ``` QA 如何设置pulldown完成 参照`pullup`, 使用方法`donePulldown`或者绑定`pulldownStatus`然后重置为`default`。 QA 如何禁用或者启用pullup 在有些情况下,比如数据不多不需要上拉加载或者已经加载完成,我们需要禁用pullup 同样可以调用方法 `disablePullup` 或者设置`pullupStatus`为`disabled` 反之,则调用方法 `enablePullup` 或者设置`pullupStatus`为`enabled` 启用keep-alive后滚动有问题 需要在调用Scroller的页面上在 hook: `activated`上执行`reset` ``` js activated () { this.$refs.scroller.reset() } ``` events: on-scroll: params: '`(position)`' en: emits when user scrolls zh-CN: '容器滚动时触发,参数为`top`和`left`位置' on-scroll-bottom: version: v2.2.1-rc.6 en: emits when user scrolls to bottom. The event may emit multiple times, so you should use a variable for keeping status if you need fetching data zh-CN: 滚动到底部时触发,注意事件会触发多次,如果你需要进行数据获取,记得设置一个状态值 on-pulldown-loading: en: emits when scroller is on pulldown-loading status zh-CN: 用户触发下拉刷新状态,监听该事件以获取加载新数据 on-pullup-loading: en: emits when scroller is on pullup-loading status zh-CN: 用户触发上拉加载状态,监听该事件以加载新数据 props: value: type: 'Object' default: '' en: current pulldown and pullup status, use `v-model` for binding zh-CN: 对象,上拉或者下拉的状态双向绑定,使用 v-model 绑定,pulldownStatus 及 pullupStatus height: type: String default: 'viewport height' en: "scroller's height, you can use `height=-40` to make scroller set to height: `viewportHeight - 40px`" zh-CN: '容器高度,默认为整个viewport高度,注意,该属性接受的是 String 类型,比如 200px,如果你希望scroller自动计算除去头部尾部的高度,请这样设置让组件自动计算,如`height="-40"`' lock-x: type: Boolean default: false en: lock the horizontal direction zh-CN: 锁定X方向 lock-y: type: Boolean default: false en: lock the vertical direction zh-CN: 锁定Y方向 scrollbar-x: type: Boolean default: false en: if show horizontal scrollbar zh-CN: 是否显示横向滚动条 scrollbarY: type: Boolean default: false en: if show vertical scrollerbar zh-CN: 是否显示垂直方向滚动条 bounce: type: Boolean default: true en: if use bounce effect zh-CN: 是否显示边缘回弹效果 use-pulldown: type: Boolean default: false en: if use pulldown plugin zh-CN: 是否使用下拉组件 use-pullup: type: Boolean default: false en: if use pullup plugin zh-CN: 是否使用上拉组件 pulldown-config: type: Object default: see below en: config for pulldown plugin zh-CN: 下拉组件配置 pullup-config: type: Object default: see below en: config for pullup plugin zh-CN: 上拉组件配置 scroll-bottom-offset: version: v2.2.1-rc.6 type: Number default: 0 en: where to emit event:on-scroll-bottom before reaching bottom zh-CN: 在距离底部多长距离时触发事件 on-scroll-bottom slots: default: en: scroller content, should has only one `div` root zh-CN: scroller 内容,必须是一个 `div` 元素 methods: reset: params: '`(position, duration, easing)`' en: 'reset scroller to reset height whenever your data is changed. easing can be one of ease-in, ease-in-out, ease, bezier(n, n, n, n)' zh-CN: '在内容变化(v-for渲染,异步数据加载)后需要调用,用以重新渲染,避免新加的内容无法上拉看到,一般在 $nextTick 回调里调用。easing 可以为 ease-in, ease-in-out, ease, bezier(n, n, n, n)' donePullup: en: set pullup done after new data is fetched zh-CN: 设置上拉刷新操作完成,在数据加载后执行 disablePullup: en: disable pullup plugin when there is no more data zh-CN: 禁用上拉刷新,在没有更多数据时执行 enablePullup: en: enable pullup plugin zh-CN: 启用上拉刷新插件 donePulldown: en: set pulldown done after new data is fetched zh-CN: 设置下拉刷新操作完成,在数据加载后执行 changes: v2.6.1: en: - '[change] call `reset` in `updated` hook' zh-CN: - '[change] 在`updated` 钩子中调用`reset`' v2.2.1-rc.8: en: - '[change] set prop:prevent-default to false by default' zh-CN: - '[change] prop:prevent-default 默认设为 false' v2.2.1-rc.6: en: - '[feature] Support event:on-scroll-bottom' - '[fix] Disable trigger click' zh-CN: - '[feature] 支持滚动到底部时触发事件 on-scroll-bottom' - '[fix] 禁止点击事件触发,修复 pc 上触发两次的问题' v2.2.1-rc.4: en: - '[feature] Support duration and easing for reset function. #1240' zh-CN: - '[feature] 支持在滚回顶部时设置时间和缓动 #1240' v2.2.0: en: - '[fix] Fix issue when redirecting on scrolling #1187' - '[fix] Fix resize listener not being destroyed #1183' zh-CN: - '[fix] 修复 resize 事件没有移除导致切换页面报错 #1183' - '[fix] 修复滚动情况下跳转页面报错 #1187' ================================================ FILE: src/components/search/index.vue ================================================ cancel_text: en: cancel zh-CN: 取消 placeholder: en: Search zh-CN: 搜索 ================================================ FILE: src/components/search/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - form - search zh-CN: - 表单 - 搜索 props: placeholder: default: 搜索(search) en: placeholder text zh-CN: 提示文字 cancel-text: default: 取消(cancel) en: cancel text zh-CN: 取消文字 value: en: input value, use `v-model` for binding zh-CN: 表单值,`v-model`绑定 results: type: Array en: "search results, [{title: 'hello', otherData: otherValue}], will not show when auto-fixed is false" zh-CN: "指定搜索结果, 为带有 title key 的对象组成的数组,如 [{title: 'hello', otherData: otherValue}], auto-fixed 为 false 时不会显示结果" auto-fixed: type: Boolean default: true en: if auto fixed on top when input gets focus zh-CN: 是否自动固定在顶端 top: type: String default: '0px' en: top value when fixed on top zh-CN: 自动固定时距离顶部的距离 position: type: String default: 'fixed' en: position value when pin the component on the top, one of `fixed`, `absolute` zh-CN: '自动固定时的定位,一些布局下可能需要使用其他定位,比如`absolute`' auto-scroll-to-top: type: Boolean default: false en: if auto scroll to top when input gets focus zh-CN: '`Safari`下弹出键盘时可能会出现看不到input,需要手动滚动,启用该属性会在fix时滚动到顶端' type: type: String default: search en: input type zh-CN: '即input的`type`属性' events: on-submit: params: '`(value)`' en: emits when form is submitted zh-CN: 表单提交时触发 on-cancel: en: emits when cancel button is clicked zh-CN: '点击`取消`按钮时触发' on-change: params: '`(value)`' en: emits when value is changed zh-CN: 输入文字变化时触发 on-result-click: params: '`(item)`' en: emits when result item is clicked zh-CN: '点击结果条目时触发,原来的`result-click`事件不符合规范已经废弃' on-focus: version: v2.1.1-rc.10 en: when input gets focus zh-CN: 输入框获取到焦点时触发 on-blur: version: v2.6.3 en: fires when input losts focus zh-CN: 输入框失去焦点时触发 on-clear: version: v2.9.0 en: fires when clicking clear icon zh-CN: 点击清除按钮时触发 slots: default: en: slot above search result zh-CN: 搜索结果列表上面 slot,可以用来自定义搜索结果显示区域(results 设为空) right: en: slot on the right of input zh-CN: 输入框右侧 slot left: version: v2.3.5 en: slot on the left of input zh-CN: 输入框左侧 slot methods: setFocus: en: "set focus on input. you should call this method in a click event's callback function on Safari" zh-CN: 获取 input 焦点,在 Safari 上你必须在 click 事件回调里使用才能生效 setBlur: version: v2.3.6 en: make input lost focus zh-CN: 手动设置 input 失去焦点,一般用于在 on-submit 事件中实现隐藏手机键盘 changes: next: en: - '[feature] Add prop:type' zh-CN: - '[feature] 添加属性 type' v2.9.0: en: - '[feature] add event:on-clear #2251' zh-CN: - '[feature] 添加事件 on-clear #2251' 2.7.6: en: - '[fix] fix not emit on-change and input events when cancel or clear' zh-CN: - '[fix] 修复 在 cancel 或 clear 时没有触发 on-change 和 input 事件' v2.7.3: en: - '[fix] fix event:on-change fires before compositionend' zh-CN: - '[fix] 修复输入法导致 on-change 事件提前触发' v2.6.3: en: - '[feature] add event:on-blur' zh-CN: - '[feature] 添加事件 on-blur' v2.3.6: en: - '[feature] Add method:setBlur' zh-CN: - '[feature] 添加方法 setBlur' v2.3.5: en: - '[feature] Add slot:left' zh-CN: - '[feature] 增加 slot:left' v2.2.2: en: - '[enhance] Show search text on iOS keyboard' zh-CN: - '[enhance] 在 iOS 键盘上显示 search' v2.2.0: en: - '[enhance] emit on-change after input' zh-CN: - '[enhance] 在 input 后触发 on-change 事件(否则此时 v-model 绑定的值和参数不一致)' v2.1.1-rc.13: en: - '[feature] Add slot:right' zh-CN: - '[feature] 添加 slot=right' v2.1.1-rc.12: en: - '[enhance] do not emit on-change is isCancel = true' zh-CN: - '[enhance] 点击取消按钮后不再触发 on-change 事件' v2.1.1-rc.10: en: - '[feature] Add event on-focus' zh-CN: - '[feature] 支持 on-focus 事件' v2.1.0: zh-CN: - '[feature] 更新到`WeUI`最新代码' - '[enhance] 当`value`为空时,不显示清除按钮' v2.1.0-rc.47: zh-CN: - '[enhance] 删除无用代码' - '[fix] 修复`取消`按钮在非`fixed`情况下没有出现的问题 @excitedcat' - '[fix] 修复页面上多个实例时,`label id`冲突' ================================================ FILE: src/components/search/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Search', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('search') }) }) ================================================ FILE: src/components/selector/index.vue ================================================ ================================================ FILE: src/components/selector/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' extra: zh-CN: | ```html ``` ::: tip `selector`只能在`Group`中使用 ::: ::: warning 在`iOS`上,如果没有指定`placeholder`也没有指定`value`,会出现弹出选择框时默认选中第一个值,但是确定后依然没有选中的情况。
    因此对于`iOS`,组件内部在列表项前面增加了一个空的`option`,强制用户滑动选择一次以避免上面的问题。 ::: tags: en: - form - select - selector - option zh-CN: - 选择 - 下拉选择 tips: - - en: key should be a `String` if you use key=>value - zh-CN: 选项的key必须是字符串,使用数字会出现问题 props: value: type: String,Number,Object en: form value, use `v-model` for binding zh-CN: 表单值,使用v-model绑定 title: en: label text zh-CN: 标题 direction: en: align value zh-CN: 选项对齐方式,同原生 select 属性一致,可选值为 ltr(left-to-right,默认), rtl options: type: Array en: "option list, `['one', 'two']` or `[{ key: KEY, value: VALUE }]`" zh-CN: '选项列表,可以为简单数组,或者 `{ key: KEY, value: VALUE }` 结构的键值对数组。当使用键值对时,返回的`value`为`key`的值。' name: en: form name zh-CN: 表单的name名字 placeholder: en: placeholder zh-CN: 提示文字 readonly: type: Boolean default: 'false' en: if the select is readonly zh-CN: 是否不可选择 value-map: version: v2.7.2 type: Array en: 'set a [value, label] pair for automatically transforming API data' zh-CN: 设置键值对映射用以自动转换接口数据, 如 ['value', 'label'] events: on-change: params: '`(value)`' en: emits when value is changed zh-CN: 值变化时触发 methods: getFullValue: version: v2.7.2 en: get full value including all the props(if option items are object) zh-CN: 获取当前完整值,在使用了 valueMap 里可以用该方法来获取当前选中值的原始对象 changes: 2.9.3: en: - '[fix] fix the chinese bracket show error when direction=rtl #2864' zh-CN: - '[fix] 修复direction=rtl时的中文括号显示异常 #2864' v2.9.0: en: - '[fix] avoid newer-version-vue select value issue #2633 #2587' - '[enhance] use v-html for title #2615' zh-CN: - '[fix] 修复新版本 vue 导致的 select 值问题 #2633 #2587' - '[enhance] 标题使用 v-html #2615' v2.7.2: en: - '[feature] add prop:value-map for automatically transform API data #2139' - '[feature] add method:getFullValue for getting full value' zh-CN: - '[feature] 支持 value-map 属性用以自动转换 API 数据 #2139' - '[feature] 支持 getFullValue 方法获取当中选中值的原始数据' v2.7.0: en: - '[fix] fix ssr rendering issue' - '[fix] fix placeholder not show when value is null #2101' zh-CN: - '[fix] 兼容服务端渲染' - '[fix] 修复值为 null 时 placeholder 没有显示的问题 #2101' v2.3.3: en: - '[enhance] set placeholder color. #1465' zh-CN: - '[enhance] 设置 placeholder 颜色. #1465' v2.2.2: en: - '[fix] fix label:for missing' zh-CN: - '[fix] 修复 label for 属性值缺失' v2.2.1-rc.6: en: - '[fix] fix placeholder. #1273' zh-CN: - '[fix] 修复 placeholder 逻辑问题. #1273' v2.1.1-rc.14: en: - '[fix] fix can not set Selector name attrbiute bug #1133' - '[enhance] Selector value support Boolean. @rbao' zh-CN: - '[fix] 修复Selector不能设置name属性的问题#1133' - '[enhance] Selector的value支持布尔型. @rbao' ================================================ FILE: src/components/selector/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Selector', () => { it('basic', () => { const wrapper = mount(Comp, { propsData: { options: [] } }) expect(wrapper.name()).to.equal('selector') }) }) ================================================ FILE: src/components/shake/index.vue ================================================ ================================================ FILE: src/components/spinner/index.vue ================================================ ================================================ FILE: src/components/spinner/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' props: type: type: String default: 'ios' en: icon type zh-CN: 图标类型 changes: v2.3.6: en: - '[feature] Support prop:size #1447' zh-CN: - '[feature] 支持 prop:size #1447' ================================================ FILE: src/components/spinner/requestAnimationFrame.js ================================================ var lastTime = 0 var vendors = ['webkit', 'moz'] for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'] } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (callback, element) { var currTime = new Date().getTime() var timeToCall = Math.max(0, 16 - (currTime - lastTime)) var id = window.setTimeout(function () { callback(currTime + timeToCall) }, timeToCall) lastTime = currTime + timeToCall return id } } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (id) { clearTimeout(id) } } ================================================ FILE: src/components/spinner/spinner.js ================================================ /** * http://ionicframework.com/docs/api/directive/ionSpinner/ */ import './requestAnimationFrame' var TRANSLATE32 = 'translate(32,32)' var STROKE_OPACITY = 'stroke-opacity' var ROUND = 'round' var INDEFINITE = 'indefinite' var DURATION = '750ms' var NONE = 'none' var SHORTCUTS = { a: 'animate', an: 'attributeName', at: 'animateTransform', c: 'circle', da: 'stroke-dasharray', os: 'stroke-dashoffset', f: 'fill', lc: 'stroke-linecap', rc: 'repeatCount', sw: 'stroke-width', t: 'transform', v: 'values' } var SPIN_ANIMATION = { v: '0,32,32;360,32,32', an: 'transform', type: 'rotate', rc: INDEFINITE, dur: DURATION } function createSvgElement (tagName, data, parent, spinnerName, size) { var ele = document.createElement(SHORTCUTS[tagName] || tagName) var k, x, y for (k in data) { if (Object.prototype.toString.call(data[k]) === '[object Array]') { for (x = 0; x < data[k].length; x++) { if (data[k][x].fn) { for (y = 0; y < data[k][x].t; y++) { createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName) } } else { createSvgElement(k, data[k][x], ele, spinnerName) } } } else { setSvgAttribute(ele, k, data[k]) } } if (size && size !== '28px') { setSvgAttribute(ele, 'style', `width: ${size}; height: ${size}`) } parent.appendChild(ele) } function setSvgAttribute (ele, k, v) { ele.setAttribute(SHORTCUTS[k] || k, v) } function animationValues (strValues, i) { var values = strValues.split(';') var back = values.slice(i) var front = values.slice(0, values.length - back.length) values = back.concat(front).reverse() return values.join(';') + ';' + values[0] } var IOS_SPINNER = { sw: 4, lc: ROUND, line: [{ fn (i, spinnerName) { return { y1: spinnerName === 'ios' ? 17 : 12, y2: spinnerName === 'ios' ? 29 : 20, t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')', a: [{ fn () { return { an: STROKE_OPACITY, dur: DURATION, v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i), rc: INDEFINITE } }, t: 1 }] } }, t: 12 }] } var spinners = { android: { c: [{ sw: 6, da: 128, os: 82, r: 26, cx: 32, cy: 32, f: NONE }] }, ios: IOS_SPINNER, 'ios-small': IOS_SPINNER, bubbles: { sw: 0, c: [{ fn (i) { return { cx: 24 * Math.cos(2 * Math.PI * i / 8), cy: 24 * Math.sin(2 * Math.PI * i / 8), t: TRANSLATE32, a: [{ fn () { return { an: 'r', dur: DURATION, v: animationValues('1;2;3;4;5;6;7;8', i), rc: INDEFINITE } }, t: 1 }] } }, t: 8 }] }, circles: { c: [{ fn (i) { return { r: 5, cx: 24 * Math.cos(2 * Math.PI * i / 8), cy: 24 * Math.sin(2 * Math.PI * i / 8), t: TRANSLATE32, sw: 0, a: [{ fn () { return { an: 'fill-opacity', dur: DURATION, v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i), rc: INDEFINITE } }, t: 1 }] } }, t: 8 }] }, crescent: { c: [{ sw: 4, da: 128, os: 82, r: 26, cx: 32, cy: 32, f: NONE, at: [SPIN_ANIMATION] }] }, dots: { c: [{ fn (i) { return { cx: 16 + (16 * i), cy: 32, sw: 0, a: [{ fn () { return { an: 'fill-opacity', dur: DURATION, v: animationValues('.5;.6;.8;1;.8;.6;.5', i), rc: INDEFINITE } }, t: 1 }, { fn () { return { an: 'r', dur: DURATION, v: animationValues('4;5;6;5;4;3;3', i), rc: INDEFINITE } }, t: 1 }] } }, t: 3 }] }, lines: { sw: 7, lc: ROUND, line: [{ fn (i) { return { x1: 10 + (i * 14), x2: 10 + (i * 14), a: [{ fn () { return { an: 'y1', dur: DURATION, v: animationValues('16;18;28;18;16', i), rc: INDEFINITE } }, t: 1 }, { fn () { return { an: 'y2', dur: DURATION, v: animationValues('48;44;36;46;48', i), rc: INDEFINITE } }, t: 1 }, { fn () { return { an: STROKE_OPACITY, dur: DURATION, v: animationValues('1;.8;.5;.4;1', i), rc: INDEFINITE } }, t: 1 }] } }, t: 4 }] }, ripple: { f: NONE, 'fill-rule': 'evenodd', sw: 3, circle: [{ fn (i) { return { cx: 32, cy: 32, a: [{ fn () { return { an: 'r', begin: (i * -1) + 's', dur: '2s', v: '0;24', keyTimes: '0;1', keySplines: '0.1,0.2,0.3,1', calcMode: 'spline', rc: INDEFINITE } }, t: 1 }, { fn () { return { an: STROKE_OPACITY, begin: (i * -1) + 's', dur: '2s', v: '.2;1;.2;0', rc: INDEFINITE } }, t: 1 }] } }, t: 2 }] }, spiral: { defs: [{ linearGradient: [{ id: 'sGD', gradientUnits: 'userSpaceOnUse', x1: 55, y1: 46, x2: 2, y2: 46, stop: [{ offset: 0.1, class: 'stop1' }, { offset: 1, class: 'stop2' }] }] }], g: [{ sw: 4, lc: ROUND, f: NONE, path: [{ stroke: 'url(#sGD)', d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9' }, { d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32' }], at: [SPIN_ANIMATION] }] } } var animations = { android (ele) { var self = this this.stop = false var rIndex = 0 var rotateCircle = 0 var startTime var svgEle = ele.querySelector('g') var circleEle = ele.querySelector('circle') function run () { if (self.stop) return var v = easeInOutCubic(Date.now() - startTime, 650) var scaleX = 1 var translateX = 0 var dasharray = (188 - (58 * v)) var dashoffset = (182 - (182 * v)) if (rIndex % 2) { scaleX = -1 translateX = -64 dasharray = (128 - (-58 * v)) dashoffset = (182 * v) } var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex] setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128)) setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0)) setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)') rotateCircle += 4.1 if (rotateCircle > 359) rotateCircle = 0 setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)') if (v >= 1) { rIndex++ if (rIndex > 7) rIndex = 0 startTime = Date.now() } window.requestAnimationFrame(run) } return function () { startTime = Date.now() run() return self } } } function easeInOutCubic (t, c) { t /= c / 2 if (t < 1) return 1 / 2 * t * t * t t -= 2 return 1 / 2 * (t * t * t + 2) } export default function (el, icon, size) { var spinnerName, anim // eslint-disable-line spinnerName = icon var container = document.createElement('div') createSvgElement('svg', { viewBox: '0 0 64 64', g: [spinners[spinnerName]] }, container, spinnerName, size) // Specifically for animations to work, // Android 4.3 and below requires the element to be // added as an html string, rather than dynmically // building up the svg element and appending it. el.innerHTML = container.innerHTML start() function start () { if (animations[spinnerName]) { anim = animations[spinnerName](el)() } } return el } ================================================ FILE: src/components/step/index.js ================================================ import Step from './step' import StepItem from './step-item' export { Step, StepItem } ================================================ FILE: src/components/step/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' items: - step - step-item step: props: step-item: props: changes: v2.1.1-rc.13: en: - '[fix] use export instead of module.exports' zh-CN: - '[fix] 修复 import 和 module.exports 混用的问题' ================================================ FILE: src/components/step/step-item.vue ================================================ ================================================ FILE: src/components/step/step.vue ================================================ ================================================ FILE: src/components/sticky/index.vue ================================================ ================================================ FILE: src/components/sticky/metas.yml ================================================ category: en: Layout zh-CN: 布局 icon: '' tags: en: - layout - sticky zh-CN: - 布局 - 定位 extra: | ::: tip 在Chrome模拟器运行时你可能会发现没有效果,这是因为Chrome并不支持原生sticky实现而模拟器环境是iPhone,目前程序是根据Safari版本来判断是否原生支持sticky。因此你可以切换到Android系统进行测试,但是真机上是完全正常的交互。

    你也可以设置禁用原生支持检测以获得相同的效果:`:check-sticky-support="false"` ::: 如果你没有使用`100%`的布局,也没有头部可以直接这样使用 ``` html
    Blabla
    ``` 如果你像demo一样使用`view-box`和`x-header`,那么需要这样: ``` html
    Blabla
    ``` 建议加一个`div`高度为内容高度,这样可以避免当定位为`sticky`时下面的元素会突然向上走。 ``` html // 禁用原生检测时,可以在外围加
    Blabla
    // 使用原生检测时,`div`紧挨着组件之后,并设置类名`vux-sticky-fill`
    Blabla
    ``` ::: warning 请不要直接照复制 demo 代码,滚动容器为 window 时请不要指定 scroll-box,滚动容器并非使用 `view-box` 时 id 并不是 `vux_view_box_body`,请按照实际情况来设定。 ::: props: scroll-box: default: window en: scroll tagget zh-CN: '滚动容器,默认为`window`,如果你使用了viewbox,那么你需要指定容器id:`vux_view_box_body`' check-sticky-support: type: Boolean default: 'true' en: if check if sticky is supported on current browser zh-CN: 是否检测当前浏览器是否支持sticky特性,禁用则在`iPhone`设置上也使用`scroll`实现 offset: type: Number default: 0 en: top distance zh-CN: '距离顶部高度,在存在头部(如使用了`x-header`)的情况下需要设置一个距离' disabled: version: v2.9.0 type: Boolean default: false en: whether disable sticky zh-CN: 是否禁用,在某些浏览器禁用,比如万恶的 UC slots: default: en: content zh-CN: 内容插槽 methods: bindSticky: version: v2.5.4 en: manually re-bind sticky zh-CN: 手动重新绑定,用于内容变化导致位置变化定位不再正确的情况 tips: zh-CN: - q: 定位位置、时机不正确 a: | 出现这个问题可能是由于在 sticky 上方布局存在异步加载内容,比如图片、延迟加载的内容块。 这些情况下需要自己判断正确的加载完成的时机调用 `bindSticky` 方法。如果知道图片或者延迟加载的区域大小可以直接设置尺寸用以占位,其他情况下要根据实际情况处理。 相关 issue: [#2564](https://github.com/airyland/vux/issues/2564)。 - q: UC 浏览器下闪烁跳动 a: | `UC 浏览器`的 webview 不支持 `sticky` 也无法实时响应 `onscroll` 事件,fixed 定位的元素会在滚动时跳动,暂时无法解决。 如果面对的用户群使用 UC 浏览器,建议暂时使用绑定属性 `disabled` 来禁用功能。 ``` js data () { return { disabled: typeof navigator !== 'undefined' && /iphone/i.test(navigator.userAgent) && /ucbrowser/i.test(navigator.userAgent) } } ``` 如果你有解决方案,欢迎来 PR 贡献。 相关 issue [#2601](https://github.com/airyland/vux/issues/2601)。 changes: v2.9.0: en: - '[feature] add prop:disabled #2601' - '[enhance] add development tip if scroll-box doesnot exist' zh-CN: - '[feature] 添加属性 disabled #2601' - '[enhance] scroll-box 不存在时在开发模式下进行提示' v2.7.7: en: - '[fix] Fix the problem of inaccurate positioning and invalid positioning 0 #2367' zh-CN: - '[fix] 修复定位不准确和定位无效的问题 #2367' v2.5.9: en: - '[fix] Fix scrollTop always return 0 #1827' zh-CN: - '[fix] 修复 scrollTop 总是返回0的问题 #1827' v2.5.4: en: - '[fix] Auto re-bind after router-view transition is finished' - '[fix] Fix position fixed not work in ios #1657' zh-CN: - '[fix] 在 router-view 动画结束后自动重新绑定' - '[fix] 修复在ios上fixed不生效的问题 #1657' v2.1.1-rc.1: en: - '[fix] Fix wrong offset after transition between routes' - '[enhance] Set z-index #976 @olymk' zh-CN: - '[fix] 修复路由间有过渡动画导致顶部距离计算不准确的问题' - '[enhance] 设置 z-index 避免被覆盖 #976 @olymk' ================================================ FILE: src/components/sticky/sticky.js ================================================ // http://efe.baidu.com/blog/position-sticky/ // 检测iOS版本大于等于6 function gtIOS6 () { var userAgent = window.navigator.userAgent var ios = userAgent.match(/(iPad|iPhone|iPod)\s+OS\s([\d_.]+)/) return ios && ios[2] && (parseInt(ios[2].replace(/_/g, '.'), 10) >= 6) } // 判断是否支持sticky属性 function isSupportSticky () { var prefixTestList = ['', '-webkit-', '-ms-', '-moz-', '-o-'] var stickyText = '' for (var i = 0; i < prefixTestList.length; i++) { stickyText += 'position:' + prefixTestList[i] + 'sticky' } // 创建一个dom来检查 var div = document.createElement('div') var body = document.body div.style.cssText = 'display:none' + stickyText body.appendChild(div) var isSupport = /sticky/i.test(window.getComputedStyle(div).position) body.removeChild(div) div = null return isSupport } export default function (nav, options = {}) { let scrollBox = options.scrollBox || window let offset = options.offset || 0 const checkStickySupport = options.checkStickySupport === true || false if (typeof scrollBox === 'string') { scrollBox = document.getElementById(scrollBox) if (!scrollBox) { if (process.env.NODE_ENV === 'development') { console.error('[VUX] sticky:scroll-box element doesn\'t exist') } return } } let navOffsetY = nav.offsetTop - offset scrollBox.removeEventListener('scroll', scrollBox.e) const getTop = function () { if (scrollBox === window) { return (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop } else { return scrollBox.scrollTop } } const getFillElem = function (el) { let next = el.nextSibling // 寻找最近的一个兄弟元素 while (next.nodeType !== 1) { next = next.nextSibling } if (next.classList.contains('vux-sticky-fill')) { return next } // 没有使用vux-sticky-fill按照之前的方式获取外层容器 return el.parentNode } const scrollHandler = function () { const distance = getTop() if (distance > navOffsetY) { nav.style.top = offset + 'px' nav.classList.add('vux-fixed') } else { nav.classList.remove('vux-fixed') } } if (checkStickySupport && (gtIOS6() || isSupportSticky())) { nav.style.top = offset + 'px' // 大于等于iOS6版本使用sticky nav.classList.add('vux-sticky') } else { if (nav.classList.contains('vux-fixed')) { const top = getTop() navOffsetY = getFillElem(nav).offsetTop - offset if (top < navOffsetY) { nav.classList.remove('vux-fixed') } } else { navOffsetY = nav.offsetTop - offset } scrollBox.e = scrollHandler scrollBox.addEventListener('scroll', scrollHandler) } } ================================================ FILE: src/components/swipeout/index.js ================================================ /** * Thanks to: http://jdc.jd.com/demo/slide_to_remove/ */ import Swipeout from './swipeout.vue' import SwipeoutItem from './swipeout-item.vue' import SwipeoutButton from './swipeout-button.vue' export { Swipeout, SwipeoutItem, SwipeoutButton } ================================================ FILE: src/components/swipeout/metas.yml ================================================ category: en: Form zh-CN: 表单 icon: '' name: swipeout import_code: ' ' extra: | ``` js import { Swipeout, SwipeoutItem, SwipeoutButton } from 'vux' ``` items: - swipeout - swipeout-item - swipeout-button swipeout: sub_extra: | 包装子组件的wrap组件 slots: default: zh-CN: 子组件插槽 swipeout-button: props: text: zh-CN: '按钮文字,同`slot=default`' background-color: zh-CN: 背景颜色 type: zh-CN: '内置的颜色类型,可选`primary`, `warn`' width: default: 80 zh-CN: 按钮宽度 disabled: type: Boolean default: false en: if disabled zh-CN: 是否禁用 swipeout-item: props: sensitivity: type: Number default: 0 zh-CN: 滑动多少距离后开始触发菜单显示 auto-close-on-button-click: type: Boolean default: true zh-CN: 点击按钮后是否收回菜单 disabled: type: Boolean default: false zh-CN: 是否不可滑动 threshold: type: Number default: 0.3 zh-CN: 滑动多少距离后自动打开菜单,否则收回。可以为小于1的比例或者宽度值 transition-mode: type: String default: reveal zh-CN: 菜单打开方式,`reveal`表示菜单不动内容滑出,`follow`表示菜单随内容滑出 methods: open: params: '`(direction)`' zh-CN: 打开菜单,参数为方向 close: zh-CN: 关闭菜单 slots: left-menu: zh-CN: 左菜单 right-menu: zh-CN: 右菜单 events: on-open: zh-CN: 菜单完全打开时触发 on-close: zh-CN: 菜单完全关闭时触发 changes: next: en: - '[feature] Add prop:disabled' zh-CN: - '[feature] 添加属性 disabled' v2.5.5: en: - '[enhance] swipeout-item add render method #1688' zh-CN: - '[enhance] swipeout-item 新增 render 方法 #1688' v2.1.1-rc.9: en: - '[fix] Fix left-menu is shown when swiping right quickly' zh-CN: - '[fix] 修复向右快速滑动时左侧菜单可能会被滑出' v2.1.1-rc.7: en: - '[fix] Fix button click on Android. #1044 @tangtaoit' zh-CN: - '[fix] 修复 Android 下按钮点击事件不触发问题 #1044 @tangtaoit' v2.1.0-rc.50: zh-CN: - '[feature] `swipeout`组件`beta`版本' v2.1.1-rc.14: en: - '[fix] Fix left-menu is shown when sliding back and forth #1139' zh-CN: - '[fix] 修复来回滑动时左侧菜单可能会被滑出 #1139' v2.2.0: en: - '[fix] Fix cannot scroll when disabled' - '[fix] Fix unable to trigger click event when disabled on android' zh-CN: - '[fix] 修复disabled时不能上下滚动问题' - '[fix] 修复disabled时,安卓手机上不能触发点击事件的问题' ================================================ FILE: src/components/swipeout/swipeout-button.vue ================================================ ================================================ FILE: src/components/swipeout/swipeout-item.vue ================================================ ================================================ FILE: src/components/swipeout/swipeout.vue ================================================ ================================================ FILE: src/components/swiper/index.js ================================================ import Swiper from './swiper.vue' import SwiperItem from './swiper-item' export { Swiper, SwiperItem } ================================================ FILE: src/components/swiper/metas.yml ================================================ category: en: Data Display zh-CN: 数据展示 icon: '' extra: | ::: tip `list`为图片列表快捷设置,如果你需要自定义一些样式,或者内容并不为纯图片,可以引用`swiper-item`组件来自定义。
    `list` 格式如下
    ``` js [{ url: 'javascript:', img: 'https://static.vux.li/demo/1.jpg', title: '送你一朵fua' }, { url: 'javascript:', img: 'https://static.vux.li/demo/5.jpg', title: '送你一次旅行', fallbackImg: 'https://static.vux.li/demo/3.jpg' }] ``` `fallbackImg` 在 `v2.5.13` 支持,它将在 `img` 加载失败时显示,注意的是 `fallbackImg` 可能会在 `img` 加载成功时也进行了加载,只是不会显示(取决于浏览器实现)。 ::: ::: warning 不要在`swiper`里嵌套`scroller`,在`web`上过于复杂化而且手势会有冲突,相关`Issue`将不会处理。

    该组件场景是固定高度的内容列表,不支持为不同 swiper-item 设置不同高度。

    如果确实需要设置不同高度,可以通过 ref 获取 swiper, 通过 this.$refs.swiper.xheight = '100px' 设置。 切记,需要放在 $nextTick 中执行。 ::: tips: zh-CN: - q: swiper-item 只有两项时无法循环播放 a: | 是的,建议直接使用 `list` 来实现循环。因为实现原理是对`swiper-item`元素进行移动,需要至少有`3`个`swiper-item`才能保证有上一张和下一张可以切换。 props: list: type: Array en: image list, use swiper-item if you need to customize style zh-CN: 轮播图片列表,如果有自定义样式需求,请使用 swiper-item(使用 swiper-item 时仅有2个的情况下不支持循环) direction: type: String default: horizontal en: swiping direction zh-CN: 方向 show-dots: type: Boolean default: true en: if show indicators zh-CN: 是否显示提示点 show-desc-mask: type: Boolean default: true en: if show description mask zh-CN: 是否显示描述半透明遮罩 dots-position: type: String default: right en: indicator position zh-CN: 提示点位置 dots-class: en: custom indicator's classname zh-CN: 提示className auto: type: Boolean default: false en: if auto playing zh-CN: 是否自动轮播 loop: type: Boolean default: false en: if use loop mode zh-CN: 是否循环 interval: type: Number default: 3000 en: interval value zh-CN: 轮播停留时长 threshold: type: Number default: 50 en: threshold value zh-CN: 当滑动超过这个距离时才滑动 duration: type: Number default: 300 en: transition duration zh-CN: 切换动画时间 height: type: String default: 180px en: height value zh-CN: '高度值。如果为`100%`宽度并且知道宽高比,可以设置`aspect-ratio`自动计算高度' aspect-ratio: type: Number en: if specified, height will be caculated automatically zh-CN: 用以根据当前可用宽度计算高度值 min-moving-distance: type: Number default: 0 en: min distance before moving zh-CN: 超过这个距离时才滑动 v-model: type: Number default: 0 en: index value, use `v-model` for binding zh-CN: index 绑定,使用`v-model`,一般不需要绑定 events: on-index-change: params: '`(currentIndex)`' en: fires when index is changed zh-CN: 轮播 index 变化时触发 on-get-height: version: v2.7.0 params: '`(height)`' en: fires after height is caculated zh-CN: 高度获取后触发 changes: v2.9.2: en: - '[fix] fix stuck swipe after setting min-moving-distance #2773' - '[fix] fix error for repeat setting data in loop #2803' zh-CN: - '[fix] 修复设置最小移动距离后滑动卡住 #2773' - '[fix] 修复在循环模式下重复设置数据的错误 #2803' v2.7.7: en: - '[fix] fix cannot scroll body when touching with one item' zh-CN: - '[fix] 修复只有一个 item 时无法滚动页面的问题' v2.7.6: en: - '[enhance] stop moving width one item' zh-CN: - '[enhance] 只有一张图片时阻止滑动跟随' v2.7.0: en: - '[enhance] set shorter distance for first and last item for better experience' - '[feature] add event:on-get-height #2112' - '[enhance] stop interval if auto is set to false #2027' zh-CN: - '[enhance] 非循环模式下对第一张和最后一张使用更短的拖动距离' - '[feature] 添加事件 event:on-get-height #2112' - '[enhance] auto 支持动态设置 #2027' v2.6.0: en: - '[enhance] support fallbackImg for prop:list #1923' zh-CN: - '[enhance] 支持 list.fallbackImg 属性在图片加载失败时显示预设图片 #1923' v2.5.3: en: - '[fix] clear listTtwoLoopItem when list size !== 2 #1684 @weizs' zh-CN: - '[fix] 轮播个数不为2时清除 listTtwoLoopItem 列表 #1684 @weizs' v2.5.1: en: - '[fix] Fix array-from issue with webpack@3 #1649' zh-CN: - '[fix] 修复 array-from 在 webpack@3 下报错问题 #1649' v2.3.5: en: - '[fix] Fix repeat render bug #1458' zh-CN: - '[fix] 修复重复渲染问题 #1458' v2.3.4: en: - '[fix] Fix click to fail when only two swiper-item and loop #1484 @unclay' zh-CN: - '[fix] 修复只有两个轮播图且为 loop 时点击失效问题 #1484 @unclay' v2.2.1-rc.2: en: - '[enhance] Use document width as swiper width if initial width is 0 #1188' - '[fix] Fix initial index > 0 not works' zh-CN: - '[enhance] 如果初始化时获取不到宽度则直接使用视口宽度 #1188' - '[fix] 修复初始化时 index > 0 不生效' v2.1.1-rc.3: en: - '[fix] Fix nodes list forEach function is undefined #991 @howyhuang' zh-CN: - '[fix] 修复 node 列表没有 forEach 方法 #991 @howyhuang' v2.1.1-rc.2: en: - '[fix] Fix es6 iterator compability, use forEach' zh-CN: - '[fix] 修复部分机子不支持 for of 遍历' ================================================ FILE: src/components/swiper/swiper-item.vue ================================================ ================================================ FILE: src/components/swiper/swiper.js ================================================ import objectAssign from 'object-assign' const arrayFrom = (nodeList) => Array.prototype.slice.call(nodeList) class Swiper { constructor (options) { this._default = { container: '.vux-swiper', item: '.vux-swiper-item', direction: 'vertical', activeClass: 'active', threshold: 50, duration: 300, auto: false, loop: false, interval: 3000, height: 'auto', minMovingDistance: 0 } this._options = objectAssign(this._default, options) this._options.height = this._options.height.replace('px', '') this._start = {} this._move = {} this._end = {} this._eventHandlers = {} this._prev = this._current = this._goto = 0 this._width = this._height = this._distance = 0 this._offset = [] this.$box = this._options.container this.$container = this._options.container.querySelector('.vux-swiper') this.$items = this.$container.querySelectorAll(this._options.item) this.count = this.$items.length this.realCount = this.$items.length // real items length this._position = [] // used by go event this._firstItemIndex = 0 this._isMoved = false // used by minMovingDistance #2773 if (!this.count) { return } this._init() this._auto() this._bind() this._onResize() return this } _auto () { const me = this me.stop() if (me._options.auto) { me.timer = setTimeout(() => { me.next() }, me._options.interval) } } updateItemWidth () { this._width = this.$box.offsetWidth || document.documentElement.offsetWidth this._distance = this._options.direction === 'horizontal' ? this._width : this._height } stop () { this.timer && clearTimeout(this.timer) } _loop () { return this._options.loop && this.realCount >= 3 } _onResize () { const me = this this.resizeHandler = () => { setTimeout(() => { me.updateItemWidth() me._setOffset() me._setTransform() }, 100) } window.addEventListener('orientationchange', this.resizeHandler, false) } _init () { this._height = this._options.height === 'auto' ? 'auto' : this._options.height - 0 this.updateItemWidth() this._initPosition() this._activate(this._current) this._setOffset() this._setTransform() if (this._loop()) { this._loopRender() } } _initPosition () { for (let i = 0; i < this.realCount; i++) { this._position.push(i) } } _movePosition (position) { const me = this if (position > 0) { let firstIndex = me._position.splice(0, 1) me._position.push(firstIndex[0]) } else if (position < 0) { let lastIndex = me._position.pop() me._position.unshift(lastIndex) } } _setOffset () { let me = this let index = me._position.indexOf(me._current) me._offset = [] arrayFrom(me.$items).forEach(function ($item, key) { me._offset.push((key - index) * me._distance) }) } _setTransition (duration) { duration = duration || (this._options.duration || 'none') let transition = duration === 'none' ? 'none' : duration + 'ms' arrayFrom(this.$items).forEach(function ($item, key) { $item.style.webkitTransition = transition $item.style.transition = transition }) } _setTransform (offset) { const me = this offset = offset || 0 arrayFrom(me.$items).forEach(function ($item, key) { let distance = me._offset[key] + offset let transform = `translate3d(${distance}px, 0, 0)` if (me._options.direction === 'vertical') { transform = `translate3d(0, ${distance}px, 0)` } $item.style.webkitTransform = transform $item.style.transform = transform me._isMoved = true }) } _bind () { const me = this me.touchstartHandler = (e) => { me.stop() me._start.x = e.changedTouches[0].pageX me._start.y = e.changedTouches[0].pageY me._setTransition('none') me._isMoved = false } me.touchmoveHandler = (e) => { if (me.count === 1) { return } me._move.x = e.changedTouches[0].pageX me._move.y = e.changedTouches[0].pageY let distanceX = me._move.x - me._start.x let distanceY = me._move.y - me._start.y let distance = distanceY let noScrollerY = Math.abs(distanceX) > Math.abs(distanceY) if (me._options.direction === 'horizontal' && noScrollerY) { distance = distanceX } /* set shorter distance for first and last item for better experience */ if (!this._options.loop && (this._current === this.count - 1 || this._current === 0)) { distance = distance / 3 } if ((((me._options.minMovingDistance && Math.abs(distance) >= me._options.minMovingDistance) || !me._options.minMovingDistance) && noScrollerY) || me._isMoved) { me._setTransform(distance) } noScrollerY && e.preventDefault() } me.touchendHandler = (e) => { if (me.count === 1) { return } me._end.x = e.changedTouches[0].pageX me._end.y = e.changedTouches[0].pageY let distance = me._end.y - me._start.y if (me._options.direction === 'horizontal') { distance = me._end.x - me._start.x } distance = me.getDistance(distance) if (distance !== 0 && me._options.minMovingDistance && Math.abs(distance) < me._options.minMovingDistance && !me._isMoved) { return } if (distance > me._options.threshold) { me.move(-1) } else if (distance < -me._options.threshold) { me.move(1) } else { me.move(0) } me._loopRender() } me.transitionEndHandler = (e) => { me._activate(me._current) let cb = me._eventHandlers.swiped cb && cb.apply(me, [me._prev % me.count, me._current % me.count]) me._auto() me._loopRender() e.preventDefault() } me.$container.addEventListener('touchstart', me.touchstartHandler, false) me.$container.addEventListener('touchmove', me.touchmoveHandler, false) me.$container.addEventListener('touchend', me.touchendHandler, false) me.$items[1] && me.$items[1].addEventListener('webkitTransitionEnd', me.transitionEndHandler, false) } _loopRender () { const me = this if (me._loop()) { // issue #507 (delete cloneNode) if (me._offset[me._offset.length - 1] === 0) { me.$container.appendChild(me.$items[0]) me._loopEvent(1) } else if (me._offset[0] === 0) { me.$container.insertBefore(me.$items[me.$items.length - 1], me.$container.firstChild) me._loopEvent(-1) } } } _loopEvent (num) { const me = this me._itemDestoy() me.$items = me.$container.querySelectorAll(me._options.item) me.$items[1] && me.$items[1].addEventListener('webkitTransitionEnd', me.transitionEndHandler, false) me._movePosition(num) me._setOffset() me._setTransform() } getDistance (distance) { if (this._loop()) { return distance } else { if (distance > 0 && this._current === 0) { return 0 } else if (distance < 0 && this._current === this.realCount - 1) { return 0 } else { return distance } } } _moveIndex (num) { if (num !== 0) { this._prev = this._current this._current += this.realCount this._current += num this._current %= this.realCount } } _activate (index) { let clazz = this._options.activeClass Array.prototype.forEach.call(this.$items, ($item, key) => { $item.classList.remove(clazz) if (index === Number($item.dataset.index)) { $item.classList.add(clazz) } }) } go (index) { const me = this me.stop() index = index || 0 index += this.realCount index = index % this.realCount index = this._position.indexOf(index) - this._position.indexOf(this._current) me._moveIndex(index) me._setOffset() me._setTransition() me._setTransform() me._auto() return this } next () { this.move(1) return this } move (num) { this.go(this._current + num) return this } on (event, callback) { if (this._eventHandlers[event]) { console.error(`[swiper] event ${event} is already register`) } if (typeof callback !== 'function') { console.error('[swiper] parameter callback must be a function') } this._eventHandlers[event] = callback return this } _itemDestoy () { this.$items.length && arrayFrom(this.$items).forEach(item => { item.removeEventListener('webkitTransitionEnd', this.transitionEndHandler, false) }) } destroy () { this.stop() this._current = 0 this._setTransform(0) window.removeEventListener('orientationchange', this.resizeHandler, false) this.$container.removeEventListener('touchstart', this.touchstartHandler, false) this.$container.removeEventListener('touchmove', this.touchmoveHandler, false) this.$container.removeEventListener('touchend', this.touchendHandler, false) this._itemDestoy() // remove clone item (used by loop only 2) if (this._options.loop && this.count === 2) { let $item = this.$container.querySelector(`${this._options.item}-clone`) $item && this.$container.removeChild($item) $item = this.$container.querySelector(`${this._options.item}-clone`) $item && this.$container.removeChild($item) } } } export default Swiper ================================================ FILE: src/components/swiper/swiper.vue ================================================ ================================================ FILE: src/components/tab/index.js ================================================ import Tab from './tab' import TabItem from './tab-item' export { Tab, TabItem } ================================================ FILE: src/components/tab/metas.yml ================================================ category: en: Navigation zh-CN: 导航 icon: '' items: - tab - tab-item tags: en: - tab zh-CN: - 选项 - 选项卡 import_code: "import { Tab, TabItem } from 'vux'" extra: | 如果需要监听`tab-item`的点击事件并取得索引,请使用`on-item-click`事件: ``` html ``` tab: props: line-width: type: Number default: 3 en: line width zh-CN: 线条宽度 active-color: en: selected text color zh-CN: 选中时文字颜色 default-color: en: default text color zh-CN: 默认文字颜色 disabled-color: en: disabled text color zh-CN: 不可点击时文字颜色 bar-active-color: en: active color for bar zh-CN: '设置底部`bar`颜色,该颜色也可以通过`less`变量`@tab-bar-active-color`设置。' animate: type: Boolean default: true en: whether use transition zh-CN: 切换时是否需要动画 custom-bar-width: type: String, Function version: v2.1.1-rc.7 en: 'set active bar width, like `50px`. or you can use function, param is `(currentInex)`' zh-CN: '设置底部`bar`宽度,默认宽度是整体tab宽度平分,比如`50px`。使用函数时参数为当前索引`index`,你可以定义不同`tab-item`对应的`bar`宽度。' badge-label: version: v2.3.5 type: String en: badge label zh-CN: 徽标文字 badge-background: version: v2.3.5 type: String default: #f74c31 en: badge background color zh-CN: 徽标背景颜色 badge-color: version: v2.3.5 type: String default: #fff en: badge font color zh-CN: 徽标文字颜色 prevent-default: version: v2.7.2 default: false en: whether prevent auto-switching tab-item when tab-item is clicked zh-CN: 是否禁止自动切换 tab-item scroll-threshold: version: v2.8.1 type: Number default: 4 en: scroll tabs threshold zh-CN: 滚动阀值,超过可滚动 bar-position: version: v2.9.0 type: String default: bottom en: bar position, one of [bottom, top]. only works when animate is true. zh-CN: 边框位置,可以为 bottom 或者 top。仅支持 animate 为 true 的情况。 event: on-index-change: params: '`(index)`' en: fires when switching tab-item zh-CN: tab-item 切换时触发 on-before-index-change: version: v2.7.2 params: '`(index)`' en: fires when item is clicked and prop:preventDefault is true zh-CN: 点击 tab-item 并且 属性 preventDefault 为 true 时触发 tab-item: props: disabled: type: Boolean default: false en: wheher disabled clicking zh-CN: 是否不可选 active-class: en: active classname for current tab-item zh-CN: 当前项选中时的class events: on-item-click: params: '`(index)`' version: v2.2.1-rc.4 en: emits when current tabItem is clicked zh-CN: 当前 tabItem 被点击时触发 changes: v2.9.4: en: - '[fix] tab #2850' zh-CN: - '[change] 修复 tab的父级width没有100%或者有padding 滚动造成文字跟横杠错位问题 #2850' v2.9.1: en: - '[fix] fix box-sizing issue #2621. thanks to @suncodeer' zh-CN: - '[fix] 修复全局 box-sizing 为 border-box 时导致的高度问题 #2621。感谢 @suncodeer ' v2.9.0: en: - '[feature] support prop:bar-position' zh-CN: - '[feature] 支持使用 bar-position 定义边框位置' v2.8.1: en: - '[feature] support prop:scroll-threshold' zh-CN: - '[feature] 支持滚动' v2.7.2: en: - '[feature] support prop:prevent-default #2176' zh-CN: - '[feature] 支持阻止自动切换 #2176' v2.3.5: en: - '[feature] Support badge #1513' zh-CN: - '[feature] 支持设置 badge #1513' v2.2.1-rc.4: en: - '[enhance] Support param:index on event:on-item-click' zh-CN: - '[enhance] 支持 on-item-click 带上 index 参数' v2.1.1-rc.7: en: - '[feature] Support setting bar width with prop:custom-bar-width' - '[fix] Fix errors when initializing without tab-item #1038 @liu2010y' zh-CN: - '[feature] 支持定义 bar 宽度' - '[fix] 修复初始化时没有 tab-item 时导致报错 #1038 @liu2010y' v2.1.0-rc.46: en: - '[feature] add prop:bar-active-color #715 @greedying' zh-CN: - '[feature] 支持设置 `bar-active-color` #715 @greedying' ================================================ FILE: src/components/tab/tab-item.vue ================================================ ================================================ FILE: src/components/tab/tab.vue ================================================ ================================================ FILE: src/components/tabbar/index.js ================================================ import Tabbar from './tabbar' import TabbarItem from './tabbar-item' export { Tabbar, TabbarItem } ================================================ FILE: src/components/tabbar/metas.yml ================================================ category: en: Navigation zh-CN: 导航 icon: '' items: - tabbar - tabbar-item import_code: "import { Tabbar, TabbarItem } from 'vux'" extra: | 支持简单模式,即不指定`icon`,`label`将垂直居中显示。 ::: warning 默认定位为 `absolute`,适用于 `100%页面布局`,如果你并非 100% 布局(可以配合使用 view-box 组件),请手动重置样式为 `position: fixed` ::: tags: en: - tabbar zh-CN: - 导航 tabbar: props: icon-class: en: icon's classname zh-CN: 图标的class名 slots: default: en: slot for tabbar items zh-CN: tabbar主体内容,只允许tabbar-item events: on-index-change: version: v2.5.4 en: emits when value is changed zh-CN: value 值变化时触发 tabbar-item: props: selected: type: Boolean default: false en: 'if selected, you can also use `v-model="index"` on tabbar to set item selected' zh-CN: '是否选中当前项,你也可以使用`v-model`来指定选中的`tabbar-item`的`index`' badge: en: badge text, if not specified, the badge will not show zh-CN: 徽标文字,不指定则不显示 show-dot: type: Boolean default: false en: if show the red dot zh-CN: 是否显示红点 link: en: the url for current tabbar item, can be a url or a `vue-router` link value zh-CN: 链接,可以为普通url或者用`vue-link`的路径写法,使用 object 写法指定 replace 为 true 可实现 replace 跳转 icon-class: en: the classname for current icon, if tabbar and tabbar-item both specify icon-class, tabbar-item's will be usePulldown zh-CN: 图标类名,如果tabbar也同时定义了icon-class, 会使用tabbar-item的 slots: icon: en: icon area zh-CN: 图标区域 icon-active: version: v2.1.1-rc.8 en: will show when current item is activated zh-CN: 如果存在,当前 tabbar-item 点击时会显示,用于切换图标 label: en: label text zh-CN: 图标下方文字 events: on-item-click: en: emits when tabbar item is clicked zh-CN: 点击菜单项时触发 changes: v2.7.7: en: - '[fix] fix tabbar-item ssr rendering issue' zh-CN: - '[fix] 修复 tabbar-item 服务端渲染不一致问题' v2.7.4: en: - '[enhance] add development tip for position usage' zh-CN: - '[enhance] 开发模式下对于非 100% 布局使用显示 absolute 定位说明' v2.5.5: en: - '[feature] Support replace: true for link #1761' zh-CN: - '[feature] 支持 link 上使用 replace: true 写法 #1761' v2.5.4: en: - '[feature] Add event:on-index-change #1715' zh-CN: - '[feature] 增加事件 on-index-change #1715' v2.1.1-rc.8: en: - '[feature] Support slot:active-icon' zh-CN: - '[feature] 支持点击时切换图标' v2.1.1-rc.4: en: - '[fix] fix this.$slots undefined #1000 @asingingfish' zh-CN: - '[fix] 修复 this.$slots 不存在 #1000 @asingingfish' v2.1.1-rc.1: en: - '[fix] fix variable @tabbar-text-active-color #982 @marsal1212' zh-CN: - '[fix] 修复 less 变量 @tabbar-text-active-color #982 @marsal1212' ================================================ FILE: src/components/tabbar/tabbar-item.vue ================================================ ================================================ FILE: src/components/tabbar/tabbar.vue ================================================ ================================================ FILE: src/components/timeline/index.js ================================================ import Timeline from './timeline' import TimelineItem from './timeline-item' export { Timeline, TimelineItem } ================================================ FILE: src/components/timeline/metas.yml ================================================ category: en: DataDisplay zh-CN: 数据展示 icon: '' import_code: "import { Timeline, TimelineItem } from 'vux'" extra: | 待补充 changes: v2.1.1-rc.10: en: - '[fix] Fix icon position' zh-CN: - '[fix] 修复图标位置' ================================================ FILE: src/components/timeline/timeline-item.vue ================================================ ================================================ FILE: src/components/timeline/timeline.vue ================================================ ================================================ FILE: src/components/tip/index.vue ================================================ ================================================ FILE: src/components/toast/index.vue ================================================ ================================================ FILE: src/components/toast/metas.yml ================================================ category: en: Feedback zh-CN: 弹窗提示 icon: '' tags: en: - toast - tip - dialog zh-CN: - 弹窗 - 提示 extra: | 该组件支持以`plugin`形式调用: ::: warning 以插件形式调用时,和`template`中使用不同,属性名请使用`小驼峰式`,比如`isShowMask`而不是`is-show-mask`。 ::: ``` js import { ToastPlugin } from 'vux' Vue.use(ToastPlugin) // 或者umd方式 // 引入构建的js文件 Vue.use(vuxToastPlugin) // 默认参数 Vue.use(ToastPlugin, {position: 'top'}) ``` ``` js // 显示 this.$vux.toast.show({ text: 'Loading' }) // 显示文字 this.$vux.toast.text('hello', 'top') // 隐藏 this.$vux.toast.hide() // 获取显示状态 this.$vux.toast.isVisible() // v2.9.1 开始支持 ``` props: value: type: Boolean default: false en: if showing toast, use v-model for binding zh-CN: 是否显示, 使用 v-model 绑定 time: type: Number default: 2000 en: show time zh-CN: 显示时间 type: type: String default: success en: toast type, success, warn, cancel, text zh-CN: 类型,可选值 success, warn, cancel, text width: type: String default: 7.6em en: toast's width zh-CN: 宽度 is-show-mask: type: Boolean default: false en: if showing mask, users cannot operate other elements on the page zh-CN: 是否显示遮罩,如果显示,用户将不能点击页面上其他元素 text: type: String default: '' en: content of the toast, text or html, the same function as default slot zh-CN: 提示内容,支持 html,和默认slot同样功能 position: version: v2.1.1-rc.4 type: String default: 'default' en: 'toast position, available values: default, top, middle, bottom' zh-CN: 显示位置,可选值 default, top, middle, bottom slots: default: en: default slot, the content of the toast zh-CN: 默认slot, 提示内容 events: on-show: en: emits when toast shows zh-CN: 提示弹出时触发 on-hide: en: emits when toast hides zh-CN: 提示隐藏时触发 changes: v2.9.1: en: - '[feature] add isVisible function for ToastPlugin #2704' zh-CN: - '[feature] 添加 isVisible 获取当前显示状态 #2704' v2.7.7: en: - '[fix] plugin options is used as toast default options' zh-CN: - '[fix] 插件 options 参数作为toast的默认参数' v2.6.3: en: - '[fix] fix no padding when using slot and type === text' zh-CN: - '[fix] 修复 type 为 text 时使用 slot 缺失 padding 样式的问题' v2.5.8: en: - "[fix] Toast's zIndex should be higher than XDialog's #1820" zh-CN: - '[fix] zIndex 值应该比 XDialog 大 #1820' v2.3.1: en: - '[feature] Add text method for plugin' zh-CN: - '[feature] 为插件添加 text 方法方便调用' v2.1.1-rc.13: en: - '[fix] fix After the call is not set webkitOverflowScrolling for touch' zh-CN: - '[fix] 修复 调用后没有设置webkitOverflowScrolling为touch' v2.1.1-rc.10: en: - '[fix] Fix warn icon style' zh-CN: - '[fix] 修复警告图标样式' v2.1.1-rc.9: en: - '[enhance] Reset props every time plugin.show is called #870 @jsonviewer' zh-CN: - '[enhance] 插件调用时每次都重置为默认参数 #870 @jsonviewer' v2.1.1-rc.6: en: - '[fix] fix forbidden style being overwritten #1016 @LaiXuechao' zh-CN: - '[fix] 修复禁止样式被覆盖 #1016 @LaiXuechao' v2.1.1-rc.4: en: - '[feature] Support position setting. #973 @ LaiXuechao' zh-CN: - '[feature] 支持显示位置设置 #973 @LaiXuechao' v2.1.1-rc.2: en: - '[feature] Add more less variables' zh-CN: - '[feature] 支持更多 less 变量' v2.0.0: en: - '[change] use `v-model` instead of `:show.sync`' - '[change] do not show mask by default' - '[feature] add prop:isShowMask' zh-CN: - '[change] 使用 `v-model` 而不是`:show.sync`进行显示属性绑定' - '[change] 默认不显示遮罩' - '[feature] 添加 isShowMask 属性' ================================================ FILE: src/components/toast/test.js ================================================ import Comp from './index.vue' import { mount } from 'vue-test-utils' import { expect } from 'chai' describe('Toast', () => { it('basic', () => { const wrapper = mount(Comp) expect(wrapper.name()).to.equal('toast') }) }) ================================================ FILE: src/components/uploader/index.vue ================================================ ================================================ FILE: src/components/uploader/metas.yml ================================================ category: en: Form 'zh-CN': 表单 icon: '' tags: en: - uploader zh-CN: - 上传组件 extra: en: zh-CN: | ```html ``` props: title: type: String default: '图片上传' en: component title zh-CN: 组件标题 files: type: Array default: [] en: initial file data source, through event `on-fileList-change` bind v-model zh-CN: 初始化数据源,通过`on-fileList-change`事件绑定 v-model limit: type: Number, String default: 5 en: limit upload image count zh-CN: 限制上传图片个数 limitPrompt: type: Function default: (limit) => `不能上传超过${limit}张图片` en: prompt for limit alert zh-CN: 限制上传alert的提示语 capture: type: Number, String default: false en: whether only invoke camera zh-CN: 是否只选择调用相机 enableCompress: type: Boolean default: true en: whether enable compress zh-CN: 是否压缩 maxWidth: type: String, Number default: 1024 en: image compress max width zh-CN: 图片压缩最大宽度 quality: type: String, Number default: 0.92 en: image compress rate zh-CN: 图片压缩率 url: type: String en: upload server url zh-CN: 上传服务器 url headers: type: Object default: {} en: custom upload request headers zh-CN: 上传文件时自定义请求头 withCredentials: type: Boolean en: setting as true, can support CORS set cookie zh-CN: 设置为`true`的话,支持标准CORS设置cookie值 params: type: Object en: custom formData params zh-CN: 上传文件时自定义参数 name: type: String default: 'file' en: upload formData key, default as `file` zh-CN: 上传文件时 FormData 的 Key,默认为 file autoUpload: type: Boolean default: true en: whether auto upload zh-CN: 是否自动开启上传 multiple: type: String, Boolean en: whether support multiple select image, `false` will not support zh-CN: 是否支持多选, `false`为不支持 readonly: type: Boolean default: false en: field readonly (hide add and delete button) zh-CN: 只读模式(隐藏添加和删除按钮) events: on-change: params: '`(FileItem, FileList)`' en: emits when file change zh-CN: 选完照片,删除照片时,FileList 变化时触发,返回当前改变的 FileItem,以及当前的 FileList on-cancel: en: emits when cancel select image zh-CN: 选择照片后取消的回调,用于错误提示 on-success: params: '`(result, fileItem)`' en: emits when file upload success zh-CN: 上传请求成功后的回调,返回远程请求的返回结果,及当前添加文件的 FileItem on-error: params: '`(xhr)`' en: emits when file upload failed zh-CN: 上传请求失败后的回调,返回`xhr` on-delete: params: '`(cb)`' en: emits when delete file, the first params is a callback which can hide previewer zh-CN: 上传删除照片时的回调,返回隐藏 Previewer,删除图片的回调,没监听`onDelete`事件的时候,直接执行删除回调 before-upload: en: emits before invoke xhr request zh-CN: 上传图片之前的回调 after-upload: params: '`(action)`' en: emits after xhr request finished zh-CN: 上传图片之后的回调 ================================================ FILE: src/components/uploader/utils.js ================================================ import EXIF from 'exif-js' /** * Detecting vertical squash in loaded image. * Fixes a bug which squash image vertically while drawing into canvas for some images. */ function detectVerticalSquash (img) { let data let ih = img.naturalHeight let canvas = document.createElement('canvas') canvas.width = 1 canvas.height = ih let ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0) try { // Prevent cross origin error data = ctx.getImageData(0, 0, 1, ih).data } catch (err) { // hopeless, assume the image is well and good. console.log('Cannot check verticalSquash: CORS?') return 1 } // search image edge pixel position in case it is squashed vertically. var sy = 0 var ey = ih var py = ih while (py > sy) { var alpha = data[(py - 1) * 4 + 3] if (alpha === 0) { ey = py } else { sy = py } py = (ey + sy) >> 1 } var ratio = py / ih return ratio === 0 ? 1 : ratio } /** * Detect subsampling in loaded image. * In iOS, larger images than 2M pixels may be subsampled in rendering. */ function detectSubsampling (img) { var iw = img.naturalWidth var ih = img.naturalHeight if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image var canvas = document.createElement('canvas') canvas.width = canvas.height = 1 var ctx = canvas.getContext('2d') ctx.drawImage(img, -iw + 1, 0) // subsampled image becomes half smaller in rendering size. // check alpha channel value to confirm image is covering edge pixel or not. // if alpha value is 0 image is not covering, hence subsampled. return ctx.getImageData(0, 0, 1, 1).data[3] === 0 } else { return false } } /** * Transform canvas coordination according to specified frame size and orientation * Orientation value is from EXIF tag */ function transformCoordinate (canvas, ctx, width, height, orientation) { switch (orientation) { case 5: case 6: case 7: case 8: canvas.width = height canvas.height = width break default: canvas.width = width canvas.height = height } switch (orientation) { case 2: // horizontal flip ctx.translate(width, 0) ctx.scale(-1, 1) break case 3: // 180 rotate left ctx.translate(width, height) ctx.rotate(Math.PI) break case 4: // vertical flip ctx.translate(0, height) ctx.scale(1, -1) break case 5: // vertical flip + 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.scale(1, -1) break case 6: // 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.translate(0, -height) break case 7: // horizontal flip + 90 rotate right ctx.rotate(0.5 * Math.PI) ctx.translate(width, -height) ctx.scale(-1, 1) break case 8: // 90 rotate left ctx.rotate(-0.5 * Math.PI) ctx.translate(-width, 0) break default: break } } // https://stackoverflow.com/a/12300351/6472444 function dataURItoBlob (dataURI) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this var byteString = atob(dataURI.split(',')[1]) // separate out the mime component var mimeString = dataURI .split(',')[0] .split(':')[1] .split(';')[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length) // create a view into the buffer var ia = new Uint8Array(ab) // set the bytes of the buffer to the correct values for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i) } // write the ArrayBuffer to a blob, and you're done var blob = new Blob([ab], { type: mimeString }) return blob } /** * convert blob to canvas to blob */ function handleFile (file, options, doSquash) { return new Promise((resolve, reject) => { const { maxWidth, quality, enableCompress } = options const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const image = new Image() try { image.src = URL.createObjectURL(file) } catch (e) { throw Error(e) } image.onload = () => { let w = image.naturalWidth let h = image.naturalHeight EXIF.getData(image, function () { const orientation = EXIF.getTag(this, 'Orientation') const subsampled = detectSubsampling(image) if (subsampled) { w /= 2 h /= 2 } const vertSquashRatio = doSquash ? detectVerticalSquash(image) : 1 const dw = enableCompress ? Math.min(Number(maxWidth), w) : w const dh = (h * (dw / w)) / vertSquashRatio detectImageAutoRotate().then(isImageAutoRotate => { console.log('detectImageAutoRotate:', isImageAutoRotate) if (!isImageAutoRotate) { transformCoordinate(canvas, ctx, dw, dh, orientation) } else { canvas.width = dw canvas.height = dh } ctx.clearRect(0, 0, dw, dh) ctx.drawImage(image, 0, 0, dw, dh) URL.revokeObjectURL(image.src) canvas.toBlob( blob => { resolve(new File([blob], file.name, { type: file.type })) }, file.type, quality ) }) }) } image.onerror = err => reject(err) }) } /** * https://github.com/Mawi137/ngx-image-cropper/issues/306#issuecomment-611771078 * 判断图片是否被浏览器自动旋转 */ function detectImageAutoRotate () { // 一张 2x1 的 JPEG 图片, EXIF Orientation: 6 const testAutoOrientationImageURL = 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==' let isImageAutoRotate return new Promise(resolve => { if (isImageAutoRotate === undefined) { const img = new Image() img.onload = () => { isImageAutoRotate = img.width === 1 && img.height === 2 resolve(isImageAutoRotate) } img.src = testAutoOrientationImageURL } else { resolve(isImageAutoRotate) } }) } export { detectVerticalSquash, detectSubsampling, transformCoordinate, dataURItoBlob, handleFile, detectImageAutoRotate } ================================================ FILE: src/components/v-chart/metas.yml ================================================ icon: '' category: en: Chart zh-CN: 图表 beta: true description: | VChart 基于[F2@蚂蚁金服(MIT license)](https://antv.alipay.com/zh-cn/f2/3.x/)封装的图表组件。 F2 是专为移动端定制的一套开箱即用的可视化解决方案,具有精简、高性能、易扩展的特性,适用于对性能、大小、扩展性要求严苛的场景。 组件部分属性可能未列出,可以直接参考 [F2文档](https://antv.alipay.com/zh-cn/f2/3.x/api/index.html) changes: 2.9.3: en: - '[enhance] v-point can set size and shape, v-line can set shape' zh-CN: - '[enhance] v-point 可以设置size和shape, v-line 可以设置shape' tips: zh-CN: - q: 全局设置分辨率(devicePixelRatio) a: | ``` js // 入口文件处设置 Vue.prototype.$devicePixelRatio = 2 ``` - q: 自定义渲染行为 a: | 在面对复杂的渲染逻辑时,直接使用组件无法满足。此时可以这样处理: ``` html ``` ``` js methods: { renderVChart ({ chart }) { // do what you want } } ``` - q: 修改子组件配置不会自动刷新 a: | 是的,考虑到移动端更多是展示而不是操作,暂时不支持自动刷新。 - q: 为什么我自己封装时会报错 a: | 要先了解 Vue 组件的生命周期,在 canvas 元素未 mounted 前 F2 无法正确渲染。 ``` js mounted () { this.$nextTick(( => { // do what you want with F2 }) } ``` items: - v-chart - v-line - v-area - v-bar - v-pie - v-point - v-scale - v-axis - v-guide - v-tooltip - v-legend - v-guide v-chart: props: prevent-render: version: v2.9.1 type: boolean default: false en: whether prevent rendering zh-CN: 是否自定义渲染逻辑 prevent-default: version: v2.9.1 type: boolean default: false en: whether prevent default behavior zh-CN: 是否阻止默认行为 events: on-render: version: v2.9.1 params: '`({ chart })`' en: fires before render zh-CN: 渲染前触发,一般用于配合 `prevent-render` 自定义渲染逻辑 methods: rerender: version: v2.9.1 en: destroy and re-render canvas zh-CN: 清除并重新渲染 repaint: version: v2.9.1 en: repain canvas zh-CN: 重新渲染 destroy: version: v2.9.1 en: destroy chart zh-CN: 销毁图表,canvas dom 元素不会销毁 v-point: description: | 点,用于点图的构建。Point 为 `Geometry` 的一部分,和其他图表组件一致,支持 shape, color, series-field 等。完整属性文档写参照 [F2 Geometry](https://antv.alipay.com/zh-cn/f2/3.x/api/geometry.html)。 props: v-line: description: | ::: tip 折线图,完整属性文档写参照 [F2 Geometry](https://antv.alipay.com/zh-cn/f2/3.x/api/geometry.html) ::: props: shape: version: v2.9.1 type: String en: shape type, one of `line`, `smooth`, `dash` zh-CN: 线条形状,可选值 `line`(默认), `smooth`(平滑线), `dash`(虚线) series-field: version: v2.9.1 type: String en: alias to color, specify color value or which field to color zh-CN: 用于绘制多组线条时的分组字段名。该属性相当于 F2 里的 `color`,出于理解和设置方便使用 `series-field` colors: version: v2.9.1 type: String,Array en: colors, use default colors if not specified zh-CN: 线条颜色,可以为单个颜色或者颜色列表。不指定时使用默认颜色。 v-guide: description: | ::: tip 辅助元素,完整属性文档写参照 [F2 Guide](https://antv.alipay.com/zh-cn/f2/3.x/api/guide.html) ::: props: type: version: v2.9.1 type: String en: guide type, one of `line`, `text`, `tag`, `rect`, `html`, `arc` zh-CN: 辅助元素类型,可以为 `line`, `text`, `tag`, `rect`, `html`, `arc` options: version: v2.9.1 type: Object en: guide options zh-CN: 辅助元素属性对象。你也可以在 template 里将属性分开写,不需要使用该 prop,同时使用时属性会被合并。 v-area: props: series-field: version: v2.9.1 type: String en: alias to color, specify color value or which field to color zh-CN: 用于绘制多组线条时的分组字段名。该属性相当于 F2 里的 `color`,出于理解和设置方便使用 `series-field` colors: version: v2.9.1 type: String,Array en: colors, use default colors if not specified zh-CN: 线条颜色,可以为单个颜色或者颜色列表。不指定时使用默认颜色。 v-tooltip: description: | ::: tip 该组件非必须,渲染折线图时会默认使用配置 `showCrosshairs: true` 完整属性文档写参照 [F2 Tooltip](https://antv.alipay.com/zh-cn/f2/3.x/api/tooltip.html) ::: props: disabled: version: v2.9.1 type: boolean default: false en: whether disable tooltip zh-CN: '[快捷属性,非 F2 原有属性]是否禁用 tooltip, 相当于调用`chart.tooltip(false)`' show-x-value: version: v2.9.1 type: boolean default: false en: whether set display template to `xValue:yValue`, default `xLabel:yValue` zh-CN: '[快捷属性,非 F2 原有属性]用于单折线的情况,设定是否将 x 轴值显示在 tooltip 里,默认是 `xLabel:yValue` 的形式(value:23),启用将变成 `xValue:yValue` 的形式(2017-01-01:23),建议在 `tickCount` 无法完全显示时启用。该设置会覆盖原有 `onShow` 设置。' show-value-in-legend: version: v2.9.1 type: boolean default: false en: whether hide default tooltip and show value in legend zh-CN: '[快捷属性,非 F2 原有属性]是否禁用默认 tooltip 而是显示在 legend 里。适用于有分组(series-field)情况。' v-legend: description: | 图例。[F2 Legend](https://antv.alipay.com/zh-cn/f2/3.x/api/legend.html)。 props: disabled: version: v2.9.1 type: boolean default: false en: whether disable tooltip zh-CN: '[快捷属性,非 F2 原有属性]是否禁用 legend, 相当于调用`chart.legend(false)`' v-bar: description: | 柱状图,使用垂直的柱子显示类别之间的数值比较。 props: colors: version: v2.9.1 type: String,Array en: colors, use default colors if not specified zh-CN: 线条颜色,可以为单个颜色或者颜色列表。不指定时使用默认颜色。 v-pie: props: v-axis: description: | 坐标轴配置。[详细文档](https://antv.alipay.com/zh-cn/f2/3.x/api/axis.html)。 props: disabled: version: v2.9.1 type: boolean zh-CN: 禁用当前坐标轴。 x: version: v2.9.1 type: boolean zh-CN: '[组件属性]是否为 `x轴` 配置。' y: version: v2.9.1 type: boolean zh-CN: '[组件属性]是否为 `y轴` 配置。' v-scale: description: | 度量 Scale,是数据空间到图形空间的转换桥梁,负责原始数据到 [0, 1] 区间数值的相互转换工作。针对不同的数据类型对应不同类型的度量。详细文档 [F2 Scale](https://antv.alipay.com/zh-cn/f2/3.x/api/scale.html)。 props: field: version: v2.9.1 type: string zh-CN: '[组件属性]指定 `坐标轴` 字段。对于简单数据,VChart 可以判断使用哪个字段,但是如果顺序不确定或者多字段的数据,最好手动指定。' x: version: v2.9.1 type: boolean zh-CN: '[组件属性]是否为 `x轴` 配置。' y: version: v2.9.1 type: boolean zh-CN: '[组件属性]是否为 `y轴` 配置。' type: version: v2.9.1 type: string zh-CN: 指定不同的度量类型,支持的 type 为 `identity`、`linear`、`cat`、`timeCat`。 formatter: version: v2.9.1 type: function zh-CN: 回调函数,用于格式化坐标轴刻度点的文本显示,会影响数据在坐标轴 axis、图例 legend、提示信息 tooltip 上的显示。 range: version: v2.9.1 type: array default: '[0, 1]' zh-CN: 输出数据的范围,默认[0, 1],格式为 [min, max],min 和 max 均为 0 至 1 范围的数据。 alias: version: v2.9.1 type: string zh-CN: '该数据字段的显示别名,一般用于将字段的英文名称转换成中文名(tooltip 中显示)。' tick-count: version: v2.9.1 type: number default: 5 zh-CN: 坐标轴上刻度点的个数,设为 0 时表示全部显示。 ticks: version: v2.9.1 type: array zh-CN: '用于指定坐标轴上刻度点的文本信息,当用户设置了 ticks 就会按照 ticks 的个数和文本来显示。如 `[ 0, 50, 100, 150, 200, 300, 500 ]`' ================================================ FILE: src/components/v-chart/mixin.js ================================================ import { camelAttrs } from './util' const defaultShapeMap = { line: 'line', point: 'circle', area: 'area' } export default { props: { colors: [String, Array], seriesField: String, adjust: [String, Object] }, created () { this.$parent.set(this.chartName, { shape: defaultShapeMap[this.chartName] || '', ...this.$props, ...camelAttrs(this.$attrs) }) }, render () {} } ================================================ FILE: src/components/v-chart/util.js ================================================ const camel = function (key) { return key.replace(/(-[a-z])/g, function ($1) { return $1.toUpperCase().replace('-', '') }) } export const camelAttrs = function (attrs) { for (let i in attrs) { const key = camel(i) attrs[key] = attrs[i] if (key !== i) { delete attrs[i] } } return attrs } ================================================ FILE: src/components/v-chart/v-area.vue ================================================ ================================================ FILE: src/components/v-chart/v-axis.vue ================================================ ================================================ FILE: src/components/v-chart/v-bar.vue ================================================ ================================================ FILE: src/components/v-chart/v-chart.vue ================================================ ================================================ FILE: src/components/v-chart/v-guide.vue ================================================ ================================================ FILE: src/components/v-chart/v-legend.vue ================================================ ================================================ FILE: src/components/v-chart/v-line.vue ================================================ ================================================ FILE: src/components/v-chart/v-pie.vue ================================================ ================================================ FILE: src/components/v-chart/v-point.vue ================================================ ================================================ FILE: src/components/v-chart/v-scale.vue ================================================ ================================================ FILE: src/components/v-chart/v-tooltip.vue ================================================ ================================================ FILE: src/components/video/index.vue ================================================ ================================================ FILE: src/components/video/zy.media.css ================================================ body { margin: 0 } /* zy.media style */ .zy_media { background: #000; position: relative } .zy_media video, .zy_media audio { width: 100%; position: absolute; top: 0; left: 0; display: block } .zy_fullscreen { overflow: hidden } .zy_fullscreen .zy_media { position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 1000 } .zy_fullscreen .zy_wrap, .zy_fullscreen video { width: 100%; height: 100% } .zy_wrap { width: 100% } /* 视频标题 */ .zy_title { height: 34px; padding-left: 10px; color: #fff; font-size: 12px; line-height: 34px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; background: rgba(0, 0, 0, .25); position: absolute; left: 0; right: 0; top: 0; -webkit-transition: top .5s; transition: top .5s } /* 视频主区中播放、加载中、错误提示 */ .zy_media .dec_play, .zy_media .dec_loading, .zy_media .dec_error { margin: -32px 0 0 -31px; position: absolute; top: 50%; left: 50% } .zy_media .dec_play::before { width: 60px; height: 60px; content: ''; border-radius: 60px; border: #e5e5e4 1px solid; display: block } .zy_media .dec_play::after { width: 0; height: 0; content: ''; border-color: transparent transparent transparent #e5e5e4; border-width: 14px 20px; border-style: solid; position: absolute; top: 16px; left: 23px; z-index: 2; display: block } .zy_media .dec_loading { width: 62px; height: 62px; -webkit-animation: ani_loading .6s infinite linear; -webkit-animation-fill-mode: forwards; animation: ani_loading .6s infinite linear; animation-fill-mode: forwards } @-webkit-keyframes ani_loading { 100% { -webkit-transform: rotate(360deg) } } @keyframes ani_loading { 100% { transform: rotate(360deg) } } .zy_media .dec_loading::before { width: 7px; height: 7px; content: ''; border-radius: 7px; background: #fff; opacity: .8; position: absolute; top: 25px } .zy_media .dec_loading::after { width: 48px; height: 48px; content: ''; border-radius: 50px; border: 7px solid #fff; opacity: .2; display: block } .zy_media .dec_error { width: 62px; height: 62px; margin-top: -53px; margin-left: -25px; white-space: nowrap; color: #fff; font-size: 12px; text-align: center; position: absolute; top: 50%; left: 50%; } /* 控制栏 */ .zy_controls { height: 44px; background: rgba(0, 0, 0, .55); position: absolute; left: 0; right: 0; bottom: 0; -webkit-transition: bottom .5s; transition: bottom .5s; display: -webkit-box; display: -webkit-flex; display: flex } /* 播放、暂停按钮 */ .zy_playpause_btn { width: 26px; height: 30px; margin-right: 4px; padding: 13px 0 0 14px; position: relative } .zy_play::before { width: 0; height: 0; content: ''; border-color: transparent transparent transparent #cbcbcb; border-width: 8px 12px; border-style: solid; display: block } .zy_pause::before, .zy_pause::after { width: 3px; height: 14px; content: ''; background: #cbcbcb; position: absolute; top: 13px; left: 14px } .zy_pause::after { left: 22px } /* 时间线操作区 */ .zy_timeline { margin-right: 10px; -webkit-box-flex: 1; -webkit-flex: 1 1 auto; flex: 1 1 auto } .zy_timeline_slider { width: 100%; height: 1px; background: #999; position: relative; top: 21px; left: 0 } .zy_timeline_buffering { width: 100%; height: 15px; top: -7px; background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255, 255, 255, .15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255, 255, 255, .15)), color-stop(.75, rgba(255, 255, 255, .15)), color-stop(.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(-45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -webkit-background-size: 15px 15px; background-size: 15px 15px; -webkit-animation: ani_buffering 2s linear infinite; animation: ani_buffering 2s linear infinite; position: absolute; } @-webkit-keyframes ani_buffering { from { background-position: 0 0 } to { background-position: 30px 0 } } @keyframes ani_buffering { from { background-position: 0 0 } to { background-position: 30px 0 } } .zy_timeline_loaded { width: 0; height: 1px; background: #e5e5e5; position: absolute; top: 0; left: 0; z-index: 1 } .zy_timeline_current { width: 0; height: 1px; background: #ff6159; position: relative; z-index: 2 } .zy_timeline_handle { width: 16px; height: 16px; border-radius: 16px; background: #e5e5e5; position: absolute; top: -8px; left: -8px; z-index: 3 } /* 时间展示 */ .zy_time { width: auto; height: 44px; margin-right: 5px; line-height: 44px; font-size: 11px; color: #999; text-align: center } .zy_time .zy_currenttime { color: #e5e5e5 } /* 全屏控制按钮 */ .zy_fullscreen_btn { width: 38px; height: 44px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaBAMAAAEsY2FrAAAAElBMVEX///////////////////////+65XQCAAAABXRSTlMAHm1u3TG+li4AAAB5SURBVBgZBcGxbQNBEAQwPnCXC49TviU4UQnKx8ZP/62YVB58qQCIBwArGgAAwK4HkAUEgEXAEmBFG/AH+B0gN5BrQLwAAG4bXLOBewPXB/AGu6VtG4CeAUCdAaCcAVCcAQAAAAMAzrAD4IwdAM7PDgDOJwBt2wAA/9uDEjcL3fqtAAAAAElFTkSuQmCC); background-repeat: no-repeat; background-position: center; -webkit-background-size: 16px; background-size: 16px } .zy_unfullscreen { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaBAMAAAEsY2FrAAAAElBMVEX///////////////////////+65XQCAAAABXRSTlMAHm1u3TG+li4AAAB5SURBVBgZBcGxDcMwEAQwGtH1QuD0WiGAB8gI39z+q4SEhR8AwALAwmAwgCAIS4AV0BYg7UAWEIttwNeA1x7gO8BrQDsAAGlBDpA3kOuAeIO4eDYZAM+WAeDZGQA8nwFo2w4AAAAAANq2A9D7AKDuA0C5D4DiPgDAH9lBEChOLXSRAAAAAElFTkSuQmCC) } ================================================ FILE: src/components/video/zy.media.js ================================================ /* * * zy.media.js * HTML5