├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .stylelintrc ├── .travis.yml ├── README.md ├── build ├── bin │ ├── build-entry.js │ ├── build-locale.js │ ├── build-mixins.js │ ├── build-theme.js │ └── build-utils.js ├── config.js ├── dev-server.js ├── happypack.js ├── webpack.base.config.js ├── webpack.components.config.js ├── webpack.dev.config.js ├── webpack.package.config.js ├── webpack.prod.config.js └── webpack.test.config.js ├── commitlint.config.js ├── components.json ├── components ├── button │ ├── index.js │ └── src │ │ └── index.vue ├── carousel │ ├── index.js │ └── src │ │ └── index.vue ├── checkbox │ ├── index.js │ └── src │ │ └── checkbox.vue ├── collapse-item │ ├── index.js │ └── src │ │ ├── collapse-transition.js │ │ └── index.vue ├── collapse │ ├── index.js │ └── src │ │ └── index.vue ├── dialog │ ├── index.js │ └── src │ │ ├── index.vue │ │ └── main.js ├── input │ ├── index.js │ └── src │ │ └── index.vue ├── loading │ ├── index.js │ └── src │ │ └── index.vue ├── notification │ ├── index.js │ └── src │ │ ├── index.vue │ │ └── main.js ├── option │ └── index.js ├── radio-button-group │ ├── index.js │ └── src │ │ └── index.vue ├── radio │ ├── index.js │ └── src │ │ └── radio.vue ├── rate │ ├── index.js │ └── src │ │ └── index.vue ├── select │ ├── index.js │ └── src │ │ ├── index.vue │ │ └── option.vue ├── switch │ ├── index.js │ └── src │ │ └── index.vue ├── tab-panel │ ├── index.js │ └── src │ │ └── index.vue ├── tabs │ ├── index.js │ └── src │ │ └── index.vue └── toast │ ├── index.js │ └── src │ ├── index.vue │ └── main.js ├── config └── index.js ├── docs ├── common.less ├── components │ ├── demo-block.vue │ ├── header.vue │ └── side-nav.vue ├── dist │ ├── 0.da699a70.js │ ├── 0.da699a70.js.map │ ├── 1.37c351b9.js │ ├── 1.37c351b9.js.gz │ ├── 1.37c351b9.js.map │ ├── 10.4be66b11.js │ ├── 10.4be66b11.js.gz │ ├── 10.4be66b11.js.map │ ├── 11.cec9d3a6.js │ ├── 11.cec9d3a6.js.map │ ├── 12.89eeb768.js │ ├── 12.89eeb768.js.gz │ ├── 12.89eeb768.js.map │ ├── 13.154fba12.js │ ├── 13.154fba12.js.gz │ ├── 13.154fba12.js.map │ ├── 14.8242b6a4.js │ ├── 14.8242b6a4.js.gz │ ├── 14.8242b6a4.js.map │ ├── 15.2d69444c.js │ ├── 15.2d69444c.js.gz │ ├── 15.2d69444c.js.map │ ├── 16.6b4129ec.js │ ├── 16.6b4129ec.js.gz │ ├── 16.6b4129ec.js.map │ ├── 17.08c2c7cb.js │ ├── 17.08c2c7cb.js.map │ ├── 2.d39ad9e2.js │ ├── 2.d39ad9e2.js.gz │ ├── 2.d39ad9e2.js.map │ ├── 3.2d5ecdc4.js │ ├── 3.2d5ecdc4.js.gz │ ├── 3.2d5ecdc4.js.map │ ├── 4.b1fc655a.js │ ├── 4.b1fc655a.js.gz │ ├── 4.b1fc655a.js.map │ ├── 5.7b5d6514.js │ ├── 5.7b5d6514.js.map │ ├── 6.8633996a.js │ ├── 6.8633996a.js.map │ ├── 7.70d5d555.js │ ├── 7.70d5d555.js.gz │ ├── 7.70d5d555.js.map │ ├── 8.314732f0.js │ ├── 8.314732f0.js.map │ ├── 9.7b7f3815.js │ ├── 9.7b7f3815.js.map │ ├── app.1be2bc04.js │ ├── app.1be2bc04.js.gz │ ├── app.1be2bc04.js.map │ ├── app.9b85dbc2.css │ ├── app.dae792d1.css │ ├── vendor.01d16740.js │ ├── vendor.01d16740.js.gz │ └── vendor.01d16740.js.map ├── entry.js ├── entry.vue ├── examples │ ├── button.md │ ├── carousel.md │ ├── checkbox.md │ ├── collapse.md │ ├── dialog.md │ ├── input.md │ ├── loading.md │ ├── notification.md │ ├── radio.md │ ├── rate.md │ ├── rbg.md │ ├── select.md │ ├── switch.md │ ├── tabs.md │ ├── toast.md │ └── util.md ├── i18n │ ├── en-US.json │ ├── index.js │ └── zh-CN.json ├── index.html ├── logo.png ├── nav.config.json ├── pages │ ├── change-log.vue │ ├── guide.md │ └── i18n.md ├── routes.config.js └── tpl.html ├── package.json ├── postcss.config.js ├── src ├── index.js ├── locale │ ├── format.js │ ├── index.js │ └── lang │ │ ├── en-US.json │ │ ├── zh-CN.json │ │ ├── zh-HK.json │ │ └── zh-TW.json ├── mixins │ └── i18n.js └── utils │ ├── helpers.js │ ├── index.js │ └── storage.js ├── test ├── index.js ├── karma.conf.js ├── specs │ └── button.spec.js └── utils.js ├── theme-default ├── gulpfile.js └── src │ ├── base.less │ ├── button.less │ ├── carousel.less │ ├── checkbox.less │ ├── collapse-item.less │ ├── collapse.less │ ├── common │ ├── transition.less │ └── var.less │ ├── dialog.less │ ├── index.less │ ├── input.less │ ├── loading.less │ ├── notification.less │ ├── option.less │ ├── radio-button-group.less │ ├── radio.less │ ├── rate.less │ ├── select.less │ ├── switch.less │ ├── tab-panel.less │ ├── tabs.less │ └── toast.less └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "targets": { 6 | "browsers": ["last 2 versions", "safari > 7"] 7 | }, 8 | "modules": false, 9 | "useBuiltIns": "entry", 10 | "loose": true 11 | } 12 | ], 13 | "stage-2" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default 13 | [*.{js,vue,less}] 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # Matches the exact files either package.json or .travis.yml 19 | [{package.json,.travis.yml}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | root: true, 3 | parser: "babel-eslint", 4 | parserOptions: { 5 | ecmaVersion: 7, 6 | sourceType: "module", 7 | allowImportExportEverywhere: false, 8 | ecmaFeatures: { 9 | jsx: true, 10 | modules: true 11 | } 12 | }, 13 | env: { 14 | es6: true, 15 | node: true, 16 | browser: true 17 | }, 18 | extends: "vue", 19 | rules: { 20 | indent: [2, 4], 21 | quotes: [2, "single", { "allowTemplateLiterals": true }], 22 | linebreak-style: [2, "unix"], 23 | semi: [2, "always"], 24 | eqeqeq: [2, "always"], 25 | strict: [2, "global"], 26 | key-spacing: [2, { "afterColon": true }], 27 | no-console: 0, 28 | no-debugger: 0, 29 | no-empty: 0, 30 | no-unused-vars: 0, 31 | no-constant-condition: 0, 32 | no-undef: 0, 33 | handle-callback-err: 0, 34 | no-trailing-spaces: 0, 35 | no-unneeded-ternary: 0, 36 | no-cond-assign: 0, 37 | no-return-assign: 0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | .idea 42 | public/dist 43 | .DS_Store 44 | .cache 45 | converage 46 | .vscode 47 | reports 48 | .cache 49 | .happypack 50 | lib 51 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "comment-empty-line-before": null, 5 | "declaration-empty-line-before": null, 6 | "function-comma-newline-after": null, 7 | "function-name-case": null, 8 | "function-parentheses-newline-inside": null, 9 | "function-max-empty-lines": null, 10 | "function-whitespace-after": null, 11 | "indentation": null, 12 | "number-leading-zero": null, 13 | "number-no-trailing-zeros": null, 14 | "rule-empty-line-before": null, 15 | "selector-combinator-space-after": null, 16 | "selector-list-comma-newline-after": null, 17 | "selector-pseudo-element-colon-notation": null, 18 | "unit-no-unknown": null, 19 | "value-list-max-empty-lines": null 20 | } 21 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | sudo: false 4 | dist: trusty 5 | cache: 6 | directories: 7 | - node_modules 8 | addons: 9 | chrome: stable 10 | before_install: 11 | - export CHROME_BIN=chromium-browser 12 | - export DISPLAY=:99.0 13 | - sh -e /etc/init.d/xvfb start 14 | branches: 15 | only: 16 | - master 17 | node_js: 18 | - '8' 19 | script: 20 | - npm test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build pass](https://api.travis-ci.org/fmfe/fm-vue-ui.svg?branch=master)](https://travis-ci.org/fmfe/fm-vue-ui) ![npm-version](https://img.shields.io/npm/v/fm-vue-ui.svg) ![license](https://img.shields.io/npm/l/fm-vue-ui.svg) 2 | ## fm-vue-ui 3 | Basic vue components. 4 | 5 | ## Development 6 | 7 | ``` 8 | git clone -b dev git@github.com:fmfe/fm-vue-ui.git 9 | 10 | cd fm-vue-ui 11 | 12 | npm i 13 | 14 | npm run dev:docs 15 | # http://localhost:3000/fm-vue-ui/index 16 | ``` 17 | 18 | 文档地址:https://fmfe.github.io/fm-vue-ui/index 19 | 20 | ## 贡献指南 21 | 1. 可以在 [fm-vue-ui](https://github.com/fmfe/fm-vue-ui/issues) 以 issue 的形式说明你的需求 22 | 2. fork [fm-vue-ui](https://github.com/fmfe/fm-vue-ui), 然后开发自己的组件,写好对应的文档说明和单测,提交 PR 23 | 3. 代码规范请参考 [coding style](https://github.com/fmfe/fe-coding-style-guide/) 24 | 4. 对于组件中涉及的图标,请优先使用 CSS 来实现; 如果实现不了, 请将对应的图片资源放在 CDN 上 25 | ## 开发步骤 26 | 1. 在 `components.json` 文件中添加对应组件的映射(组件名和组件路径) 27 | 2. 在 `components` 目录下按格式建立自己的组件目录,对应的样式在 `theme-default/src` 目录下,文件名和映射的组件名保持一致 28 | 3. 执行 `npm run com` -------------------------------------------------------------------------------- /build/bin/build-entry.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const endOfLine = require('os').EOL; 4 | const render = require('json-templater/string'); 5 | const uppercamelcase = require('uppercamelcase'); 6 | 7 | const Components = require('../../components.json'); 8 | const OUTPUT_PATH = path.join(__dirname, '../../src/index.js'); 9 | const IMPORT_TEMPLATE = 'import {{name}} from \'../components/{{package}}/index.js\';'; 10 | const INSTALL_COMPONENT_TEMPLATE = ' {{name}}'; 11 | const MAIN_TEMPLATE = `/* Automatic generated by './build/build-entry.js' */ 12 | 13 | import utils from 'fm-vue-ui/src/utils'; 14 | import locale from 'fm-vue-ui/src/locale'; 15 | 16 | {{include}} 17 | 18 | const components = [ 19 | {{install}} 20 | ]; 21 | 22 | const install = function (Vue, opts = {}) { 23 | if (install.installed) return; 24 | 25 | let lang = 'zh-CN'; 26 | try { 27 | lang = opts.lang || (window.FMlocale ? window.FMlocale() : 'zh-CN'); 28 | } catch (e) {} 29 | locale.use(lang); 30 | 31 | components.map(component => { 32 | Vue.component(component.name, component); 33 | }); 34 | 35 | Vue.prototype.$fmdialog = Dialog; 36 | Vue.prototype.$fmtoast = Toast; 37 | Vue.prototype.$fmutils = utils; 38 | }; 39 | 40 | if (typeof window !== 'undefined' && window.Vue) { 41 | install(window.Vue); 42 | }; 43 | 44 | export { 45 | {{list}} 46 | }; 47 | 48 | export default { 49 | version: '{{version}}', 50 | install, 51 | {{list}} 52 | }; 53 | 54 | `; 55 | 56 | const ComponentNames = Object.keys(Components); 57 | 58 | const includeComponentTemplate = []; 59 | const installTemplate = []; 60 | const listTemplate = []; 61 | 62 | ComponentNames.forEach(name => { 63 | const componentName = uppercamelcase(name); 64 | 65 | includeComponentTemplate.push(render(IMPORT_TEMPLATE, { 66 | name: componentName, 67 | package: name 68 | })); 69 | 70 | if (['Dialog'].indexOf(componentName) === -1) { 71 | installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, { 72 | name: componentName, 73 | component: name 74 | })); 75 | } 76 | listTemplate.push(` ${componentName}`); 77 | }); 78 | 79 | const template = render(MAIN_TEMPLATE, { 80 | include: includeComponentTemplate.join(endOfLine), 81 | install: installTemplate.join(',' + endOfLine), 82 | version: process.env.VERSION || require('../../package.json').version, 83 | list: listTemplate.join(',' + endOfLine) 84 | }); 85 | 86 | fs.writeFileSync(OUTPUT_PATH, template); 87 | console.log('[build entry] DONE:', OUTPUT_PATH); 88 | 89 | -------------------------------------------------------------------------------- /build/bin/build-locale.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const exec = require('child_process').execSync; 4 | const babel = require('babel-core'); 5 | 6 | const localePath = path.resolve(__dirname, '../../src/locale'); 7 | const outputPath = path.resolve(__dirname, '../../lib/locale'); 8 | const filesList = fs.readdirSync(localePath); 9 | 10 | function dirExist (path) { 11 | try { 12 | fs.accessSync(path, fs.F_OK); 13 | } catch (e) { 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | function copyFiles () { 20 | if (!dirExist(path.resolve(__dirname, '../../lib'))) { 21 | exec('mkdir lib'); 22 | } 23 | 24 | if (!dirExist(path.resolve(__dirname, '../../lib/locale'))) { 25 | exec('mkdir lib/locale'); 26 | } 27 | 28 | exec('cp -rf ./src/locale/lang ./lib/locale/lang'); 29 | } 30 | 31 | copyFiles(); 32 | 33 | function transformFile (filename, name, cb) { 34 | babel.transformFile(path.resolve(localePath, filename), { 35 | plugins: ['add-module-exports', 'transform-es2015-modules-commonjs'], 36 | moduleId: name 37 | }, cb); 38 | } 39 | 40 | filesList.forEach(file => { 41 | const name = path.basename(file, '.js'); 42 | if (fs.lstatSync(path.resolve(localePath, file)).isDirectory()) { 43 | return; 44 | } 45 | transformFile(file, name, (err, result) => { 46 | if (err) { 47 | console.log('write failed', err); 48 | } else { 49 | const code = result.code; 50 | fs.writeFileSync(path.resolve(outputPath, file), code); 51 | } 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /build/bin/build-mixins.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const exec = require('child_process').execSync; 4 | const babel = require('babel-core'); 5 | 6 | const mixinsPath = path.resolve(__dirname, '../../src/mixins/'); 7 | const outputPath = path.resolve(__dirname, '../../lib/mixins/'); 8 | 9 | const filesList = fs.readdirSync(mixinsPath); 10 | 11 | function dirExist (path) { 12 | try { 13 | fs.accessSync(path, fs.F_OK); 14 | } catch (e) { 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | function transformFile (filename, name, cb) { 21 | babel.transformFile(path.resolve(mixinsPath, filename), { 22 | plugins: [['replace-import-path', { 23 | src: 'fm-vue-ui/src/locale/index.js', 24 | dest: '@fmfe/fm-vue-ui/lib/locale/index.js' 25 | }], 'add-module-exports', 'transform-es2015-modules-commonjs'], 26 | moduleId: name 27 | }, cb); 28 | } 29 | 30 | filesList.forEach(file => { 31 | const name = path.basename(file, '.js'); 32 | transformFile(file, name, (err, result) => { 33 | if (err) { 34 | console.log('write failed', err); 35 | } else { 36 | if (!dirExist(path.resolve(__dirname, '../../lib'))) { 37 | exec('mkdir lib'); 38 | } 39 | if (!dirExist(path.resolve(__dirname, '../../lib/mixins'))) { 40 | exec('mkdir lib/mixins'); 41 | } 42 | 43 | const code = result.code; 44 | fs.writeFileSync(path.resolve(outputPath, file), code); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /build/bin/build-theme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Components = require('../../components.json'); 4 | 5 | const allKeys = Object.keys(Components); 6 | const basepath = path.resolve(__dirname, '../../theme-default/'); 7 | 8 | function fileExists (filePath) { 9 | try { 10 | return fs.statSync(filePath).isFile(); 11 | } catch (err) { 12 | return false; 13 | } 14 | } 15 | 16 | let indexContent = '@import "./base.less";\n'; 17 | 18 | allKeys.forEach(key => { 19 | const fileName = `${key}.less`; 20 | indexContent += '@import "./' + fileName + '";\n'; 21 | 22 | const filePath = path.resolve(basepath, 'src', fileName); 23 | if (!fileExists(filePath)) { 24 | fs.writeFileSync(filePath, '', 'utf8'); 25 | console.log('创建遗漏的 ', fileName, ' 文件'); 26 | } 27 | }); 28 | 29 | fs.writeFileSync(path.resolve(basepath, 'src', 'index.less'), indexContent); 30 | -------------------------------------------------------------------------------- /build/bin/build-utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const exec = require('child_process').execSync; 4 | const babel = require('babel-core'); 5 | 6 | const utilsPath = path.resolve(__dirname, '../../src/utils/'); 7 | const outputPath = path.resolve(__dirname, '../../lib/utils/'); 8 | 9 | const filesList = fs.readdirSync(utilsPath); 10 | 11 | function dirExist (path) { 12 | try { 13 | fs.accessSync(path, fs.F_OK); 14 | } catch (e) { 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | function transformFile (filename, name, cb) { 21 | babel.transformFile(path.resolve(utilsPath, filename), { 22 | plugins: ['add-module-exports', 'transform-es2015-modules-commonjs'], 23 | moduleId: name 24 | }, cb); 25 | } 26 | 27 | filesList.forEach(file => { 28 | const name = path.basename(file, '.js'); 29 | transformFile(file, name, (err, result) => { 30 | if (err) { 31 | console.log('write failed', err); 32 | } else { 33 | if (!dirExist(path.resolve(__dirname, '../../lib'))) { 34 | exec('mkdir lib'); 35 | } 36 | if (!dirExist(path.resolve(__dirname, '../../lib/utils'))) { 37 | exec('mkdir lib/utils'); 38 | } 39 | 40 | const code = result.code; 41 | fs.writeFileSync(path.resolve(outputPath, file), code); 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | const nodeExternals = require('webpack-node-externals'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const Components = require('../components.json'); 6 | // const utilsList = fs.readdirSync(path.resolve(__dirname, '../src/utils')); 7 | const mixinsList = fs.readdirSync(path.resolve(__dirname, '../src/mixins')); 8 | 9 | const vueExternal = { 10 | 'vue': { 11 | root: 'Vue', 12 | commonjs: 'vue', 13 | commonjs2: 'vue', 14 | amd: 'vue' 15 | } 16 | }; 17 | 18 | let externals = {}; 19 | 20 | Object.keys(Components).forEach(function (key) { 21 | externals[`fm-vue-ui/components/${key}`] = `@fmfe/fm-vue-ui/lib/${key}`; 22 | }); 23 | 24 | externals['fm-vue-ui/src/locale'] = '@fmfe/fm-vue-ui/lib/locale'; 25 | externals[`fm-vue-ui/src/utils`] = `@fmfe/fm-vue-ui/lib/utils`; 26 | 27 | // utilsList.forEach(file => { 28 | // file = path.basename(file, '.js'); 29 | // externals[`fm-vue-ui/src/utils/${file}`] = `fm-vue-ui/lib/utils/${file}`; 30 | // }); 31 | 32 | mixinsList.forEach(file => { 33 | file = path.basename(file, '.js'); 34 | externals[`fm-vue-ui/src/mixins/${file}`] = `@fmfe/fm-vue-ui/lib/mixins/${file}`; 35 | }); 36 | 37 | externals = [ 38 | Object.assign({}, externals, vueExternal), 39 | nodeExternals() 40 | ]; 41 | 42 | const alias = { 43 | main: path.resolve(__dirname, '../src'), 44 | components: path.resolve(__dirname, '../components'), 45 | 'fm-vue-ui': path.resolve(__dirname, '../') 46 | }; 47 | 48 | module.exports = { 49 | alias, 50 | externals 51 | }; 52 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const WebpackDevServer = require('webpack-dev-server'); 3 | const ora = require('ora'); 4 | const gutil = require('gulp-util'); 5 | 6 | const webpackDevConfig = require('./webpack.dev.config.js'); 7 | const config = require('../config/index'); 8 | 9 | const compiler = webpack(webpackDevConfig); 10 | const server = new WebpackDevServer(compiler, webpackDevConfig.devServer); 11 | 12 | const env = process.env.NODE_ENV || 'development'; 13 | const url = `localhost:${config.dev.port}/fm-vue-ui/index`; 14 | 15 | let spinner = ora({ 16 | text: 'Webpack 正在编译...\n', 17 | color: 'green' 18 | }).start(); 19 | 20 | function compiledFail () { 21 | if (spinner) { 22 | spinner.color = 'red'; 23 | spinner.text = gutil.colors.white('Webpack 编译失败: \n'); 24 | spinner.fail(); 25 | spinner = null; 26 | } 27 | } 28 | 29 | server.listen(config.dev.port, 'localhost', (err) => { 30 | if (err) { 31 | compiledFail(); 32 | throw new gutil.PluginError('[webpack-dev-server err]', err); 33 | } 34 | }); 35 | 36 | // 编译完成 37 | compiler.plugin('done', (stats) => { 38 | if (spinner) { 39 | spinner.text = gutil.colors.green(`Webpack 编译成功, open browser to visit ${url}\n`); 40 | spinner.succeed(); 41 | spinner = null; 42 | } 43 | }); 44 | 45 | // 编译失败 46 | compiler.plugin('failed', (err) => { 47 | compiledFail(); 48 | throw new gutil.PluginError('[webpack build err]', err); 49 | }); 50 | 51 | // 监听文件修改 52 | compiler.plugin('compilation', compilation => {}); 53 | -------------------------------------------------------------------------------- /build/happypack.js: -------------------------------------------------------------------------------- 1 | const HappyPack = require('happypack'); 2 | const os = require('os'); 3 | const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 4 | 5 | module.exports = function (opts) { 6 | return { 7 | id: opts.id, 8 | threadPool: happyThreadPool, 9 | verbose: true, 10 | loaders: opts.loaders 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /build/webpack.components.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); 4 | const os = require('os'); 5 | 6 | const Components = require('../components.json'); 7 | const config = require('./config'); 8 | 9 | module.exports = { 10 | entry: Components, 11 | output: { 12 | path: path.join(__dirname, '../lib'), 13 | filename: '[name].js', 14 | libraryTarget: 'commonjs2' 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.vue$/, 20 | loader: 'vue-loader' 21 | }, 22 | { 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | loader: 'babel-loader' 26 | }, 27 | { 28 | test: /\.less$/, 29 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: ['vue-style-loader', 'css-loader'] 34 | } 35 | ] 36 | }, 37 | externals: config.externals, 38 | resolve: { 39 | alias: config.alias 40 | }, 41 | plugins: [ 42 | // new ParallelUglifyPlugin({ 43 | // workerCount: os.cpus().length, 44 | // cacheDir: '.cache/', 45 | // uglifyJS: { 46 | // compress: { 47 | // warnings: false, 48 | // /* eslint-disable */ 49 | // drop_debugger: true, 50 | // drop_console: true 51 | // }, 52 | // comments: false, 53 | // sourceMap: true, 54 | // mangle: true 55 | // } 56 | // }), 57 | new webpack.optimize.ModuleConcatenationPlugin() 58 | ] 59 | }; 60 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const OpenBrowserPlugin = require('open-browser-webpack-plugin'); 4 | const HappyPack = require('happypack'); 5 | 6 | const getHappyPackConfig = require('./happypack'); 7 | 8 | const devConfig = require('./webpack.base.config'); 9 | const config = require('../config'); 10 | const url = `http://localhost:${config.dev.port}/fm-vue-ui/index`; 11 | 12 | devConfig.module.rules.unshift({ 13 | test: /\.less$/, 14 | use: ['happypack/loader?id=less-dev'] 15 | }, { 16 | test: /\.css$/, 17 | use: ['happypack/loader?id=css-dev'] 18 | }); 19 | 20 | devConfig.plugins = (devConfig.plugins || []).concat([ 21 | new webpack.HotModuleReplacementPlugin(), 22 | 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | 'NODE_ENV': JSON.stringify(config.dev.env) 26 | } 27 | }), 28 | 29 | new HappyPack(getHappyPackConfig({ 30 | id: 'less-dev', 31 | loaders: ['vue-style-loader', 'css-loader', 'postcss-loader', 'less-loader'] 32 | })), 33 | 34 | new HappyPack(getHappyPackConfig({ 35 | id: 'css-dev', 36 | loaders: ['vue-style-loader', 'css-loader', 'postcss-loader'] 37 | })), 38 | 39 | new webpack.NoEmitOnErrorsPlugin(), 40 | new OpenBrowserPlugin({ url: url }) 41 | ]); 42 | 43 | // see https://webpack.github.io/docs/webpack-dev-server.html 44 | devConfig.devServer = { 45 | hot: true, 46 | noInfo: false, 47 | quiet: false, 48 | port: config.dev.port, 49 | // #https://github.com/webpack/webpack-dev-server/issues/882 50 | disableHostCheck: true, 51 | headers: { 52 | 'Access-Control-Allow-Origin': '*', 53 | 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' 54 | }, 55 | inline: true, 56 | // 解决开发模式下 在子路由刷新返回 404 的情景 57 | historyApiFallback: { 58 | index: config.dev.assetsPublicPath 59 | }, 60 | stats: { 61 | colors: true, 62 | modules: false 63 | }, 64 | contentBase: config.dev.contentBase, 65 | publicPath: config.dev.assetsPublicPath 66 | }; 67 | 68 | module.exports = Object.assign({}, devConfig, { 69 | entry: { 70 | app: [ 71 | 'webpack/hot/dev-server', 72 | `webpack-dev-server/client?http://localhost:${config.dev.port}/`, 73 | path.resolve(__dirname, '../docs/entry.js') 74 | ] 75 | }, 76 | output: { 77 | filename: '[name].js', 78 | path: config.dev.assetsRoot, 79 | publicPath: config.dev.assetsPublicPath, 80 | sourceMapFilename: '[file].map', 81 | chunkFilename: '[name].js' 82 | }, 83 | devtool: 'source-map' 84 | }); 85 | -------------------------------------------------------------------------------- /build/webpack.package.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); 4 | const os = require('os'); 5 | 6 | const config = require('./config'); 7 | 8 | module.exports = { 9 | entry: path.join(__dirname, '../src/index'), 10 | output: { 11 | path: path.join(__dirname, '../lib'), 12 | filename: 'fm-vue-ui.common.js', 13 | library: 'FMUI', 14 | libraryTarget: 'commonjs2' 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.vue$/, 20 | loader: 'vue-loader' 21 | }, 22 | { 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | loader: 'babel-loader' 26 | }, 27 | { 28 | test: /\.less$/, 29 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: ['vue-style-loader', 'css-loader'] 34 | } 35 | ] 36 | }, 37 | externals: config.externals, 38 | resolve: { 39 | alias: config.alias 40 | }, 41 | plugins: [ 42 | // new ParallelUglifyPlugin({ 43 | // workerCount: os.cpus().length, 44 | // cacheDir: '.cache/', 45 | // uglifyJS: { 46 | // compress: { 47 | // warnings: false, 48 | // /* eslint-disable */ 49 | // drop_debugger: true, 50 | // drop_console: true 51 | // }, 52 | // comments: false, 53 | // sourceMap: true, 54 | // mangle: true 55 | // } 56 | // }), 57 | new webpack.optimize.ModuleConcatenationPlugin() 58 | ] 59 | }; 60 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); 5 | const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); 6 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 7 | const WebpackMd5Hash = require('webpack-md5-hash'); 8 | const os = require('os'); 9 | const CompressionPlugin = require('compression-webpack-plugin'); 10 | const HappyPack = require('happypack'); 11 | 12 | const getHappyPackConfig = require('./happypack'); 13 | 14 | const prodConfig = require('./webpack.base.config'); 15 | const config = require('../config'); 16 | 17 | prodConfig.module.rules.unshift({ 18 | test: /\.less$/, 19 | use: ExtractTextPlugin.extract({ 20 | fallback: 'vue-style-loader', 21 | use: ['happypack/loader?id=less-prod'] 22 | }) 23 | }, { 24 | test: /\.css$/, 25 | use: ExtractTextPlugin.extract({ 26 | fallback: 'vue-style-loader', 27 | use: ['happypack/loader?id=css-prod'] 28 | }) 29 | }); 30 | 31 | prodConfig.plugins = (prodConfig.plugins || []).concat([ 32 | new CleanWebpackPlugin(['dist'], { 33 | root: path.join(__dirname, '../docs'), 34 | verbose: true, 35 | dry: false 36 | }), 37 | 38 | new webpack.DefinePlugin({ 39 | 'process.env': { 40 | 'NODE_ENV': JSON.stringify(config.build.env) 41 | } 42 | }), 43 | 44 | new ExtractTextPlugin({ 45 | filename: '[name].[contenthash:8].css' 46 | }), 47 | 48 | new HappyPack(getHappyPackConfig({ 49 | id: 'less-prod', 50 | loaders: ['css-loader', { 51 | path: 'postcss-loader', 52 | query: { 53 | sourceMap: 'inline' 54 | } 55 | }, 'less-loader'] 56 | })), 57 | 58 | new HappyPack(getHappyPackConfig({ 59 | id: 'css-prod', 60 | loaders: ['css-loader', { 61 | path: 'postcss-loader', 62 | query: { 63 | sourceMap: 'inline' 64 | } 65 | }] 66 | })), 67 | 68 | // Compress extracted CSS. We are using this plugin so that possible 69 | // duplicated CSS from different components can be deduped. 70 | new OptimizeCSSPlugin({ 71 | cssProcessorOptions: { 72 | safe: true 73 | }, 74 | cssProcessor: require('cssnano'), 75 | assetNameRegExp: /\.less|\.css$/g 76 | }), 77 | 78 | new webpack.optimize.CommonsChunkPlugin({ 79 | name: 'vendor', 80 | minChunks: ({ resource }) => ( 81 | resource && 82 | resource.indexOf('node_modules') >= 0 && 83 | resource.match(/\.js$/) 84 | ) 85 | }), 86 | 87 | // gzip 88 | new CompressionPlugin({ 89 | asset: '[path].gz[query]', 90 | algorithm: 'gzip', 91 | test: /\.(js|html|less)$/, 92 | threshold: 10240, 93 | minRatio: 0.8 94 | }), 95 | 96 | new ParallelUglifyPlugin({ 97 | workerCount: os.cpus().length, 98 | cacheDir: '.cache/', 99 | sourceMap: true, 100 | uglifyJS: { 101 | compress: { 102 | // warnings: false, 103 | /* eslint-disable camelcase */ 104 | drop_debugger: true, 105 | drop_console: true 106 | }, 107 | mangle: true 108 | } 109 | }), 110 | 111 | new webpack.optimize.ModuleConcatenationPlugin(), 112 | new WebpackMd5Hash() 113 | ]); 114 | 115 | module.exports = Object.assign({}, prodConfig, { 116 | entry: { 117 | app: path.resolve(__dirname, '../docs/entry.js') 118 | }, 119 | output: { 120 | filename: '[name].[chunkhash:8].js', 121 | path: config.build.assetsRoot, 122 | publicPath: config.build.assetsPublicPath, 123 | sourceMapFilename: '[file].map', 124 | chunkFilename: '[name].[chunkhash:8].js' 125 | }, 126 | devtool: 'source-map' 127 | }); 128 | -------------------------------------------------------------------------------- /build/webpack.test.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: path.join(__dirname, '../src/index'), 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.vue$/, 10 | loader: 'vue-loader' 11 | }, 12 | { 13 | test: /\.js$/, 14 | exclude: /node_modules/, 15 | loader: 'babel-loader' 16 | }, 17 | { 18 | test: /\.less$/, 19 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 20 | }, 21 | { 22 | test: /\.css$/, 23 | use: ['vue-style-loader', 'css-loader'] 24 | } 25 | ] 26 | }, 27 | // externals: config.externals, 28 | resolve: { 29 | extensions: ['.js', '.vue'], 30 | alias: { 31 | components: path.resolve(__dirname, '../components'), 32 | 'main': path.resolve(__dirname, '../src'), 33 | 'vue$': 'vue/dist/vue.esm.js', 34 | 'fm-vue-ui': path.resolve(__dirname, '../') 35 | }, 36 | modules: [path.join(__dirname, '../node_modules')] 37 | }, 38 | devtool: 'inline-source-map' 39 | }; 40 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-angular'], 3 | rules: { 4 | 'subject-case': [0] 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": "./components/button/index.js", 3 | "dialog": "./components/dialog/index.js", 4 | "toast": "./components/toast/index.js", 5 | "notification": "./components/notification/index.js", 6 | "loading": "./components/loading/index.js", 7 | "collapse": "./components/collapse/index.js", 8 | "collapse-item": "./components/collapse-item/index.js", 9 | "tabs": "./components/tabs/index.js", 10 | "tab-panel": "./components/tab-panel/index.js", 11 | "rate": "./components/rate/index.js", 12 | "carousel": "./components/carousel/index.js", 13 | "input": "./components/input/index.js", 14 | "checkbox": "./components/checkbox/index.js", 15 | "radio": "./components/radio/index.js", 16 | "switch": "./components/switch/index.js", 17 | "option": "./components/option/index.js", 18 | "select": "./components/select/index.js", 19 | "radio-button-group": "./components/radio-button-group/index.js" 20 | } 21 | -------------------------------------------------------------------------------- /components/button/index.js: -------------------------------------------------------------------------------- 1 | import FMButton from './src/index.vue'; 2 | 3 | FMButton.install = function (Vue) { 4 | Vue.component(FMButton.name, FMButton); 5 | }; 6 | 7 | export default FMButton; 8 | -------------------------------------------------------------------------------- /components/button/src/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 59 | -------------------------------------------------------------------------------- /components/carousel/index.js: -------------------------------------------------------------------------------- 1 | import FMCarousel from './src/index.vue'; 2 | 3 | FMCarousel.install = function (Vue) { 4 | Vue.component(FMCarousel.name, FMCarousel); 5 | }; 6 | 7 | export default FMCarousel; 8 | -------------------------------------------------------------------------------- /components/carousel/src/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 58 | -------------------------------------------------------------------------------- /components/checkbox/index.js: -------------------------------------------------------------------------------- 1 | import FMCheckbox from './src/checkbox.vue'; 2 | 3 | FMCheckbox.install = function (Vue) { 4 | Vue.component(FMCheckbox.name, FMCheckbox); 5 | }; 6 | 7 | export default FMCheckbox; 8 | -------------------------------------------------------------------------------- /components/checkbox/src/checkbox.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 63 | -------------------------------------------------------------------------------- /components/collapse-item/index.js: -------------------------------------------------------------------------------- 1 | import FMCollapseItem from './src/index.vue'; 2 | 3 | FMCollapseItem.install = function (Vue) { 4 | Vue.component(FMCollapseItem.name, FMCollapseItem); 5 | }; 6 | 7 | export default FMCollapseItem; 8 | -------------------------------------------------------------------------------- /components/collapse-item/src/collapse-transition.js: -------------------------------------------------------------------------------- 1 | import { createTinyDOM, css } from 'tiny-dom-helpers'; 2 | 3 | // https://cn.vuejs.org/v2/guide/transitions.html#可复用的过渡 4 | const hooks = { 5 | // 进入时 6 | beforeEnter (el) { 7 | const dom = createTinyDOM(el); 8 | dom.addClass('fm-collapse-enter-transition'); 9 | if (!el.dataset) { 10 | el.dataset = {}; 11 | } 12 | el.dataset.oldPaddingTop = css(el, 'padding-top', null); 13 | el.dataset.oldPaddingBottom = css(el, 'padding-bottom', null); 14 | dom.attr('style', 'height: 0; padding-top: 0; padding-bottom: 0'); 15 | }, 16 | 17 | enter (el, done) { 18 | el.dataset.oldOverflow = css(el, 'overflow', null); 19 | const dom = createTinyDOM(el); 20 | if (el.scrollHeight !== 0) { 21 | dom.attr('style', `overflow: hidden; height: ${el.scrollHeight}px; padding-top: ${el.dataset.oldPaddingTop}; padding-bottom: ${el.dataset.oldPaddingBottom}`); 22 | } else { 23 | dom.attr('style', `overflow: hidden; height: ''; padding-top: ${el.dataset.oldPaddingTop}; padding-bottom: ${el.dataset.oldPaddingBottom}`); 24 | } 25 | }, 26 | 27 | afterEnter (el) { 28 | const dom = createTinyDOM(el); 29 | dom.removeClass('fm-collapse-enter-transition'); 30 | el.style.height = 0; 31 | el.style.overflow = el.dataset.oldOverflow; 32 | }, 33 | 34 | // 离开时 35 | beforeLeave (el) { 36 | if (!el.dataset) { 37 | el.dataset = {}; 38 | }; 39 | const dom = createTinyDOM(el); 40 | dom.addClass('fm-collapse-leave-transition'); 41 | el.dataset.oldPaddingTop = css(el, 'padding-top', null); 42 | el.dataset.oldPaddingBottom = css(el, 'padding-bottom', null); 43 | el.dataset.oldOverflow = css(el, 'overflow', null); 44 | 45 | dom.attr('style', `overflow: hidden; height: ${el.scrollHeight}px`); 46 | }, 47 | 48 | leave (el) { 49 | const dom = createTinyDOM(el); 50 | if (el.scrollHeight !== 0) { 51 | dom.addClass('fm-collapse-leave-transition'); 52 | dom.attr('style', `overflow: hidden; height: 0; padding-top: 0; padding-bottom: 0`); 53 | } 54 | }, 55 | 56 | afterLeave (el) { 57 | const dom = createTinyDOM(el); 58 | dom.removeClass('fm-collapse-leave-transition'); 59 | dom.attr('style', `overflow: ${el.dataset.oldOverflow}; height: 0; padding-top: ${el.dataset.oldPaddingTop}; padding-bottom: ${el.dataset.oldPaddingBottom}`); 60 | } 61 | }; 62 | 63 | export default { 64 | name: 'fm-collapse-transition', 65 | functional: true, 66 | render (h, { children }) { 67 | const data = { 68 | on: hooks, 69 | props: { 70 | mode: 'out-in' 71 | } 72 | }; 73 | return h('transition', data, children); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /components/collapse/index.js: -------------------------------------------------------------------------------- 1 | import FMCollapse from './src/index.vue'; 2 | 3 | FMCollapse.install = function (Vue) { 4 | Vue.component(FMCollapse.name, FMCollapse); 5 | }; 6 | 7 | export default FMCollapse; 8 | -------------------------------------------------------------------------------- /components/collapse/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /components/dialog/index.js: -------------------------------------------------------------------------------- 1 | import FMDialog from './src/main.js'; 2 | export default FMDialog; 3 | -------------------------------------------------------------------------------- /components/dialog/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import FMDialog from './index.vue'; 3 | 4 | const DialogConstructor = Vue.extend(FMDialog); 5 | 6 | let instance = null; 7 | 8 | DialogConstructor.prototype.remove = function () { 9 | this.shown = false; 10 | if (this.$el.parentNode) { 11 | this.$el.parentNode.removeChild(this.$el); 12 | } 13 | instance = null; 14 | }; 15 | 16 | const getAnInstance = (props) => { 17 | if (instance) { 18 | return instance; 19 | } 20 | 21 | return new DialogConstructor({ 22 | propsData: props, 23 | el: document.createElement('div') 24 | }); 25 | }; 26 | 27 | const Dialog = function (opts) { 28 | if (Vue.prototype.$isServer) { 29 | return; 30 | } 31 | 32 | if (typeof opts === 'string') { 33 | opts = { 34 | message: opts 35 | }; 36 | } 37 | 38 | if (typeof opts.onCancel === 'function') { 39 | const cancel = opts.onCancel; 40 | opts.onCancel = () => { 41 | instance.remove(); 42 | cancel(); 43 | }; 44 | } else { 45 | opts.onCancel = () => { 46 | instance.remove(); 47 | }; 48 | } 49 | 50 | if (typeof opts.onConfirm === 'function') { 51 | const confirm = opts.onConfirm; 52 | opts.onConfirm = () => { 53 | instance.remove(); 54 | confirm(); 55 | }; 56 | } else { 57 | opts.onConfirm = () => { 58 | instance.remove(); 59 | }; 60 | } 61 | 62 | if (typeof opts.onClose === 'function') { 63 | const close = opts.onClose; 64 | opts.onClose = () => { 65 | instance.remove(); 66 | close(); 67 | }; 68 | } else { 69 | opts.onClose = () => {}; 70 | } 71 | 72 | instance = getAnInstance(opts); 73 | document.body.appendChild(instance.$el); 74 | instance.shown = true; 75 | 76 | // 自动销毁 77 | if (instance.validType) { 78 | setTimeout(() => { 79 | instance && instance.remove(); 80 | }, instance.duration); 81 | } 82 | }; 83 | 84 | export default Dialog; 85 | -------------------------------------------------------------------------------- /components/input/index.js: -------------------------------------------------------------------------------- 1 | import FMInput from './src/index.vue'; 2 | 3 | FMInput.install = function (Vue) { 4 | Vue.component(FMInput.name, FMInput); 5 | }; 6 | 7 | export default FMInput; 8 | -------------------------------------------------------------------------------- /components/input/src/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 149 | -------------------------------------------------------------------------------- /components/loading/index.js: -------------------------------------------------------------------------------- 1 | import FMLoading from './src/index.vue'; 2 | 3 | FMLoading.install = function (Vue) { 4 | Vue.component(FMLoading.name, FMLoading); 5 | }; 6 | 7 | export default FMLoading; 8 | -------------------------------------------------------------------------------- /components/loading/src/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /components/notification/index.js: -------------------------------------------------------------------------------- 1 | import FMNotification from './src/main.js'; 2 | export default FMNotification; 3 | -------------------------------------------------------------------------------- /components/notification/src/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 71 | -------------------------------------------------------------------------------- /components/notification/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import FMToast from './index.vue'; 3 | 4 | const ToastConstructor = Vue.extend(FMToast); 5 | 6 | let instance = null; 7 | let closeTimeoutEvent = null; 8 | 9 | ToastConstructor.prototype.remove = function () { 10 | this.shown = false; 11 | if (this.$el.parentNode) { 12 | this.$el.parentNode.removeChild(this.$el); 13 | } 14 | instance = null; 15 | }; 16 | 17 | const getAnInstance = (props) => { 18 | if (instance) { 19 | instance.remove(); 20 | instance = null; 21 | // return instance; 22 | } 23 | 24 | return new ToastConstructor({ 25 | propsData: props, 26 | el: document.createElement('div') 27 | }); 28 | }; 29 | 30 | const Toast = function (opts) { 31 | if (Vue.prototype.$isServer) { 32 | return; 33 | } 34 | 35 | if (typeof opts === 'string') { 36 | opts = { 37 | message: opts 38 | }; 39 | } 40 | 41 | if (typeof opts.onClose === 'function') { 42 | const close = opts.onClose; 43 | opts.onClose = () => { 44 | instance.remove(); 45 | close(); 46 | }; 47 | } else { 48 | opts.onClose = () => { 49 | instance.remove(); 50 | clearTimeout(closeTimeoutEvent); 51 | }; 52 | } 53 | 54 | instance = getAnInstance(opts); 55 | document.body.appendChild(instance.$el); 56 | instance.shown = true; 57 | 58 | // 自动销毁 59 | if (instance.duration > 0) { 60 | clearTimeout(closeTimeoutEvent); 61 | closeTimeoutEvent = setTimeout(() => { 62 | instance && instance.remove(); 63 | }, instance.duration); 64 | } 65 | }; 66 | 67 | export default Toast; 68 | -------------------------------------------------------------------------------- /components/option/index.js: -------------------------------------------------------------------------------- 1 | import FMOption from '../select/src/option.vue'; 2 | 3 | FMOption.install = function (Vue) { 4 | Vue.component(FMOption.name, FMOption); 5 | }; 6 | 7 | export default FMOption; 8 | -------------------------------------------------------------------------------- /components/radio-button-group/index.js: -------------------------------------------------------------------------------- 1 | import RadioButtonGroup from './src/index.vue'; 2 | 3 | RadioButtonGroup.install = function (Vue) { 4 | Vue.component(RadioButtonGroup.name, RadioButtonGroup); 5 | }; 6 | 7 | export default RadioButtonGroup; 8 | -------------------------------------------------------------------------------- /components/radio-button-group/src/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 58 | -------------------------------------------------------------------------------- /components/radio/index.js: -------------------------------------------------------------------------------- 1 | import FMRadio from './src/radio.vue'; 2 | 3 | FMRadio.install = function (Vue) { 4 | Vue.component(FMRadio.name, FMRadio); 5 | }; 6 | 7 | export default FMRadio; 8 | -------------------------------------------------------------------------------- /components/radio/src/radio.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 79 | -------------------------------------------------------------------------------- /components/rate/index.js: -------------------------------------------------------------------------------- 1 | import FMRate from './src/index.vue'; 2 | 3 | FMRate.install = function (Vue) { 4 | Vue.component(FMRate.name, FMRate); 5 | }; 6 | 7 | export default FMRate; 8 | -------------------------------------------------------------------------------- /components/rate/src/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 99 | -------------------------------------------------------------------------------- /components/select/index.js: -------------------------------------------------------------------------------- 1 | import FMSelect from './src/index.vue'; 2 | 3 | FMSelect.install = function (Vue) { 4 | Vue.component(FMSelect.name, FMSelect); 5 | }; 6 | 7 | export default FMSelect; 8 | -------------------------------------------------------------------------------- /components/select/src/option.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 57 | -------------------------------------------------------------------------------- /components/switch/index.js: -------------------------------------------------------------------------------- 1 | import FMSwitch from './src/index.vue'; 2 | 3 | FMSwitch.install = function (Vue) { 4 | Vue.component(FMSwitch.name, FMSwitch); 5 | }; 6 | 7 | export default FMSwitch; 8 | -------------------------------------------------------------------------------- /components/switch/src/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 82 | -------------------------------------------------------------------------------- /components/tab-panel/index.js: -------------------------------------------------------------------------------- 1 | import FMTabPanel from './src/index.vue'; 2 | 3 | FMTabPanel.install = function (Vue) { 4 | Vue.component(FMTabPanel.name, FMTabPanel); 5 | }; 6 | 7 | export default FMTabPanel; 8 | -------------------------------------------------------------------------------- /components/tab-panel/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /components/tabs/index.js: -------------------------------------------------------------------------------- 1 | import FMTabs from './src/index.vue'; 2 | 3 | FMTabs.install = function (Vue) { 4 | Vue.component(FMTabs.name, FMTabs); 5 | }; 6 | 7 | export default FMTabs; 8 | -------------------------------------------------------------------------------- /components/tabs/src/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 121 | -------------------------------------------------------------------------------- /components/toast/index.js: -------------------------------------------------------------------------------- 1 | import FMToast from './src/main.js'; 2 | export default FMToast; 3 | -------------------------------------------------------------------------------- /components/toast/src/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | -------------------------------------------------------------------------------- /components/toast/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import FMToast from './index.vue'; 3 | 4 | const ToastConstructor = Vue.extend(FMToast); 5 | 6 | let instance = null; 7 | let closeTimeoutEvent = null; 8 | 9 | ToastConstructor.prototype.remove = function () { 10 | this.shown = false; 11 | if (this.$el.parentNode) { 12 | this.$el.parentNode.removeChild(this.$el); 13 | } 14 | instance = null; 15 | }; 16 | 17 | const getAnInstance = (props) => { 18 | if (instance) { 19 | instance.remove(); 20 | instance = null; 21 | // return instance; 22 | } 23 | 24 | return new ToastConstructor({ 25 | propsData: props, 26 | el: document.createElement('div') 27 | }); 28 | }; 29 | 30 | const Toast = function (opts) { 31 | if (Vue.prototype.$isServer) { 32 | return; 33 | } 34 | 35 | if (typeof opts === 'string') { 36 | opts = { 37 | message: opts 38 | }; 39 | } 40 | 41 | if (typeof opts.onClose === 'function') { 42 | const close = opts.onClose; 43 | opts.onClose = () => { 44 | instance.remove(); 45 | close(); 46 | }; 47 | } else { 48 | opts.onClose = () => { 49 | instance.remove(); 50 | clearTimeout(closeTimeoutEvent); 51 | }; 52 | } 53 | 54 | instance = getAnInstance(opts); 55 | document.body.appendChild(instance.$el); 56 | instance.shown = true; 57 | 58 | // 自动销毁 59 | if (instance.duration > 0) { 60 | clearTimeout(closeTimeoutEvent); 61 | closeTimeoutEvent = setTimeout(() => { 62 | instance && instance.remove(); 63 | }, instance.duration); 64 | } 65 | }; 66 | 67 | export default Toast; 68 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | dev: { 5 | env: 'development', 6 | assetsRoot: path.resolve(__dirname, '../docs/dist'), 7 | assetsPublicPath: '/fm-vue-ui/dist/', 8 | contentBase: path.resolve(__dirname, '../docs/dist'), 9 | port: 3000 10 | }, 11 | build: { 12 | env: 'production', 13 | assetsRoot: path.resolve(__dirname, '../docs/dist'), 14 | assetsPublicPath: '/fm-vue-ui/dist/' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /docs/common.less: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, html { 6 | margin: 0 auto; 7 | width: 1024px; 8 | font: 14px/1.4 Microsoft Yahei, Tahoma, "PingFang SC", Helvetica, Arial, sans-serif; 9 | background-color: #f4f4f4; 10 | } 11 | 12 | ul, ol { 13 | list-style: none; 14 | padding: 0; 15 | margin: 0 16 | } 17 | 18 | a { 19 | text-decoration: none 20 | } 21 | 22 | h1, h2, h3, h4, h5, h6 { 23 | cursor: pointer; 24 | a { 25 | visibility: hidden; 26 | color: #ff6200 27 | } 28 | &:hover { 29 | a { 30 | visibility: visible; 31 | } 32 | } 33 | } 34 | 35 | .docs-content-wrap { 36 | position: relative; 37 | padding-top: 80px; 38 | } 39 | 40 | .docs-content { 41 | display: inline-block; 42 | height: 100%; 43 | width: 788px; 44 | margin-left: 185px; 45 | padding: 15px; 46 | background: #fff; 47 | & > p { 48 | margin-left: 15px; 49 | } 50 | ol { 51 | list-style: decimal; 52 | margin-left: 30px; 53 | } 54 | } 55 | 56 | .table { 57 | border-collapse: collapse; 58 | width: 100%; 59 | background-color: #fff; 60 | font-size: 14px; 61 | margin-bottom: 25px; 62 | line-height: 1.5em; 63 | margin-left: 15px; 64 | tr { 65 | border-bottom: 1px solid #e1e2e6; 66 | height: 45px; 67 | } 68 | } 69 | 70 | code { 71 | background-color: #f9fafc; 72 | padding: 0 4px; 73 | border: 1px solid #eaeefb; 74 | border-radius: 4px; 75 | } 76 | 77 | .hljs { 78 | line-height: 1.8; 79 | font-family: Menlo, Monaco, Consolas, Courier, monospace; 80 | font-size: 12px; 81 | padding: 18px 24px; 82 | background-color: #fafafa; 83 | border: solid 1px #eaeefb; 84 | margin-bottom: 25px; 85 | border-radius: 4px; 86 | -webkit-font-smoothing: auto; 87 | } 88 | 89 | blockquote { 90 | background: #fafafa; 91 | margin-left: 15px; 92 | p { 93 | padding: 5px 10px; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /docs/components/demo-block.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 61 | -------------------------------------------------------------------------------- /docs/components/header.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 39 | -------------------------------------------------------------------------------- /docs/components/side-nav.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 40 | 41 | -------------------------------------------------------------------------------- /docs/dist/1.37c351b9.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/1.37c351b9.js.gz -------------------------------------------------------------------------------- /docs/dist/10.4be66b11.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/10.4be66b11.js.gz -------------------------------------------------------------------------------- /docs/dist/12.89eeb768.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/12.89eeb768.js.gz -------------------------------------------------------------------------------- /docs/dist/13.154fba12.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/13.154fba12.js.gz -------------------------------------------------------------------------------- /docs/dist/14.8242b6a4.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/14.8242b6a4.js.gz -------------------------------------------------------------------------------- /docs/dist/15.2d69444c.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/15.2d69444c.js.gz -------------------------------------------------------------------------------- /docs/dist/16.6b4129ec.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/16.6b4129ec.js.gz -------------------------------------------------------------------------------- /docs/dist/17.08c2c7cb.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([17],{248:function(t,a,e){t.exports=e(268)},268:function(t,a,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var l={render:function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("section",[t._m(0),t._v(" "),e("p",[t._v("在有限空间内,循环播放同一类型的图片、文字等内容")]),t._v(" "),t._m(1),t._v(" "),e("p",[t._v("适用广泛的基础用法。")]),t._v(" "),e("docs-demo-block",[e("div",{staticClass:"source",attrs:{slot:"source"},slot:"source"},[e("fm-carousel",{attrs:{list:t.list}})],1),t._v(" "),e("p",[t._v("Click 指示器触发")]),t._v(" "),e("pre",{pre:!0},[e("code",{pre:!0,attrs:{class:"hljs language-html"}},[e("span",{pre:!0,attrs:{class:"hljs-tag"}},[t._v("<"),e("span",{pre:!0,attrs:{class:"hljs-name"}},[t._v("fm-carousel")]),t._v(" "),e("span",{pre:!0,attrs:{class:"hljs-attr"}},[t._v(":list")]),t._v("="),e("span",{pre:!0,attrs:{class:"hljs-string"}},[t._v('"list"')]),t._v(">")]),e("span",{pre:!0,attrs:{class:"hljs-tag"}},[t._v("")]),t._v("\n")])])]),t._v(" "),t._m(2),t._v(" "),t._m(3)],1)},staticRenderFns:[function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("h2",{attrs:{id:"carousel-zou-ma-deng"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#carousel-zou-ma-deng","aria-hidden":"true"}},[t._v("¶")]),t._v(" Carousel 走马灯")])},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("h3",{attrs:{id:"ji-chu-yong-fa"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#ji-chu-yong-fa","aria-hidden":"true"}},[t._v("¶")]),t._v(" 基础用法")])},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("h3",{attrs:{id:"shu-xing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#shu-xing","aria-hidden":"true"}},[t._v("¶")]),t._v(" 属性")])},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("table",{staticClass:"table"},[e("thead",[e("tr",[e("th",{staticStyle:{"text-align":"left"}},[t._v("参数")]),t._v(" "),e("th",{staticStyle:{"text-align":"left"}},[t._v("说明")]),t._v(" "),e("th",{staticStyle:{"text-align":"left"}},[t._v("类型")]),t._v(" "),e("th",{staticStyle:{"text-align":"left"}},[t._v("可选值")]),t._v(" "),e("th",{staticStyle:{"text-align":"left"}},[t._v("默认值")])])]),t._v(" "),e("tbody",[e("tr",[e("td",{staticStyle:{"text-align":"left"}},[t._v("delay")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("轮播切换时间")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("number")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("-")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("3000")])]),t._v(" "),e("tr",[e("td",{staticStyle:{"text-align":"left"}},[t._v("list")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("轮播图片信息,默认值为,image地址,url地址,title名组成的数组对象")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("array")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("-")]),t._v(" "),e("td",{staticStyle:{"text-align":"left"}},[t._v("[{image: '', url: '', title: ''}]")])])])])}]},s=e(4)({data:function(){return{list:[{image:"http://ww2.sinaimg.cn/large/0060lm7Tly1fm9dc8l3pqj30dw09aq4g.jpg",url:"http://ww2.sinaimg.cn/large/0060lm7Tly1fm9dc8l3pqj30dw09aq4g.jpg",title:"家有萌宠"},{image:"http://ww4.sinaimg.cn/large/0060lm7Tly1fm9dca7vthj30dw09a0tw.jpg",url:"http://ww4.sinaimg.cn/large/0060lm7Tly1fm9dca7vthj30dw09a0tw.jpg",title:"家有萌宠"},{image:"http://ww2.sinaimg.cn/large/0060lm7Tly1fm9dcac9a5j30dw09adhc.jpg",url:"http://ww2.sinaimg.cn/large/0060lm7Tly1fm9dcac9a5j30dw09adhc.jpg",title:"家有萌宠"},{image:"http://ww3.sinaimg.cn/large/0060lm7Tly1fm9dca63euj30dw09a76n.jpg",url:"http://ww3.sinaimg.cn/large/0060lm7Tly1fm9dca63euj30dw09a76n.jpg",title:"家有萌宠"}]}}},l,!1,null,null,null);a.default=s.exports}}); 2 | //# sourceMappingURL=17.08c2c7cb.js.map -------------------------------------------------------------------------------- /docs/dist/17.08c2c7cb.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./examples/carousel.md","webpack:///examples/carousel.vue","webpack:///./examples/carousel.md?a05a","webpack:///./examples/carousel.md?7087"],"names":["module","exports","__webpack_require__","markdown_compilerraw_examples_carousel","render","_vm","this","_h","$createElement","_c","_self","_m","_v","staticClass","attrs","slot","list","pre","class","staticRenderFns","id","href","aria-hidden","staticStyle","text-align","Component","normalizeComponent","data","image","url","title","__webpack_exports__"],"mappings":"uCAAAA,EAAAC,QAAiBC,EAAQ,wFC0CzB,ICvCeC,EADf,CAAiBC,OAFjB,WAA0B,IAAAC,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,WAAAJ,EAAAM,GAAA,GAAAN,EAAAO,GAAA,KAAAH,EAAA,KAAAJ,EAAAO,GAAA,8BAAAP,EAAAO,GAAA,KAAAP,EAAAM,GAAA,GAAAN,EAAAO,GAAA,KAAAH,EAAA,KAAAJ,EAAAO,GAAA,gBAAAP,EAAAO,GAAA,KAAAH,EAAA,mBAAAA,EAAA,OAAqMI,YAAA,SAAAC,MAAA,CAA4BC,KAAA,UAAgBA,KAAA,UAAe,CAAAN,EAAA,eAAoBK,MAAA,CAAOE,KAAAX,EAAAW,SAAiB,GAAAX,EAAAO,GAAA,KAAAH,EAAA,KAAAJ,EAAAO,GAAA,iBAAAP,EAAAO,GAAA,KAAAH,EAAA,OAAwEQ,KAAA,GAAS,CAAAR,EAAA,QAAaQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,uBAA8B,CAAAT,EAAA,QAAaQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,aAAoB,CAAAb,EAAAO,GAAA,KAAAH,EAAA,QAAyBQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,cAAqB,CAAAb,EAAAO,GAAA,iBAAAP,EAAAO,GAAA,KAAAH,EAAA,QAAiDQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,cAAqB,CAAAb,EAAAO,GAAA,WAAAP,EAAAO,GAAA,KAAAH,EAAA,QAA2CQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,gBAAuB,CAAAb,EAAAO,GAAA,YAAAP,EAAAO,GAAA,OAAAH,EAAA,QAAgDQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,aAAoB,CAAAb,EAAAO,GAAA,MAAAH,EAAA,QAA0BQ,KAAA,EAAAH,MAAA,CAAgBI,MAAA,cAAqB,CAAAb,EAAAO,GAAA,iBAAAP,EAAAO,GAAA,OAAAP,EAAAO,GAAA,YAAAP,EAAAO,GAAA,KAAAP,EAAAM,GAAA,GAAAN,EAAAO,GAAA,KAAAP,EAAAM,GAAA,QAE16BQ,gBADjB,YAAoC,IAAAd,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,MAAgBK,MAAA,CAAOM,GAAA,yBAA6B,CAAAX,EAAA,KAAUI,YAAA,gBAAAC,MAAA,CAAmCO,KAAA,wBAAAC,cAAA,SAAqD,CAAAjB,EAAAO,GAAA,OAAAP,EAAAO,GAAA,oBAA0C,WAAc,IAAAP,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,MAAgBK,MAAA,CAAOM,GAAA,mBAAuB,CAAAX,EAAA,KAAUI,YAAA,gBAAAC,MAAA,CAAmCO,KAAA,kBAAAC,cAAA,SAA+C,CAAAjB,EAAAO,GAAA,OAAAP,EAAAO,GAAA,YAAkC,WAAc,IAAAP,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,MAAgBK,MAAA,CAAOM,GAAA,aAAiB,CAAAX,EAAA,KAAUI,YAAA,gBAAAC,MAAA,CAAmCO,KAAA,YAAAC,cAAA,SAAyC,CAAAjB,EAAAO,GAAA,OAAAP,EAAAO,GAAA,UAAgC,WAAc,IAAAP,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BC,EAAAJ,EAAAK,MAAAD,IAAAF,EAAwB,OAAAE,EAAA,SAAmBI,YAAA,SAAoB,CAAAJ,EAAA,SAAAA,EAAA,MAAAA,EAAA,MAAgCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,QAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAsCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,QAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAsCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,QAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAsCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,SAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAuCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,aAAAP,EAAAO,GAAA,KAAAH,EAAA,SAAAA,EAAA,MAAAA,EAAA,MAAgEc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,WAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAyCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,YAAAP,EAAAO,GAAA,KAAAH,EAAA,MAA0Cc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,YAAAP,EAAAO,GAAA,KAAAH,EAAA,MAA0Cc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,OAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAqCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,YAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAAA,EAAA,MAAmDc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,UAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAwCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,6CAAAP,EAAAO,GAAA,KAAAH,EAAA,MAA2Ec,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,WAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAyCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,OAAAP,EAAAO,GAAA,KAAAH,EAAA,MAAqCc,YAAA,CAAaC,aAAA,SAAqB,CAAAnB,EAAAO,GAAA,gDCaxgEa,EAdyBvB,EAAQ,EAcjCwB,CF4BA,CACAC,KADA,WAEA,OACAX,KAAA,CACA,CACAY,MAAA,mEACAC,IAAA,mEACAC,MAAA,QAEA,CACAF,MAAA,mEACAC,IAAA,mEACAC,MAAA,QAEA,CACAF,MAAA,mEACAC,IAAA,mEACAC,MAAA,QAEA,CACAF,MAAA,mEACAC,IAAA,mEACAC,MAAA,YEhDE3B,GATF,EAEA,KAEA,KAEA,MAUe4B,EAAA,QAAAN,EAAiB","file":"17.08c2c7cb.js","sourceRoot":""} -------------------------------------------------------------------------------- /docs/dist/2.d39ad9e2.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/2.d39ad9e2.js.gz -------------------------------------------------------------------------------- /docs/dist/3.2d5ecdc4.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/3.2d5ecdc4.js.gz -------------------------------------------------------------------------------- /docs/dist/4.b1fc655a.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/4.b1fc655a.js.gz -------------------------------------------------------------------------------- /docs/dist/5.7b5d6514.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([5],{264:function(e,n,r){e.exports=r(292)},292:function(e,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var o={render:function(){var e=this,n=e.$createElement;e._self._c;return e._m(0)},staticRenderFns:[function(){var e=this,n=e.$createElement,r=e._self._c||n;return r("section",[r("h2",{attrs:{id:"guo-ji-hua"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#guo-ji-hua","aria-hidden":"true"}},[e._v("¶")]),e._v(" 国际化")]),e._v(" "),r("p",[r("code",{pre:!0},[e._v("fm-vue-ui")]),e._v(" 目前仅支持两种语言: "),r("code",{pre:!0},[e._v("zh-CN")]),e._v("、"),r("code",{pre:!0},[e._v("en-US")]),e._v(" 和 "),r("code",{pre:!0},[e._v("zh-HK")]),e._v(",默认使用 "),r("code",{pre:!0},[e._v("zh-CN")]),e._v("。")]),e._v(" "),r("blockquote",[r("p",[e._v("我们所有的项目都是需要支持国际化的")])]),e._v(" "),r("p",[e._v("如果需要设置其它语言,配置示例如下:")]),e._v(" "),r("pre",{pre:!0},[r("code",{pre:!0,attrs:{class:"hljs language-html"}},[e._v("// 项目的 i18n 配置 src/i18n/index.js\nimport Vue from 'vue';\nimport VueI18n from 'vue-i18n'; // 7.x\nimport en from './en-US';\nimport cn from './zh-CN';\n\nVue.use(VueI18n);\n\nexport default new VueI18n({\n locale: 'zh-CN',\n messages: {\n 'en-US': en,\n 'zh-CN': cn\n }\n});\n\n// page/index.js\nimport Vue from 'vue';\nimport FMUI from 'fm-vue-ui';\n\nimport i18n from './i18n/index';\n\nVue.use(FMUI, {\n lang: i18n.locale\n});\n")])]),e._v(" "),r("p",[e._v("如果是按需引入:")]),e._v(" "),r("pre",{pre:!0},[r("code",{pre:!0,attrs:{class:"hljs language-html"}},[e._v("// 按需引入\nimport Vue from 'vue'\nimport { Button } from 'fm-vue-ui'\nimport locale from 'fm-vue-ui/lib/locale/index';\n\nlocale.use(i18n.locale);\nVue.use(Button); // 然后安装组件\n")])]),e._v(" "),r("p",[e._v("组件内的开发需要考虑国际化文案时,使用案例请参考 "),r("code",{pre:!0},[e._v("Dialog")]),e._v("。")])])}]},t=r(4)(null,o,!1,null,null,null);n.default=t.exports}}); 2 | //# sourceMappingURL=5.7b5d6514.js.map -------------------------------------------------------------------------------- /docs/dist/5.7b5d6514.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./pages/i18n.md","webpack:///./pages/i18n.md?d9bc","webpack:///./pages/i18n.md?58e6"],"names":["module","exports","__webpack_require__","i18n","render","_vm","this","_h","$createElement","_self","_c","_m","staticRenderFns","attrs","id","staticClass","href","aria-hidden","_v","pre","class","Component","normalizeComponent","__webpack_exports__"],"mappings":"sCAAAA,EAAAC,QAAiBC,EAAQ,wFCAzB,IAGeC,EADf,CAAiBC,OAFjB,WAA0B,IAAAC,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BH,EAAAI,MAAAC,GAAwB,OAAAL,EAAAM,GAAA,IAExEC,gBADjB,YAAoC,IAAAP,EAAAC,KAAaC,EAAAF,EAAAG,eAA0BE,EAAAL,EAAAI,MAAAC,IAAAH,EAAwB,OAAAG,EAAA,WAAAA,EAAA,MAA8BG,MAAA,CAAOC,GAAA,eAAmB,CAAAJ,EAAA,KAAUK,YAAA,gBAAAF,MAAA,CAAmCG,KAAA,cAAAC,cAAA,SAA2C,CAAAZ,EAAAa,GAAA,OAAAb,EAAAa,GAAA,UAAAb,EAAAa,GAAA,KAAAR,EAAA,KAAAA,EAAA,QAAgES,KAAA,GAAS,CAAAd,EAAAa,GAAA,eAAAb,EAAAa,GAAA,gBAAAR,EAAA,QAA0DS,KAAA,GAAS,CAAAd,EAAAa,GAAA,WAAAb,EAAAa,GAAA,KAAAR,EAAA,QAA2CS,KAAA,GAAS,CAAAd,EAAAa,GAAA,WAAAb,EAAAa,GAAA,OAAAR,EAAA,QAA6CS,KAAA,GAAS,CAAAd,EAAAa,GAAA,WAAAb,EAAAa,GAAA,UAAAR,EAAA,QAAgDS,KAAA,GAAS,CAAAd,EAAAa,GAAA,WAAAb,EAAAa,GAAA,OAAAb,EAAAa,GAAA,KAAAR,EAAA,cAAAA,EAAA,KAAAL,EAAAa,GAAA,yBAAAb,EAAAa,GAAA,KAAAR,EAAA,KAAAL,EAAAa,GAAA,wBAAAb,EAAAa,GAAA,KAAAR,EAAA,OAAgLS,KAAA,GAAS,CAAAT,EAAA,QAAaS,KAAA,EAAAN,MAAA,CAAgBO,MAAA,uBAA8B,CAAAf,EAAAa,GAAA,idAAidb,EAAAa,GAAA,KAAAR,EAAA,KAAAL,EAAAa,GAAA,cAAAb,EAAAa,GAAA,KAAAR,EAAA,OAAwES,KAAA,GAAS,CAAAT,EAAA,QAAaS,KAAA,EAAAN,MAAA,CAAgBO,MAAA,uBAA8B,CAAAf,EAAAa,GAAA,sLAA4Kb,EAAAa,GAAA,KAAAR,EAAA,KAAAL,EAAAa,GAAA,6BAAAR,EAAA,QAAsFS,KAAA,GAAS,CAAAd,EAAAa,GAAA,YAAAb,EAAAa,GAAA,YCY9nDG,EAbyBnB,EAAQ,EAajCoB,CAXA,KAaEnB,GATF,EAEA,KAEA,KAEA,MAUeoB,EAAA,QAAAF,EAAiB","file":"5.7b5d6514.js","sourceRoot":""} -------------------------------------------------------------------------------- /docs/dist/7.70d5d555.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/7.70d5d555.js.gz -------------------------------------------------------------------------------- /docs/dist/app.1be2bc04.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/app.1be2bc04.js.gz -------------------------------------------------------------------------------- /docs/dist/vendor.01d16740.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/dist/vendor.01d16740.js.gz -------------------------------------------------------------------------------- /docs/entry.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css'; 2 | import 'highlight.js/styles/color-brewer.css'; 3 | import 'github-markdown-css'; 4 | 5 | import './common.less'; 6 | 7 | import 'babel-polyfill'; 8 | 9 | import Vue from 'vue'; 10 | import VueRouter from 'vue-router'; 11 | 12 | import FMUI from 'main/index'; 13 | // import fmutils from 'main/utils/index'; 14 | import 'fm-vue-ui/theme-default/src/index.less'; 15 | // import locale from 'main/locale/index'; 16 | 17 | import routes from './routes.config'; 18 | import entry from './entry.vue'; 19 | import DocsHeader from './components/header.vue'; 20 | import DocsSideNav from './components/side-nav.vue'; 21 | import DocsDemoBlock from './components/demo-block.vue'; 22 | 23 | import i18n from './i18n/index'; 24 | // Vue.prototype.$utils = fmutils; 25 | // locale.use(i18n.locale); 26 | Vue.use(FMUI, { 27 | lang: i18n.locale 28 | }); 29 | // Vue.use(Button); 30 | // Vue.use(Input); 31 | 32 | Vue.use(VueRouter); 33 | Vue.component('docs-demo-block', DocsDemoBlock); 34 | Vue.component('docs-header', DocsHeader); 35 | Vue.component('docs-side-nav', DocsSideNav); 36 | 37 | const router = new VueRouter({ 38 | mode: 'history', 39 | base: '/fm-vue-ui/', 40 | fallback: true, 41 | routes 42 | }); 43 | 44 | new Vue({ 45 | render: h => h(entry), 46 | router, 47 | i18n 48 | }).$mount('#app'); 49 | -------------------------------------------------------------------------------- /docs/entry.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/button.md: -------------------------------------------------------------------------------- 1 | 6 | 15 | ## Button 按钮 16 | 常用的操作按钮。 17 | 18 | ### 基础用法 19 | 20 | 基础的按钮用法。 21 | 22 | :::demo 使用 `type` `radius` 和 `size` 属性来定义 Button 的样式。 23 | 24 | ```html 25 |
26 | 默认按钮 27 | 主要按钮 28 | 幽灵按钮 29 | 小圆角按钮 30 |
31 | 32 |
33 | small(默认) 34 | medium 35 | large 36 | xlarge 37 |
38 | ``` 39 | ::: 40 | 41 | ### 深色模式 42 | 43 | 按钮可以切换成深色模式。 44 | 45 | :::demo 可以使用 `invert` 属性来决定是否使用深色模式,它接受一个Boolean值。 46 | 47 | ```html 48 |
49 | 默认按钮 50 | 主要按钮 51 | 幽灵按钮 52 |
53 | ``` 54 | ::: 55 | ### loading状态 56 | 57 | 加载中状态。 58 | 59 | :::demo 可以使用 `loading` 属性来定义按钮是否是正在加载状态,它接受一个`Boolean`值。可以通过传递`loadingText`来直接显示 也可以自定义slot(不带loading图标) 60 | 61 | ```html 62 | Tap to loading 63 | Tap to loading 64 | Tap to loading 65 | Tap to loading 66 | 67 | 70 | 71 | ``` 72 | ::: 73 | 74 | ### 禁用状态 75 | 76 | 按钮不可用状态。 77 | 78 | :::demo 可以使用 `disabled` 属性来定义按钮是否可用,它接受一个Boolean值。 79 | 80 | ```html 81 | 默认按钮 82 | 主要按钮 83 | 幽灵按钮 84 | ``` 85 | ::: 86 | 87 | ### 属性 88 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 89 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 90 | | type | 类型 | string | success/ghost/default | default | 91 | | size | 大小 | string | small/large | small | 92 | | invert | 深色模式 | boolean | — | false | 93 | | disabled | 是否禁用 | boolean | — | false | 94 | | radius | 是否启用大圆角模式 | boolean | — | true | 95 | | loading | 是否loading | boolean | — | false | 96 | | loadingText | 加载中显示的文案 | string | — | | 97 | -------------------------------------------------------------------------------- /docs/examples/carousel.md: -------------------------------------------------------------------------------- 1 | 31 | 32 | ## Carousel 走马灯 33 | 34 | 在有限空间内,循环播放同一类型的图片、文字等内容 35 | 36 | ### 基础用法 37 | 38 | 适用广泛的基础用法。 39 | 40 | :::demo Click 指示器触发 41 | 42 | ```html 43 | 44 | ``` 45 | ::: 46 | 47 | ### 属性 48 | 49 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 50 | | :-------------- | :-------------- | :---------- | :--------------------------- | :-------- | 51 | | delay | 轮播切换时间 | number | - | 3000 | 52 | | list | 轮播图片信息,默认值为,image地址,url地址,title名组成的数组对象 | array | - | [{image: '', url: '', title: ''}] | 53 | -------------------------------------------------------------------------------- /docs/examples/checkbox.md: -------------------------------------------------------------------------------- 1 | 13 | ## Checkbox 多选框 14 | 一组备选项中进行多选。 15 | 16 | ### 基础用法 17 | 18 | 19 | :::demo 基础的多选框用法。 20 | 21 | ```html 22 | 复选框 23 | 复选框 24 | 复选框 25 | 26 | 复选框 27 | 复选框 28 | 复选框 29 | 30 | 39 | ``` 40 | ::: 41 | 42 | ### 禁用状态 43 | 多选框的不可用状态 44 | 45 | :::demo 设置 `disabled` 属性即可。 46 | 47 | ```html 48 | 不可用 49 | 选中不可用 50 | 51 | 62 | ``` 63 | ::: 64 | 65 | ### 尺寸 66 | 指定多选框的大小 67 | 68 | ::: demo 可通过 `size` 属性指定输入框的尺寸,默认大小是 `small` 69 | ```html 70 | 71 | 72 | 73 | ``` 74 | ::: 75 | 76 | ### 属性 77 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 78 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 79 | | value | 绑定值 | string/number | - | - | 80 | | size | 输入框尺寸 | string | large/medium/small | small | 81 | | disabled | 是否禁用 | boolean | — | false | 82 | | name | 原生 name 属性 | string | — | - | 83 | | label | 选中状态的值 | string/boolean | — | - | 84 | 85 | ### 事件 86 | | 事件名称 | 说明 | 回调参数 | 87 | | :---------- | :-------------- | :---------- | 88 | | change | 当绑定值变化时触发的事件 | (value: string \| number, event: Event) | 89 | -------------------------------------------------------------------------------- /docs/examples/collapse.md: -------------------------------------------------------------------------------- 1 | 36 | 45 | ## Collapse 折叠面板 46 | 通过折叠面板收纳内容区域。 47 | 48 | ### 基础用法 49 | 同时只能展开一个面板。具体见个人中心-个人设置页面。 50 | 51 | :::demo 同时只能展开一个面板。 52 | 53 | ```html 54 | 55 | 56 |
个人设置-上传头像
57 |
58 | 59 |
个人设置-基本资料
60 |
61 |
62 | ``` 63 | ::: 64 | 65 | 也可以按照个人中心-交易设置页面处理: 66 | 67 | :::demo 可以设置简要说明、右侧区域的展开/收缩文本。 68 | 69 | ```html 70 | 71 | 72 |
73 |

交易账号

74 |

展开/收缩文本需要以空格分开

75 |
76 |
77 | 78 |
79 |

快速下单

80 |

info 用于设置简要说明

81 |
82 |
83 | 84 |
85 |

快速平仓

86 |

设置右侧 icon:icon 的类以空格分开

87 |

类名包含 hover 的是鼠标 hover 上去添加的类,包含 active 的是面板展开时添加的类,带 hover & active 的是展开时鼠标 hover 时添加的类

88 |
89 |
90 |
91 | ``` 92 | ::: 93 | 94 | ### 手风琴效果 95 | 展开多个面板。 96 | 97 | :::demo 设置 `accordion` 为 true,可实现手风琴效果。 98 | 99 | ```html 100 | 101 | 102 |
个人设置-上传头像
103 |
104 | 105 |
个人设置-基本资料
106 |
107 |
108 | ``` 109 | ::: 110 | 111 | ### 默认展开面板 112 | :::demo 方法调用: `this.$refs.list.showCollapseItem()`。 113 | 114 | ```html 115 | 116 | 117 |
默认展开的面板
118 |
119 |
120 | ``` 121 | ::: 122 | 123 | ### Collapse 属性 124 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 125 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 126 | | accordion | 是否启用手风琴效果 | boolean | - | false | 127 | 128 | ### Collapse Item 属性 129 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 130 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 131 | | title | 面板标题 | string | - | '' | 132 | | info | 标题辅助说明 | string | - | '' | 133 | | act-text | 空格分开的文本 | string | - | '' | 134 | | icon-classes | icon 的类名,空格分开 | string | - | '' | -------------------------------------------------------------------------------- /docs/examples/dialog.md: -------------------------------------------------------------------------------- 1 | 30 | ## Dialog 对话框 31 | 在保留当前页面状态的情况下,告知用户并承载相关操作。 32 | 33 | ### 基本用法 34 | 35 | Dialog 弹出一个对话框,适合需要定制性更大的场景。 36 | 37 | :::demo Dialog 以 `$fmdialog` 属性挂载在 Vue Components 上, 可以在组件内通过 `this.$fmdialog` 的方式直接调用。 38 | 39 | ```html 40 | 不带Icon的Dialog 41 | 成功 42 | 信息 43 | 警告 44 | 错误 45 | 46 | 58 | ``` 59 | ::: 60 | 61 | 对话框弹出默认是没有后层遮罩的, 如果需要遮罩, 可以设置 `mask` 属性. 62 | 63 | :::demo `mask` 属性是一个 `Boolean` 值, 默认是 `false`. 64 | 65 | ```html 66 | 显示Mask 67 | 68 | 81 | ``` 82 | ::: 83 | 84 | 对于对话框需要和用户进行交互的, 不需要指定 `type` 值. 85 | 86 | :::demo `isSingle` 属性是一个 `Boolean` 值, 默认是 `false`, 表示是否只显示确认按钮. 显示带交互行为的对话框时, 需要传入 `onConfirm` 回调. 87 | 88 | ```html 89 | 两个按钮 90 | 一个按钮 91 | 92 | 105 | ``` 106 | ::: 107 | 108 | ### 属性 109 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 110 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 111 | | mask | 是否显示遮罩 | boolean | - | false | 112 | | classes | 用于 Dialog 的类名 | string | - | '' | 113 | | title | Dialog 的标题 | string | — | '请确认' | 114 | | type | 类型 | string | success/failure | '' | 115 | | duration | 自动关闭的时间 | number | - | 1500 | 116 | | message | 消息体 | string | - | '' | 117 | | cancelBtnText | 取消按钮的文本 | string | - | '取消' | 118 | | confirmBtnText | 确认按钮的文本 | string | - | '确认' | 119 | | isSingle | 是否显示取消按钮 | boolean | - | false | 120 | | onCancel | 点击取消按钮时的回调 | function | - | - | 121 | | onConfirm | 点击确认按钮时的回调 | function | - | - | 122 | -------------------------------------------------------------------------------- /docs/examples/input.md: -------------------------------------------------------------------------------- 1 | 17 | 22 | ## Input 输入框 23 | 通过鼠标或键盘输入字符 24 | 25 | ### 基础用法 26 | 27 | ::: demo 基础的输入框用法。 28 | ```html 29 | 30 | 39 | ``` 40 | ::: 41 | 42 | ### 禁用状态 43 | ::: demo 通过 `disabled` 属性指定是否禁用 input 组件 44 | ```html 45 | 46 | ``` 47 | ::: 48 | 49 | ### 尺寸 50 | ::: demo 可通过 `size` 属性指定输入框的尺寸,默认大小是 `medium` 51 | ```html 52 | 53 | 54 | 55 | 56 | ``` 57 | ::: 58 | 59 | ### 可清空 60 | 61 | ::: demo 可通过 `clearable` 属性得到一个可清空的输入框 62 | ```html 63 | 64 | 73 | ``` 74 | ::: 75 | 76 | ### 复合型输入框 77 | 可前置或后置元素,一般为标签或按钮 78 | 79 | :::demo 可通过 slot 来指定在 input 中前置或者后置内容 80 | ```html 81 |
82 | 83 | 84 | 85 |
86 |
87 | 88 | 89 | 90 |
91 |
92 | 93 | 94 | 95 | 96 |
97 | ``` 98 | ::: 99 | 100 | ### 属性 101 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 102 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 103 | | value | 绑定值 | string/number | - | - | 104 | | size | 输入框尺寸 | string | large/medium/small/mini | medium | 105 | | disabled | 是否禁用 | boolean | — | false | 106 | | placeholder | 输入框占位文本 | string | — | 请输入/Please input | 107 | | clearable | 是否可清空 | boolean | — | false | 108 | 109 | ### Slots 110 | | name | 说明 | 111 | | :---------- | :-------------- | 112 | | prepend | 输入框前置内容 | 113 | | append | 输入框后置内容 | 114 | 115 | ### 事件 116 | | 事件名称 | 说明 | 回调参数 | 117 | | :---------- | :-------------- | :---------- | 118 | | blur | 在 Input 失去焦点时触发 | (event: Event) | 119 | | focus | 在 Input 获得焦点时触发 | (event: Event) | 120 | | change | 在 Input 值改变时触发 | (value: string \| number) | 121 | 122 | -------------------------------------------------------------------------------- /docs/examples/loading.md: -------------------------------------------------------------------------------- 1 | 8 | ## Loading 加载 9 | 加载数据时显示动效。 10 | 11 | ### 区域加载 12 | 在容器元素中加载数据时显示。 13 | 14 | :::demo 容器元素需要的 `postion` 属性为非 `static`。 15 | 16 | ```html 17 |
18 | 19 |
20 | ``` 21 | ::: 22 | 23 | ### 自定义 24 | 可自定义加载文案。 25 | 26 | :::demo 自定义加载文案。 27 | 28 | ```html 29 |
30 | 31 | 加载中加载中... 32 | 33 |
34 | ``` 35 | ::: 36 | 37 | ### 属性 38 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 39 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 40 | | shown | 是否显示 loading | boolean | - | false | 41 | -------------------------------------------------------------------------------- /docs/examples/notification.md: -------------------------------------------------------------------------------- 1 | 26 | ## Notification 对话框 27 | 从网页顶部弹出一个提示框。 28 | 29 | ### 基本用法 30 | 31 | Notification 弹出一个提示框,适合提醒用户任务操作结果等。 32 | 33 | :::demo Notification 以 `$fmtoast` 属性挂载在 Vue Components 上, 可以在组件内通过 `this.$fmtoast` 的方式直接调用。 34 | 35 | ```html 36 | 成功 37 | 信息 38 | 警告 39 | 错误 40 | 41 | 54 | ``` 55 | ::: 56 | 57 | 可以指定关闭事件 也可以通过传递 `duration: 0` 使得只能用户主动点击才关闭 58 | 59 | :::demo 60 | 61 | ```html 62 | 不自动关闭 63 | 64 | 77 | ``` 78 | ::: 79 | 80 | ### 属性 81 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 82 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 83 | | classes | 用于 Notification 的类名 | string | - | '' | 84 | | type | 类型 | string | success/info/warning/error | '' | 85 | | title | 标题 | string | string | '' | 86 | | duration | 自动关闭的时间 设置为 0 则点击关闭 | number | - | `3000` | 87 | | message | 消息体 | string | - | '' | 88 | | onClose | 点击关闭的事件 | function | - | - | 89 | -------------------------------------------------------------------------------- /docs/examples/radio.md: -------------------------------------------------------------------------------- 1 | 12 | ## Radio 单选框 13 | 在一组备选项中进行单选。 14 | 15 | ### 基础用法 16 | 17 | :::demo 基础的单选框用法,`v-model` 绑定的是 `label` 属性对应的值。 18 | 19 | ```html 20 | 单选框 21 | 单选框 22 | 23 | 32 | ``` 33 | ::: 34 | 35 | ### 禁用状态 36 | 单选框的不可用状态 37 | 38 | :::demo 设置 `disabled` 属性即可。 39 | 40 | ```html 41 | 不可用 42 | 选中不可用 43 | 44 | 53 | ``` 54 | ::: 55 | 56 | ### 尺寸 57 | 指定单选框的大小 58 | 59 | ::: demo 可通过 `size` 属性指定输入框的尺寸,默认大小是 `small` 60 | ```html 61 | 62 | 63 | 64 | ``` 65 | ::: 66 | 67 | ### 属性 68 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 69 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 70 | | value | 绑定值 | string/number | - | - | 71 | | size | 输入框尺寸 | string | large/medium/small | small | 72 | | disabled | 是否禁用 | boolean | — | false | 73 | | name | 原生 name 属性 | string | — | - | 74 | | label | 选中状态的值 | string/boolean | — | - | 75 | 76 | ### 事件 77 | | 事件名称 | 说明 | 回调参数 | 78 | | :---------- | :-------------- | :---------- | 79 | | change | 当绑定值变化时触发的事件 | (value: string \| number, event: Event) | -------------------------------------------------------------------------------- /docs/examples/rate.md: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | ## Rate 评分 20 | 21 | 常用评分组件 22 | 23 | ### 基本用法 24 | 25 | 可点击,进行选择评分。通过传入`colors`可改变选择不同分值时的颜色效果,不传入,则默认不改颜色。 26 | 27 | :::demo 28 | 29 | ```html 30 | 31 | 32 | 33 | 47 | ``` 48 | ::: 49 | 50 | ### 显示分值 51 | 52 | 可选择显示评分。通过传入`score-text`可改变选择不同类型分值,不传入,则默认为`2, 4, 6, 8, 10`。 53 | 54 | :::demo 55 | 56 | ```html 57 | 58 | 59 | 60 | 74 | ``` 75 | ::: 76 | 77 | ### 只读 78 | 79 | 显示评分,不可点击,可选择是否显示分值。 80 | 81 | :::demo `disabled`设置不可点击,`show-text`设置显示分值。 82 | 83 | ```html 84 | 85 | 86 | 87 | 97 | ``` 98 | ::: 99 | 100 | ### 属性 101 | 102 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 103 | | :-------------- | :-------------- | :---------- | :--------------------------- | :-------- | 104 | | max | 最大分值 | number | - | 5 | 105 | | show-text | 是否显示分值 | boolean | - | false | 106 | | score-text | 分值显示类型数组 | array | - | ['2 分', '4 分', '6 分', '8 分', '10 分'] | 107 | | text-color | 分值文字颜色 | string | - | '#ffbe58' | 108 | | colors | icon颜色数组,共有 3 个元素,为 3 个分段所对应的颜色 | array | - | ['ffbe58', 'ffbe58', 'ffbe58'] | 109 | | disabled | 是否为只读 | boolean | - | false | 110 | -------------------------------------------------------------------------------- /docs/examples/rbg.md: -------------------------------------------------------------------------------- 1 | 17 | 18 | ## RadioButtonGroup 按钮组 19 | 20 | ### 基础用法 21 | 22 | ::: demo 基本用法 23 | ```html 24 | 25 | 36 | ``` 37 | ::: 38 | 39 | ### 属性 40 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 41 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 42 | | value | 绑定值 | - | - | - | 43 | | data-source | 用于渲染按钮组 | array | - | [] | 44 | | button-width | 按钮最小宽度 | number | — | 50 | 45 | 46 | 47 | ### 事件 48 | | 事件名称 | 说明 | 回调参数 | 49 | | :---------- | :-------------- | :---------- | 50 | | change | 选项改变时触发 | 选择的 value | 51 | -------------------------------------------------------------------------------- /docs/examples/select.md: -------------------------------------------------------------------------------- 1 | 22 | ## Select 选择器 23 | 当选项过多时,使用下拉菜单展示并选择内容。 24 | 25 | ### 基础用法 26 | 适用广泛的基础单选 27 | 28 | ::: demo `v-model` 的值为当前被选中的 `fm-option` 的 `value` 属性值 29 | ```html 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 53 | ``` 54 | ::: 55 | 56 | ### 禁用状态 57 | ::: demo 通过 `disabled` 属性指定是否禁用 select 组件 58 | ```html 59 | 60 | 61 | 62 | ``` 63 | ::: 64 | 65 | ### Select 属性 66 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 67 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 68 | | value | 绑定值 | string/number/boolean | - | - | 69 | | disabled | 是否禁用 | boolean | — | false | 70 | | placeholder | 输入框占位文本 | string | — | 请选择/Please select | 71 | | emptyText | 无数据时显示的文本 | string | — | 暂无数据/No Data | 72 | 73 | ### Option 属性 74 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 75 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 76 | | value | 选项的值, 若该值为假则与 `label` 相同 | string/number/boolean | - | - | 77 | | label | 选项的标签,若不设置则默认与 `value` 相同 | string/number | — | - | 78 | 79 | ### 事件 80 | | 事件名称 | 说明 | 回调参数 | 81 | | :---------- | :-------------- | :---------- | 82 | | change | 选值改变时触发 | (value: string \| number \| boolean) | -------------------------------------------------------------------------------- /docs/examples/switch.md: -------------------------------------------------------------------------------- 1 | 2 | 14 | ## Switch 开关 15 | 表示两种相互对立的状态间的切换,多用于触发「开/关」。 16 | 17 | ### 基础用法 18 | 基础的开关用法 19 | 20 | :::demo `v-model` 绑定的值是一个 `Boolean` 值。 21 | 22 | ```html 23 | 24 | 25 | 26 | 36 | ``` 37 | ::: 38 | 39 | ### 禁用状态 40 | 开关的不可用状态 41 | 42 | :::demo 设置 `disabled` 属性即可。 43 | 44 | ```html 45 | 46 | 47 | 48 | 58 | ``` 59 | ::: 60 | 61 | ### 属性 62 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 63 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 64 | | value | 绑定值 | boolean | - | false | 65 | | disabled | 是否禁用 | boolean | — | false | 66 | 67 | ### 事件 68 | | 事件名称 | 说明 | 回调参数 | 69 | | :---------- | :-------------- | :---------- | 70 | | change | 当绑定值变化时触发的事件 | (event: Event) | -------------------------------------------------------------------------------- /docs/examples/toast.md: -------------------------------------------------------------------------------- 1 | 23 | ## Toast 对话框 24 | 从网页顶部弹出一个提示框。 25 | 26 | ### 基本用法 27 | 28 | Toast 弹出一个提示框,适合提醒用户任务操作结果等。 29 | 30 | :::demo Toast 以 `$fmtoast` 属性挂载在 Vue Components 上, 可以在组件内通过 `this.$fmtoast` 的方式直接调用。 31 | 32 | ```html 33 | 成功 34 | 信息 35 | 警告 36 | 错误 37 | 38 | 51 | ``` 52 | ::: 53 | 54 | 可以指定关闭事件 也可以通过传递 `duration: 0` 使得只能用户主动点击才关闭 55 | 56 | :::demo 57 | 58 | ```html 59 | 不自动关闭 60 | 61 | 74 | ``` 75 | ::: 76 | 77 | ### 属性 78 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 79 | | :---------- | :-------------- | :---------- | :-------------------------------- | :-------- | 80 | | classes | 用于 Toast 的类名 | string | - | '' | 81 | | type | 类型 | string | success/info/warning/error | '' | 82 | | duration | 自动关闭的时间 设置为 0 则点击关闭 | number | - | `3000` | 83 | | message | 消息体 | string | - | '' | 84 | | onClose | 点击关闭的事件 | function | - | - | 85 | -------------------------------------------------------------------------------- /docs/examples/util.md: -------------------------------------------------------------------------------- 1 | 22 | 23 | ## Utils 工具库 24 | 常用的工具函数。 25 | 26 | ### 基本使用 27 | 在 `main.js` 中写入以下内容: 28 | 29 | ```html 30 | import Vue from 'vue' 31 | import App from './App.vue' 32 | 33 | // 按需引入时需要手动引入 utils,全量引入会挂载在 $fmutils 34 | import fmutils from fm-vue-ui/lib/utils/index'; 35 | Vue.prototype.$fmutils = fmutils; 36 | 37 | new Vue({ 38 | el: '#app', 39 | render: h => h(App) 40 | }) 41 | ``` 42 | 43 | `fmutils` 目前提供的 API 列表如下: 44 | 45 | ```js 46 | export default { 47 | formatDateToStr, // 时间格式化 48 | avatarError, // 设置图像加载失败时的默认图像 49 | // 下面三个是对 Storage 的封装, 均提供 set/get/remove/clear/getAll 五个 api 50 | localStorage, // ==> window.localStorage 51 | sessionStorage, // ==> window.sessionStorage 52 | memoryStorage // ==> 存在内存中的一个 map 对象 53 | }; 54 | ``` 55 | 56 | ### 默认图像 57 | :::demo 用于图像加载失败时显示统一的默认图像。默认图像是 Base64 格式的。也可以指定一个 url。 58 | 59 | ```html 60 | 61 | 62 | 63 | 76 | ``` 77 | ::: 78 | 79 | ### 时间格式化输出 80 | :::demo 接受一个 Date 实例和格式输出。默认的输出格式是 `yyyy-MM-dd`。 81 | 82 | ```html 83 |

默认的当前时间格式:

84 |

默认输出的格式化时间:{{formatDate()}}

85 |

指定格式输出:{{formatDate('yyyy/MM/dd hh:mm:ss')}}

86 |

只输出时间:{{formatDate('hh:mm:ss')}}

87 |

(只有一个 M/m/h/m/s等时)不加0:{{formatDate('hh:mm:s')}}

88 | 89 | 102 | ``` 103 | ::: 104 | 105 | ### Storage 操作 106 | 107 | `localStorage` 和 `sessionStorage` 仅是对 `window.localStorage` 和 `window.sessionStorage` 的一个封装: 108 | 109 | ```js 110 | // 写入的 key 均会有私有前缀 _fm_ 111 | this.$fmutils.localStorage.set('name', 'test'); // 写 112 | this.$fmutils.localStorage.get('name'); // 读 => 'test' 113 | this.$fmutils.localStorage.set('name2', 'test2'); 114 | this.$fmutils.localStorage.getAll(); // 读取所有 => {name: 'test', name2: 'test2'} 115 | this.$fmutils.localStorage.remove('name2'); // 清除指定的 key 116 | this.$fmutils.localStorage.clear(); // 清除所有 key 117 | ``` 118 | 119 | **需要注意的是 `memoryStorage` 只是内存中的一个对象,可用于临时存储应用数据,但不可用于持久化数据。** 120 | -------------------------------------------------------------------------------- /docs/i18n/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "I18n Test" 3 | } -------------------------------------------------------------------------------- /docs/i18n/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueI18n from 'vue-i18n'; 3 | import en from './en-US.json'; 4 | import cn from './zh-CN.json'; 5 | 6 | Vue.use(VueI18n); 7 | 8 | export default new VueI18n({ 9 | locale: 'zh-CN', 10 | fallbackLocale: 'zh-CN', 11 | messages: { 12 | 'en-US': en, 13 | 'zh-CN': cn 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "国际化测试" 3 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | FM Basic Vue Components
-------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/docs/logo.png -------------------------------------------------------------------------------- /docs/nav.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "zh-CN": [ 3 | { 4 | "name": "guide", 5 | "title": "开发指南", 6 | "path": "/index" 7 | }, 8 | { 9 | "name": "log", 10 | "title": "更新日志", 11 | "path": "/changelog" 12 | }, 13 | { 14 | "name": "i18n", 15 | "title": "国际化", 16 | "path": "/i18n" 17 | }, 18 | { 19 | "title": "代码规范", 20 | "href": "https://github.com/fmfe/fe-coding-style-guide/" 21 | }, 22 | { 23 | "title": "Table 组件", 24 | "href": "https://github.com/dwqs/v2-table" 25 | }, 26 | { 27 | "title": "Datepicker 组件", 28 | "href": "https://github.com/dwqs/v2-datepicker" 29 | }, 30 | { 31 | "title": "LazyList 组件", 32 | "href": "https://github.com/dwqs/v2-lazy-list" 33 | }, 34 | { 35 | "title": "Scrollbar 组件", 36 | "href": "https://github.com/dwqs/beautify-scrollbar" 37 | }, 38 | { 39 | "title": "基础组件", 40 | "children": [ 41 | { 42 | "title": "Utils 工具库", 43 | "name": "util", 44 | "path": "/utils" 45 | }, 46 | { 47 | "title": "Button 按钮", 48 | "name": "button", 49 | "path": "/button" 50 | }, 51 | { 52 | "title": "Toast 提醒", 53 | "name": "toast", 54 | "path": "/toast" 55 | }, 56 | { 57 | "title": "Notification 通知", 58 | "name": "notification", 59 | "path": "/notification" 60 | }, 61 | { 62 | "title": "Dialog 对话框", 63 | "name": "dialog", 64 | "path": "/dialog" 65 | }, 66 | { 67 | "title": "Loading 加载", 68 | "name": "loading", 69 | "path": "/loading" 70 | }, 71 | { 72 | "title": "Collapse 折叠面板", 73 | "name": "collapse", 74 | "path": "/collapse" 75 | }, 76 | { 77 | "title": "Tabs 标签页", 78 | "name": "tabs", 79 | "path": "/tabs" 80 | }, 81 | { 82 | "title": "Rate 评分", 83 | "name": "rate", 84 | "path": "/rate" 85 | }, 86 | { 87 | "title": "Carousel 走马灯", 88 | "name": "carousel", 89 | "path": "/carousel" 90 | }, 91 | { 92 | "title": "Input 输入框", 93 | "name": "input", 94 | "path": "/input" 95 | }, 96 | { 97 | "title": "Checkbox 多选框", 98 | "name": "checkbox", 99 | "path": "/checkbox" 100 | }, 101 | { 102 | "title": "Radio 单选框", 103 | "name": "radio", 104 | "path": "/radio" 105 | }, 106 | { 107 | "title": "Switch 开关", 108 | "name": "switch", 109 | "path": "/switch" 110 | }, 111 | { 112 | "title": "Select 选择器", 113 | "name": "select", 114 | "path": "/select" 115 | }, 116 | { 117 | "title": "RBG 按钮组", 118 | "name": "rbg", 119 | "path": "/rbg" 120 | } 121 | ] 122 | } 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /docs/pages/change-log.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 75 | 76 | -------------------------------------------------------------------------------- /docs/pages/guide.md: -------------------------------------------------------------------------------- 1 | ## 安装 2 | ### npm 安装 3 | ``` 4 | npm i fm-vue-ui -S 5 | ``` 6 | ### 引入 FM-ICON (Toast Dialog Alert 会用到字体图标 需要用到以上组件请务必引入) 7 | ```html 8 | 9 | ``` 10 | 11 | ### 引入 FMUI 12 | 可以引入整个 FMUI,或是根据需要仅引入部分组件。 13 | 14 | #### 完整引入 15 | 16 | 在 `main.js` 中写入以下内容: 17 | 18 | ```html 19 | import Vue from 'vue' 20 | import FMUI from 'fm-vue-ui' 21 | import 'fm-vue-ui/lib/theme-default/index.css' 22 | import App from './App.vue' 23 | 24 | Vue.use(FMUI) 25 | 26 | new Vue({ 27 | el: '#app', 28 | render: h => h(App) 29 | }) 30 | ``` 31 | 32 | #### 按需引入 33 | 借助 [babel-plugin-component](https://github.com/QingWei-Li/babel-plugin-component) 或者 [babel-plugin-on-demand-import](https://github.com/dwqs/babel-plugin-on-demand-import), 我们可以只引入需要的组件,以达到减小项目体积的目的。 34 | 35 | 36 | 37 | babel-plugin-component配置: 38 | 39 | ```js 40 | // .babelrc 41 | // .... 42 | "plugins": [["component", [ 43 | { 44 | "libraryName": "fm-vue-ui", 45 | "styleLibraryName": "theme-default" 46 | } 47 | ]]] 48 | // ... 49 | ``` 50 | 51 | babel-plugin-on-demand-import配置: 52 | 53 | ```js 54 | // .babelrc 55 | // .... 56 | "plugins": [[ 57 | "on-demand-import", { 58 | "libraryName": "fm-vue-ui", 59 | "libraryPath": "lib", 60 | "stylePath": "lib/theme-default", 61 | "needImportStyle": true 62 | } 63 | ]] 64 | // ... 65 | ``` 66 | 67 | 接下来,如果你只希望引入部分组件,比如 `Button`,那么需要在 `main.js` 中写入以下内容: 68 | 69 | ```html 70 | import Vue from 'vue' 71 | import { Button } from 'fm-vue-ui' 72 | import App from './App.vue' 73 | 74 | Vue.component(Button.name, Button) 75 | /* 或写为 76 | * Vue.use(Button) 77 | */ 78 | 79 | new Vue({ 80 | el: '#app', 81 | render: h => h(App) 82 | }) 83 | ``` 84 | 85 | 组件列表以 [components.json](https://github.com/fmfe/fm-vue-ui/blob/master/components.json) 中列出的为准。 86 | 87 | ### UI 层级规范 88 | 1. 常规元素的 `z-index` 的范围是 [0, 100] 89 | 2. 顶部导航、侧边导航等元素的 `z-index` 的范围是 (100, 1000] 90 | 3. 遮罩层的 `z-index` 的范围是 (1000, 10000] 91 | 4. 弹框、弹层以及toast等元素的 `z-index` 的范围是 (10000, 100000] 92 | 93 | ## 贡献指南 94 | 95 | 1. 可以在 [fm-vue-ui](https://github.com/fmfe/fm-vue-ui/issues) 以 issue 的形式说明你的需求 96 | 2. fork [fm-vue-ui](https://github.com/fmfe/fm-vue-ui), 然后开发自己的组件,写好对应的文档说明和单测,提交 PR 97 | 3. 代码规范请参考 [coding style](https://github.com/fmfe/fe-coding-style-guide/) 98 | 4. 对于组件中涉及的图标,请优先使用 CSS 来实现; 如果实现不了, 请将对应的图片资源放在 CDN 上 99 | ## 开发步骤 100 | 101 | 1. 在 `components.json` 文件中添加对应组件的映射(组件名和组件路径) 102 | 2. 在 `components` 目录下按格式建立自己的组件目录,对应的样式在 `theme-default/src` 目录下,文件名和映射的组件名保持一致 103 | 3. 执行 `npm run com` 104 | -------------------------------------------------------------------------------- /docs/pages/i18n.md: -------------------------------------------------------------------------------- 1 | ## 国际化 2 | `fm-vue-ui` 目前仅支持两种语言: `zh-CN`、`en-US` 和 `zh-HK`,默认使用 `zh-CN`。 3 | 4 | >我们所有的项目都是需要支持国际化的 5 | 6 | 如果需要设置其它语言,配置示例如下: 7 | 8 | ```html 9 | // 项目的 i18n 配置 src/i18n/index.js 10 | import Vue from 'vue'; 11 | import VueI18n from 'vue-i18n'; // 7.x 12 | import en from './en-US'; 13 | import cn from './zh-CN'; 14 | 15 | Vue.use(VueI18n); 16 | 17 | export default new VueI18n({ 18 | locale: 'zh-CN', 19 | messages: { 20 | 'en-US': en, 21 | 'zh-CN': cn 22 | } 23 | }); 24 | 25 | // page/index.js 26 | import Vue from 'vue'; 27 | import FMUI from 'fm-vue-ui'; 28 | 29 | import i18n from './i18n/index'; 30 | 31 | Vue.use(FMUI, { 32 | lang: i18n.locale 33 | }); 34 | ``` 35 | 36 | 如果是按需引入: 37 | 38 | ```html 39 | // 按需引入 40 | import Vue from 'vue' 41 | import { Button } from 'fm-vue-ui' 42 | import locale from 'fm-vue-ui/lib/locale/index'; 43 | 44 | locale.use(i18n.locale); 45 | Vue.use(Button); // 然后安装组件 46 | ``` 47 | 48 | 组件内的开发需要考虑国际化文案时,使用案例请参考 `Dialog`。 -------------------------------------------------------------------------------- /docs/routes.config.js: -------------------------------------------------------------------------------- 1 | import navConfig from './nav.config.json'; 2 | 3 | const LANG = 'zh-CN'; 4 | const routes = []; 5 | 6 | function loadMD (name) { 7 | return resolve => import(`./examples/${name}.md`).then(component => resolve(component || component.default)); 8 | // return r => require.ensure([], () => r(require(`./examples/${name}.md`)), 'examples'); 9 | }; 10 | 11 | function loadPages (name) { 12 | return resolve => import(`./pages/${name}.md`).then(component => resolve(component || component.default)); 13 | }; 14 | 15 | import ChangeLog from './pages/change-log.vue'; 16 | 17 | function regiterRoute (navConfig) { 18 | const navs = navConfig[LANG]; 19 | 20 | navs.forEach(nav => { 21 | if (!nav.href) { 22 | if (nav.children) { 23 | nav.children.forEach(child => { 24 | routes.push({ 25 | path: child.path, 26 | name: child.name, 27 | component: loadMD(child.name) 28 | }); 29 | }); 30 | } else if (nav.path === '/changelog') { 31 | routes.push({ 32 | path: nav.path, 33 | name: nav.name, 34 | component: ChangeLog 35 | }); 36 | } else { 37 | routes.push({ 38 | path: nav.path, 39 | name: nav.name, 40 | component: loadPages(nav.name) 41 | }); 42 | } 43 | } 44 | }); 45 | }; 46 | 47 | regiterRoute(navConfig); 48 | 49 | export default routes; 50 | -------------------------------------------------------------------------------- /docs/tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | FM Basic Vue Components 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // fix: https://github.com/akveo/ng2-admin/issues/604 2 | // 使用 happypack 之后 需单独提供 postcss 配置文件 3 | module.exports = { 4 | plugins: [ 5 | require('autoprefixer')({ browsers: ['last 5 versions', 'Android >= 4.0', 'iOS >= 7'] }) 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* Automatic generated by './build/build-entry.js' */ 2 | 3 | import utils from 'fm-vue-ui/src/utils'; 4 | import locale from 'fm-vue-ui/src/locale'; 5 | 6 | import Button from '../components/button/index.js'; 7 | import Dialog from '../components/dialog/index.js'; 8 | import Toast from '../components/toast/index.js'; 9 | import Notification from '../components/notification/index.js'; 10 | import Loading from '../components/loading/index.js'; 11 | import Collapse from '../components/collapse/index.js'; 12 | import CollapseItem from '../components/collapse-item/index.js'; 13 | import Tabs from '../components/tabs/index.js'; 14 | import TabPanel from '../components/tab-panel/index.js'; 15 | import Rate from '../components/rate/index.js'; 16 | import Carousel from '../components/carousel/index.js'; 17 | import Input from '../components/input/index.js'; 18 | import Checkbox from '../components/checkbox/index.js'; 19 | import Radio from '../components/radio/index.js'; 20 | import Switch from '../components/switch/index.js'; 21 | import Option from '../components/option/index.js'; 22 | import Select from '../components/select/index.js'; 23 | import RadioButtonGroup from '../components/radio-button-group/index.js'; 24 | 25 | const components = [ 26 | Button, 27 | Toast, 28 | Notification, 29 | Loading, 30 | Collapse, 31 | CollapseItem, 32 | Tabs, 33 | TabPanel, 34 | Rate, 35 | Carousel, 36 | Input, 37 | Checkbox, 38 | Radio, 39 | Switch, 40 | Option, 41 | Select, 42 | RadioButtonGroup 43 | ]; 44 | 45 | const install = function (Vue, opts = {}) { 46 | if (install.installed) return; 47 | 48 | let lang = 'zh-CN'; 49 | try { 50 | lang = opts.lang || (window.FMlocale ? window.FMlocale() : 'zh-CN'); 51 | } catch (e) {} 52 | locale.use(lang); 53 | 54 | components.map(component => { 55 | Vue.component(component.name, component); 56 | }); 57 | 58 | Vue.prototype.$fmdialog = Dialog; 59 | Vue.prototype.$fmtoast = Toast; 60 | Vue.prototype.$fmutils = utils; 61 | }; 62 | 63 | if (typeof window !== 'undefined' && window.Vue) { 64 | install(window.Vue); 65 | }; 66 | 67 | export { 68 | Button, 69 | Dialog, 70 | Toast, 71 | Notification, 72 | Loading, 73 | Collapse, 74 | CollapseItem, 75 | Tabs, 76 | TabPanel, 77 | Rate, 78 | Carousel, 79 | Input, 80 | Checkbox, 81 | Radio, 82 | Switch, 83 | Option, 84 | Select, 85 | RadioButtonGroup 86 | }; 87 | 88 | export default { 89 | version: '2.1.6', 90 | install, 91 | Button, 92 | Dialog, 93 | Toast, 94 | Notification, 95 | Loading, 96 | Collapse, 97 | CollapseItem, 98 | Tabs, 99 | TabPanel, 100 | Rate, 101 | Carousel, 102 | Input, 103 | Checkbox, 104 | Radio, 105 | Switch, 106 | Option, 107 | Select, 108 | RadioButtonGroup 109 | }; 110 | 111 | -------------------------------------------------------------------------------- /src/locale/format.js: -------------------------------------------------------------------------------- 1 | function hasOwn (obj, key) { 2 | return Object.prototype.hasOwnProperty.call(obj, key); 3 | } 4 | 5 | const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g; 6 | /** 7 | * String format template 8 | * - Inspired: 9 | * https://github.com/Matt-Esch/string-template/index.js 10 | */ 11 | export default function () { 12 | /** 13 | * template 14 | * 15 | * @param {String} string 16 | * @param {Array} ...args 17 | * @return {String} 18 | */ 19 | 20 | function template (string, ...args) { 21 | if (args.length === 1 && typeof args[0] === 'object') { 22 | args = args[0]; 23 | } 24 | 25 | if (!args || !args.hasOwnProperty) { 26 | args = {}; 27 | } 28 | 29 | return string.replace(RE_NARGS, (match, prefix, i, index) => { 30 | let result; 31 | 32 | if ( 33 | string[index - 1] === '{' && 34 | string[index + match.length] === '}' 35 | ) { 36 | return i; 37 | } else { 38 | result = hasOwn(args, i) ? args[i] : null; 39 | if (result === null || result === undefined) { 40 | return ''; 41 | } 42 | 43 | return result; 44 | } 45 | }); 46 | } 47 | 48 | return template; 49 | } 50 | -------------------------------------------------------------------------------- /src/locale/index.js: -------------------------------------------------------------------------------- 1 | import cn from './lang/zh-CN.json'; 2 | import en from './lang/en-US.json'; 3 | import hk from './lang/zh-HK.json'; 4 | 5 | import Format from './format'; 6 | 7 | const language = { 8 | 'zh-CN': cn, 9 | 'en-US': en, 10 | 'zh-HK': hk 11 | }; 12 | 13 | const defaultLang = 'zh-CN'; 14 | const format = Format(); 15 | let locale = defaultLang; 16 | 17 | // let i18nHandler = function i18nHandler () { 18 | // const vuei18n = Object.getPrototypeOf(this).$t; 19 | // if (typeof vuei18n === 'function') { 20 | // return vuei18n.apply(this, arguments); 21 | // } 22 | // }; 23 | 24 | // function i18n (fn) { 25 | // i18nHandler = fn || i18nHandler; 26 | // } 27 | 28 | function t (path, options) { 29 | let value = false; 30 | const array = path.split('.'); 31 | let current = language[locale] || language[defaultLang]; 32 | for (var i = 0, j = array.length; i < j; i++) { 33 | const property = array[i]; 34 | value = current[property]; 35 | if (i === j - 1) return format(value, options); 36 | if (!value) return ''; 37 | current = value; 38 | } 39 | return ''; 40 | } 41 | 42 | function use (l) { 43 | locale = l || defaultLang; 44 | } 45 | 46 | function getLocale () { 47 | return locale; 48 | } 49 | 50 | export default { use, t, getLocale }; 51 | -------------------------------------------------------------------------------- /src/locale/lang/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmdialog": { 3 | "title": "Confirm", 4 | "confirmText": "Confirm", 5 | "cancelText": "Cancel" 6 | }, 7 | "fmselect": { 8 | "placeholder": "Please select", 9 | "empty": "No Data" 10 | }, 11 | "fminput": { 12 | "placeholder": "Please input" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/locale/lang/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmdialog": { 3 | "title": "请确认", 4 | "confirmText": "确认", 5 | "cancelText": "取消" 6 | }, 7 | "fmselect": { 8 | "placeholder": "请选择", 9 | "empty": "暂无数据" 10 | }, 11 | "fminput": { 12 | "placeholder": "请输入" 13 | } 14 | } -------------------------------------------------------------------------------- /src/locale/lang/zh-HK.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmdialog": { 3 | "title": "請確認", 4 | "confirmText": "確認", 5 | "cancelText": "取消" 6 | }, 7 | "fmselect": { 8 | "placeholder": "請選擇", 9 | "empty": "暫無數據" 10 | }, 11 | "fminput": { 12 | "placeholder": "請輸入" 13 | } 14 | } -------------------------------------------------------------------------------- /src/locale/lang/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmdialog": { 3 | "title": "請確認", 4 | "confirmText": "確認", 5 | "cancelText": "取消" 6 | }, 7 | "fmselect": { 8 | "placeholder": "請選擇", 9 | "empty": "暫無數據" 10 | }, 11 | "fminput": { 12 | "placeholder": "請輸入" 13 | } 14 | } -------------------------------------------------------------------------------- /src/mixins/i18n.js: -------------------------------------------------------------------------------- 1 | import locale from 'fm-vue-ui/src/locale/index.js'; 2 | 3 | export default { 4 | computed: { 5 | $fl () { 6 | return locale.getLocale(); 7 | } 8 | }, 9 | 10 | methods: { 11 | $ft (...args) { 12 | return locale.t.apply(this, args); 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | export const addZero = (val) => { 2 | return val > 9 ? val : `0${val}`; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通用的工具库 3 | */ 4 | import { localStorage, sessionStorage, memoryStorage } from './storage'; 5 | import { addZero } from './helpers'; 6 | 7 | // 用户头像默认加载失败的图片 8 | const avatarError = (e, defaultUrl = '') => { 9 | const url = defaultUrl ? defaultUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAIAAAAn5KxJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjdGNjRBRjMyOTdCMTFFN0I1OTZGMkRFNTM0MDBBNUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjdGNjRBRjQyOTdCMTFFN0I1OTZGMkRFNTM0MDBBNUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2N0Y2NEFGMTI5N0IxMUU3QjU5NkYyREU1MzQwMEE1QSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2N0Y2NEFGMjI5N0IxMUU3QjU5NkYyREU1MzQwMEE1QSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pkm31lAAAALtSURBVHja7JnZbtpAFIY9m228YkNL0uT936kXlapWUVUE2diMt+kPpGlCCNhjyyaVj7gg0gz+fPZzQr5++6l9BKHaB5EOtAPtQDvQDrSa8Hpel1Lfc23L5JzHSbJYrJarVRynhJwTqC7ExWjIGNv9aeg6PmHgJ0m6WC7XcZImKehbBoUuR58Hz5QvRQje973d9yRNp9N7qJmoKrmqj8LiMPfJY4JzaN1xbCllO6Cuaxc//GkQMMrUWCuBmqbBaIlfgN3D0M9zFdRKoAiaslcc2wKu1GSjoAiXsldACTvIvFlQSqmSHYTcSnOgaqmmBdNrsrmLXVPSgf7XoFJ+EFD0RAq38jxvGnQVRQq31utEodmrBBpFUVpSqShIy2ilkaaDidw/zEpdeJwt0D2RTXkijUb9bL6I46Swd8rx5JaCkZKGNbqR8WRapMPAmZtfv7MsQytDtDZAodHbu4fjZ9I0+/7jBtMpGm1KVQanGkDx2Nl8fiI/rKL1OmYYRBhVm+/qqkwnns0423gmJa1Noc/+lx9t2kG4fRf1hURttT6Xx+oN50yrVm9rAz0+jvKtVOi0awLFOHrS+TzPqdLE1APa992TZwZBHw7Q2qYEihyGgWX1Coys5PrLhW7oaqyKSzLBua4Lw9Ad2zq4IXsvpK4vR8ip26Vkso5TKfP6QRENtmX2TBN8ahP9Tno9E5+njjZJ0Ssul5uCUBUUxrVta7OlNY3a+3YhuBCO5zroAdDfPM7mWZaXBgWi59q+5xY3rnp2Y6zve3gWcO/uH99OAe+CIj7Cvq+wXaoYmtAu/B5dDrT7MuXxQy9Hh4PQ+utGLQycFAAB/G08nmTossmh9AQTXF2OWqT8F3OmcX11gaS2S2f7oGHgN+CRxR3Xd538LSjqtV0gdTcpcIB8u6KmexmO1PivoToEZYUSug+ql191NyAoMDD+a9Bmk1HR1szUpfZaoxgYzhBUF2Lf9JydJyjfNz3Gr3MEReRI+UeAAQA6cgV7TSGW7QAAAABJRU5ErkJggg=='; 10 | e.target.src = url; 11 | }; 12 | 13 | // 格式化时间 14 | const formatDateToStr = (date, formatStr) => { 15 | formatStr = formatStr || 'yyyy-MM-dd'; 16 | date = date || new Date(); 17 | let str = formatStr; 18 | 19 | str = str.replace(/yyyy|YYYY/, date.getFullYear()); 20 | str = str.replace(/yy|YY/, (date.getYear() % 100) > 9 ? (date.getYear() % 100).toString() : '0' + (date.getYear() % 100)); 21 | str = str.replace(/MM/, addZero(date.getMonth() + 1)); 22 | str = str.replace(/M/g, date.getMonth() + 1); 23 | str = str.replace(/dd|DD/, addZero(date.getDate())); 24 | str = str.replace(/d|D/g, date.getDate()); 25 | str = str.replace(/hh|HH/, addZero(date.getHours())); 26 | str = str.replace(/h|H/g, date.getHours()); 27 | str = str.replace(/mm/, addZero(date.getMinutes())); 28 | str = str.replace(/m/g, date.getMinutes()); 29 | str = str.replace(/ss|SS/, addZero(date.getSeconds())); 30 | str = str.replace(/s|S/g, date.getSeconds()); 31 | return str; 32 | }; 33 | 34 | export default { 35 | formatDateToStr, 36 | avatarError, 37 | localStorage, 38 | sessionStorage, 39 | memoryStorage 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const prefix = '_fm_'; 2 | const s = sessionStorage; 3 | export const localStorage = { 4 | set (key, val) { 5 | try { 6 | window.localStorage.setItem(prefix + key, JSON.stringify(val)); 7 | } catch (err) { 8 | alert('localStorage 写入出错'); 9 | } 10 | }, 11 | 12 | get (key) { 13 | try { 14 | const val = window.localStorage.getItem(prefix + key); 15 | return JSON.parse(val); 16 | } catch (e) { 17 | return window.localStorage.getItem(prefix + key); 18 | } 19 | }, 20 | 21 | remove (key) { 22 | window.localStorage.removeItem(prefix + key); 23 | }, 24 | 25 | clear () { 26 | window.localStorage.clear(); 27 | }, 28 | 29 | getAll () { 30 | const res = {}; 31 | for (let i = window.localStorage.length - 1; i >= 0; i--) { 32 | let key = window.localStorage.key(i); 33 | if (key.startsWith(prefix)) { 34 | key = key.slice(prefix.length); 35 | res[key] = localStorage.get(key); 36 | } 37 | } 38 | return res; 39 | } 40 | }; 41 | 42 | export const sessionStorage = { 43 | set (key, val) { 44 | try { 45 | window.sessionStorage.setItem(prefix + item, JSON.stringify(val)); 46 | } catch (err) { 47 | alert('sessionStorage 写入出错'); 48 | } 49 | }, 50 | 51 | get (key) { 52 | try { 53 | const val = window.sessionStorage.getItem(prefix + key); 54 | return JSON.parse(val); 55 | } catch (e) { 56 | return window.sessionStorage.getItem(prefix + key); 57 | } 58 | }, 59 | 60 | remove (key) { 61 | window.sessionStorage.removeItem(prefix + key); 62 | }, 63 | 64 | clear () { 65 | window.sessionStorage.clear(); 66 | }, 67 | 68 | getAll () { 69 | const res = {}; 70 | for (let i = window.sessionStorage.length - 1; i >= 0; i--) { 71 | let key = window.sessionStorage.key(i); 72 | if (key.startsWith(prefix)) { 73 | key = key.slice(prefix.length); 74 | res[key] = sessionStorage.get(key); 75 | } 76 | } 77 | return res; 78 | } 79 | }; 80 | 81 | let _memoryStorage = {}; 82 | export const memoryStorage = { 83 | set (key, val) { 84 | _memoryStorage[prefix + key] = val; 85 | }, 86 | 87 | get (key) { 88 | return _memoryStorage[prefix + key]; 89 | }, 90 | 91 | remove (key) { 92 | delete _memoryStorage[prefix + key]; 93 | }, 94 | 95 | clear () { 96 | _memoryStorage = {}; 97 | }, 98 | 99 | getAll () { 100 | return _memoryStorage; 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const testsContext = require.context('./specs', true, /\.spec\.js$/); 2 | testsContext.keys().forEach(testsContext); 3 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const webpackTestConfig = require('../build/webpack.test.config'); 4 | /* eslint-disable */ 5 | let isCI = process.env.CONTINUOUS_INTEGRATION ? true : false; 6 | 7 | module.exports = config => { 8 | config.set({ 9 | frameworks: ['mocha', 'chai'], 10 | files: [ 11 | './index.js' 12 | ], 13 | browsers: [isCI ? 'ChromeTravisCI' : 'Chrome'], 14 | customLaunchers: { 15 | ChromeTravisCI: { 16 | base: 'Chrome', 17 | flags: ['--no-sandbox'] 18 | } 19 | }, 20 | plugins: [ 21 | 'karma-chrome-launcher', 22 | 'karma-mocha', 23 | 'karma-sourcemap-loader', 24 | 'karma-webpack', 25 | 'karma-mocha-reporter', 26 | 'karma-chai', 27 | 'karma-coverage' 28 | ], 29 | reporters: ['progress', 'mocha', 'coverage'], 30 | singleRun: true, 31 | autoRun: true, 32 | mochaReporter: { 33 | colors: { 34 | success: 'blue', 35 | info: 'bgGreen', 36 | warning: 'cyan', 37 | error: 'bgRed' 38 | }, 39 | symbols: { 40 | success: '+', 41 | info: '#', 42 | warning: '!', 43 | error: 'x' 44 | } 45 | }, 46 | coverageReporter: { 47 | dir: './coverage', 48 | reporters: [ 49 | { type: 'lcov', subdir: '.' }, 50 | { type: 'text-summary' } 51 | ] 52 | }, 53 | preprocessors: { 54 | './index.js': ['webpack', 'sourcemap'] 55 | }, 56 | logLevel: config.LOG_INFO, 57 | colors: true, 58 | webpack: webpackTestConfig, 59 | webpackMiddleware: { 60 | noInfo: true 61 | } 62 | }); 63 | }; -------------------------------------------------------------------------------- /test/specs/button.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import { createCompTest, destroyVM, createVM } from '../utils'; 4 | import Button from 'components/button'; 5 | 6 | describe('Button', () => { 7 | let vm; 8 | afterEach(() => { 9 | destroyVM(vm); 10 | }); 11 | 12 | it('create', () => { 13 | vm = createCompTest(Button, { 14 | type: 'primary' 15 | }); 16 | const buttonElm = vm.$el; 17 | expect(buttonElm.classList.contains('fm-btn-primary')).to.be.true; 18 | }); 19 | 20 | it('disabled', () => { 21 | vm = createCompTest(Button, { 22 | disabled: true 23 | }); 24 | const buttonElm = vm.$el; 25 | expect(buttonElm.classList.contains('fm-btn-disabled')).to.be.true; 26 | }); 27 | 28 | it('size', () => { 29 | vm = createCompTest(Button, { 30 | size: 'large' 31 | }); 32 | const buttonElm = vm.$el; 33 | expect(buttonElm.classList.contains('fm-btn-large')).to.be.true; 34 | }); 35 | 36 | it('click/text', done => { 37 | let res = ''; 38 | vm = createVM({ 39 | template: `测试按钮`, 40 | methods: { 41 | testClick (e) { 42 | res = '点击'; 43 | } 44 | } 45 | }); 46 | const buttonElm = vm.$el; 47 | buttonElm.click(); 48 | expect(buttonElm.textContent).to.equal('测试按钮'); 49 | setTimeout(() => { 50 | expect(res).to.equal('点击'); 51 | done(); 52 | }, 20); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import FMUI from 'main/index'; 3 | 4 | Vue.use(FMUI); 5 | 6 | // 创建 vm 实例 7 | export const createVM = (opts) => { 8 | return new Vue(opts).$mount(); 9 | }; 10 | 11 | // 销毁 vm 实例 12 | export const destroyVM = (vm) => { 13 | vm.$destroy && vm.$destroy(); 14 | vm.$el && 15 | vm.$el.parentNode && 16 | vm.$el.parentNode.removeChild(vm.$el); 17 | }; 18 | 19 | /** 20 | * 创建组件测试实例 21 | * https://cn.vuejs.org/v2/guide/unit-testing.html#编写可被测试的组件 22 | */ 23 | export const createCompTest = (Component, propsData) => { 24 | const Ctor = Vue.extend(Component); 25 | const vm = new Ctor({ propsData: propsData }).$mount(); 26 | return vm; 27 | }; 28 | -------------------------------------------------------------------------------- /theme-default/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const less = require('gulp-less'); 3 | const autoprefixer = require('gulp-autoprefixer'); 4 | const cssmin = require('gulp-cssmin'); 5 | 6 | gulp.task('build', function () { 7 | return gulp.src('./src/**/*.less') 8 | .pipe(less()) 9 | .pipe(autoprefixer({ 10 | browsers: ['ie > 8', 'safari > 8', 'last 5 versions'], 11 | cascade: false 12 | })) 13 | .pipe(cssmin()) 14 | .pipe(gulp.dest('../lib/theme-default')); 15 | }); 16 | -------------------------------------------------------------------------------- /theme-default/src/base.less: -------------------------------------------------------------------------------- 1 | @import "./common/transition.less"; 2 | -------------------------------------------------------------------------------- /theme-default/src/button.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-button { 4 | display: inline-block; 5 | background: transparent; 6 | border: 1px solid @border-regular-color-6; 7 | text-align: center; 8 | color: @color-regular-2; 9 | font-size: @min-font-size; 10 | cursor: pointer; 11 | border-radius: 4px; 12 | outline: none; 13 | line-height: 1.5; 14 | vertical-align: middle; 15 | box-sizing: border-box; 16 | } 17 | 18 | .fm-button-radius-large { 19 | border-radius: 100px; 20 | } 21 | 22 | .fm-button-loading-icon { 23 | width: 10px; 24 | height: 10px; 25 | border: none; 26 | background: transparent; 27 | display: inline-block; 28 | font-size: 0; 29 | line-height: 0; 30 | margin: 0 auto; 31 | margin-right: 2px; 32 | } 33 | 34 | .fm-button-ghost { 35 | border-color: @border-primary-color; 36 | color: @color-primary; 37 | 38 | &:hover { 39 | border-color: @border-primary-color; 40 | background: @color-primary; 41 | color: @color-white; 42 | } 43 | } 44 | 45 | .fm-button-primary { 46 | border-color: @border-primary-color; 47 | background: @color-primary; 48 | color: @color-white; 49 | 50 | &:hover { 51 | background: @color-primary-hover; 52 | } 53 | } 54 | 55 | .fm-button-default { 56 | &:hover { 57 | border-color: @border-primary-color; 58 | color: @color-primary; 59 | } 60 | } 61 | 62 | .fm-button-invert { 63 | &.fm-button-default { 64 | background: #515151; 65 | border: none; 66 | color: @color-white; 67 | 68 | &:hover { 69 | background: #797979; 70 | } 71 | } 72 | 73 | &.fm-button-ghost { 74 | background: @color-white; 75 | border: none; 76 | color: #111; 77 | 78 | &:hover { 79 | background: #ccc; 80 | } 81 | } 82 | } 83 | 84 | .fm-button-disabled { 85 | background: @background-color-disabled; 86 | color: @border-disabled-color; 87 | border: 1px solid @border-disabled-color; 88 | pointer-events: none; 89 | } 90 | 91 | .fm-button-small { 92 | min-height: 24px; 93 | min-width: 48px; 94 | font-size: @min-font-size; 95 | padding: 0 8px; 96 | 97 | .fm-button-loading-icon { 98 | transform: scale(2.4); 99 | } 100 | } 101 | 102 | .fm-button-medium { 103 | min-height: 30px; 104 | min-width: 60px; 105 | font-size: @normal-font-size; 106 | padding: 0 10px; 107 | 108 | .fm-button-loading-icon { 109 | transform: scale(3); 110 | } 111 | } 112 | 113 | .fm-button-large { 114 | min-width: 80px; 115 | min-height: 40px; 116 | padding: 0 16px; 117 | font-size: @mid-font-size; 118 | 119 | .fm-button-loading-icon { 120 | transform: scale(4); 121 | } 122 | } 123 | 124 | .fm-button-xlarge { 125 | min-height: 50px; 126 | min-width: 100px; 127 | font-size: @max-font-size; 128 | padding: 0 20px; 129 | 130 | .fm-button-loading-icon { 131 | transform: scale(5); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /theme-default/src/carousel.less: -------------------------------------------------------------------------------- 1 | .fm-swiper-wrapper { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | 6 | .fm-swiper-imgs { 7 | display: inline-block; 8 | width: 100%; 9 | z-index: 1; 10 | 11 | a { 12 | display: block; 13 | } 14 | } 15 | 16 | .fm-swiper-dot { 17 | position: absolute; 18 | bottom: 10px; 19 | left: 50%; 20 | transform: translateX(-50%); 21 | 22 | li { 23 | display: inline-block; 24 | margin: 0 4px; 25 | width: 10px; 26 | height: 10px; 27 | border-radius: 50%; 28 | cursor: pointer; 29 | background: rgba(255, 255, 255, .5); 30 | -webkit-transition: all .3s linear; 31 | transition: all .3s linear; 32 | 33 | &.actived { 34 | width: 20px; 35 | border-radius: 10px; 36 | background: rgba(255, 255, 255, 1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /theme-default/src/checkbox.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .checkbox-inner-after(@top, @left, @width, @height, @border-width, @border-color) { 4 | top: @top; 5 | left: @left; 6 | height: @height; 7 | width: @width; 8 | border: @border-width solid @border-color; 9 | border-left: 0; 10 | border-top: 0; 11 | } 12 | 13 | .fm-checkbox { 14 | position: relative; 15 | display: inline-block; 16 | color: @color-regular-1; 17 | font-size: @normal-font-size; 18 | cursor: pointer; 19 | white-space: nowrap; 20 | user-select: none; 21 | 22 | &.is-disabled { 23 | cursor: not-allowed; 24 | 25 | .fm-checkbox-label { 26 | color: @color-regular; 27 | } 28 | } 29 | } 30 | 31 | .fm-checkbox-input { 32 | position: relative; 33 | top: -1px; 34 | display: inline-block; 35 | box-sizing: border-box; 36 | background: @background-color-white; 37 | line-height: 1; 38 | white-space: nowrap; 39 | outline: none; 40 | cursor: pointer; 41 | vertical-align: middle; 42 | 43 | .fm-checkbox-inner { 44 | position: relative; 45 | display: inline-block; 46 | box-sizing: border-box; 47 | width: 100%; 48 | height: 100%; 49 | z-index: @z-index-normal; 50 | border-radius: @border-radius-normal; 51 | border: @border-width-base solid @border-regular-color; 52 | 53 | &::after { 54 | position: absolute; 55 | content: ''; 56 | top: 0; 57 | left: 4px; 58 | height: 7px; 59 | width: 3px; 60 | border: @border-width-base solid @border-primary-color; 61 | border-left: 0; 62 | border-top: 0; 63 | transition: transform .15s cubic-bezier(.71, -.46, .88, .6) .05s; 64 | transform-origin: center; 65 | transform: rotate(45deg) scaleY(0); 66 | } 67 | } 68 | 69 | &.fm-checkbox-small { 70 | width: 16px; 71 | height: 16px; 72 | 73 | .fm-checkbox-inner::after { 74 | .checkbox-inner-after(0, 4px, 5px, 10px, @border-width-normal, @border-primary-color); 75 | } 76 | } 77 | 78 | &.fm-checkbox-medium { 79 | width: 20px; 80 | height: 20px; 81 | 82 | .fm-checkbox-inner::after { 83 | .checkbox-inner-after(0, 6px, 5px, 12px, @border-width-normal, @border-primary-color); 84 | } 85 | } 86 | 87 | &.fm-checkbox-large { 88 | width: 24px; 89 | height: 24px; 90 | 91 | .fm-checkbox-inner::after { 92 | .checkbox-inner-after(1px, 8px, 6px, 14px, @border-width-normal, @border-primary-color); 93 | } 94 | } 95 | 96 | &.is-disabled { 97 | background: @background-color-disabled; 98 | cursor: not-allowed; 99 | 100 | .fm-checkbox-inner { 101 | border: @border-width-base solid #edeef2; 102 | } 103 | } 104 | 105 | &.is-checked { 106 | .fm-checkbox-inner { 107 | border: @border-width-base solid @border-primary-color; 108 | 109 | &::after { 110 | transform: rotate(45deg) scaleY(1); 111 | } 112 | } 113 | 114 | &.is-disabled { 115 | cursor: not-allowed; 116 | 117 | .fm-checkbox-inner { 118 | border: @border-width-base solid #edeef2; 119 | 120 | &::after { 121 | transform: rotate(45deg) scaleY(1); 122 | border: @border-width-base solid @border-regular-color-3; 123 | border-left: 0; 124 | border-top: 0; 125 | } 126 | } 127 | 128 | &.fm-checkbox-medium { 129 | .fm-checkbox-inner::after { 130 | .checkbox-inner-after(2px, 6px, 4px, 8px, @border-width-normal, @border-regular-color-3); 131 | } 132 | } 133 | 134 | &.fm-checkbox-large { 135 | .fm-checkbox-inner::after { 136 | .checkbox-inner-after(2px, 8px, 5px, 10px, @border-width-normal, @border-regular-color-3); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | .fm-checkbox-original { 144 | opacity: 0; 145 | outline: none; 146 | position: absolute; 147 | margin: 0; 148 | width: 0; 149 | height: 0; 150 | left: -999px; 151 | } 152 | 153 | .fm-checkbox-label { 154 | display: inline-block; 155 | padding-left: 5px; 156 | line-height: 1; 157 | font-size: @normal-font-size; 158 | } 159 | -------------------------------------------------------------------------------- /theme-default/src/collapse-item.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/theme-default/src/collapse-item.less -------------------------------------------------------------------------------- /theme-default/src/collapse.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-collapse { 4 | width: 100%; 5 | height: auto; 6 | } 7 | 8 | .fm-collapse-item { 9 | position: relative; 10 | border: @border-width-base solid @border-regular-color; 11 | border-radius: @border-radius-normal; 12 | overflow: hidden; 13 | } 14 | 15 | .fm-collapse-item-header { 16 | line-height: 14px; 17 | overflow: hidden; 18 | padding: 15px; 19 | background-color: @background-color-gray; 20 | cursor: pointer; 21 | &:hover { 22 | background-color: @background-color-white; 23 | } 24 | &.fm-collapse-item-active { 25 | background-color: @background-color-white; 26 | border-bottom: @border-width-base solid @border-regular-color; 27 | } 28 | span { 29 | display: inline-block; 30 | //white-space: nowrap; 31 | //overflow: hidden; 32 | //text-overflow: ellipsis; 33 | font-size: @mid-font-size; 34 | &.fm-collapse-item-title { 35 | width: 145px; 36 | } 37 | &.fm-collapse-item-info { 38 | margin-left: 15px; 39 | color: @color-regular-4; 40 | width: auto; 41 | } 42 | &.fm-collapse-item-act { 43 | position: absolute; 44 | right: 15px; 45 | color: @color-regular-3; 46 | &:hover { 47 | color: @color-primary; 48 | } 49 | } 50 | } 51 | } 52 | 53 | .fm-collapse-item-content { 54 | background-color: @background-color-white; 55 | overflow: hidden; 56 | } 57 | 58 | .fm-collapse-enter-transition { 59 | transition: all 0.5s; 60 | // transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out; 61 | } 62 | 63 | .fm-collapse-leave-transition { 64 | transition: all 0.3s; 65 | } 66 | -------------------------------------------------------------------------------- /theme-default/src/common/transition.less: -------------------------------------------------------------------------------- 1 | @import "./var.less"; 2 | 3 | // 通用的 trasition 4 | .fm-common-enter { 5 | opacity: 0; 6 | } 7 | 8 | .fm-common-enter-active { 9 | transition: all .5s ease; 10 | } 11 | 12 | .fm-common-leave { 13 | opacity: 1; 14 | } 15 | 16 | .fm-common-leave-active { 17 | transition: @all-transition; 18 | opacity: 0; 19 | } 20 | -------------------------------------------------------------------------------- /theme-default/src/common/var.less: -------------------------------------------------------------------------------- 1 | // Transition 2 | @all-transition: all .3s cubic-bezier(.645, .045, .355, 1); 3 | @fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); 4 | @fade-linear-transition: opacity 200ms linear; 5 | @md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms; 6 | @border-transition-base: border-color .2s cubic-bezier(.645, .045, .355, 1); 7 | @color-transition-base: color .2s cubic-bezier(.645, .045, .355, 1); 8 | 9 | // Fonts 10 | @min-font-size: 12px; 11 | @normal-font-size: 14px; 12 | @mid-font-size: 16px; 13 | @max-font-size: 18px; 14 | @large-font-size: 20px; 15 | 16 | @font-weight-normal: 400; 17 | @font-weight-mid: 500; 18 | @font-weight-max: 700; 19 | 20 | // Colors 21 | @color-white: #fff; 22 | @color-black: #000; 23 | 24 | @color-primary: #ff6200; 25 | @color-primary-hover: #f05102; 26 | @color-regular: #a1a4ad; 27 | @color-regular-1: #333; 28 | @color-regular-2: #555; 29 | @color-regular-3: #999; 30 | @color-regular-4: #777; 31 | @color-regular-5: #ffa871; 32 | @color-placeholder: #b4bccc; 33 | // Icon Colors 34 | @icon-color-success: #1fbb95; 35 | @icon-color-info: #999; 36 | @icon-color-warning: #f5ab3b; 37 | @icon-color-error: #eb4e5c; 38 | 39 | // Background 40 | @background-color-base: #fafafc; 41 | @background-color-transparent: transparent; 42 | @background-color-mask: rgba(0, 0, 0, 0.298); 43 | @background-color-white: @color-white; 44 | @background-color-primary: @color-primary; 45 | @background-color-disabled: #fafafa; 46 | @background-color-gray: #f5f5f7; 47 | @background-color-light-1: @color-primary; 48 | @background-color-light-2: #f5f6fa; 49 | @background-color-light-3: #9c9c9d; 50 | @background-color-light-4: #ccc; 51 | @background-color-light-5: @color-regular-5; 52 | 53 | // Border 54 | @border-primary-color: @color-primary; 55 | @border-white-color: @color-white; 56 | @border-regular-color-1: @color-regular; 57 | @border-regular-color: #e1e2e6; 58 | @border-regular-color-2: #ff8030; 59 | @border-regular-color-3: #c6c8cf; 60 | @border-regular-color-4: @color-regular-5; 61 | @border-regular-color-5: #e9e9e9; 62 | @border-regular-color-6: #ccc; 63 | @border-disabled-color: #d9d9d9; 64 | 65 | @border-width-base: 1px; 66 | @border-width-normal: 2px; 67 | @border-radius-base: 1px; 68 | @border-radius-normal: 2px; 69 | @border-radius-max: 4px; 70 | 71 | // z-index 72 | @z-index-normal: 1; 73 | @z-index-regular: 5; 74 | @z-index-nav: 150; 75 | @z-index-mask: 1500; 76 | @z-index-popper: 15000; 77 | -------------------------------------------------------------------------------- /theme-default/src/dialog.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-model-dialog-wrap { 4 | position: fixed; 5 | z-index: @z-index-popper; 6 | top: 0; 7 | left: 0; 8 | right: 0; 9 | bottom: 0; 10 | overflow: hidden; 11 | text-align: center; 12 | white-space: nowrap; 13 | font-size: 0; 14 | 15 | &:after { 16 | content: ""; 17 | display: inline-block; 18 | height: 100%; 19 | vertical-align: middle; 20 | } 21 | } 22 | 23 | .fm-model-dialog-mask { 24 | position: fixed; 25 | z-index: -1; 26 | top: 0; 27 | left: 0; 28 | right: 0; 29 | bottom: 0; 30 | overflow: hidden; 31 | } 32 | 33 | .fm-dialog-close-icon { 34 | position: absolute; 35 | top: 13px; 36 | right: 20px; 37 | cursor: pointer; 38 | font-size: 24px; 39 | line-height: 24px; 40 | width: 24px; 41 | height: 24px; 42 | border-radius: 50%; 43 | color: #767676; 44 | text-align: center; 45 | vertical-align: middle; 46 | //&::before, &:after { 47 | // position: absolute; 48 | // left: 2px; 49 | // content: ''; 50 | // width: 2px; 51 | // height: 10px; 52 | // background: @background-color-light-3; 53 | //} 54 | // 55 | //&::before { 56 | // transform: rotate(-45deg); 57 | //} 58 | // 59 | //&::after { 60 | // transform: rotate(45deg); 61 | //} 62 | 63 | &:hover { 64 | background: #f5f5f5; 65 | } 66 | } 67 | 68 | .fm-model-dialog { 69 | position: relative; 70 | display: inline-block; 71 | vertical-align: middle; 72 | border-radius: 6px; 73 | background: @background-color-white; 74 | text-align: left; 75 | white-space: normal; 76 | box-shadow: 0 4px 20px 1px rgba(0, 0, 0, 0.2); 77 | 78 | .fm-dialog-top { 79 | min-width: 350px; 80 | max-width: 430px; 81 | height: 50px; 82 | line-height: 50px; 83 | border-bottom: @border-width-base solid @border-regular-color-5; 84 | 85 | h1 { 86 | font-size: @mid-font-size; 87 | font-weight: @font-weight-mid; 88 | color: @color-regular-1; 89 | margin: 0 0 0 20px; 90 | } 91 | } 92 | 93 | .fm-dialog-middle { 94 | font-weight: @font-weight-normal; 95 | max-width: 430px; 96 | padding: 28px 20px; 97 | //text-align: center; 98 | box-sizing: border-box; 99 | 100 | .fm-dialog-middle-icon { 101 | display: inline-block; 102 | position: relative; 103 | font-size: 36px; 104 | vertical-align: middle; 105 | 106 | &.success { 107 | color: @icon-color-success; 108 | } 109 | 110 | &.info { 111 | color: @icon-color-info; 112 | } 113 | 114 | &.warning { 115 | color: @icon-color-warning; 116 | } 117 | 118 | &.error { 119 | color: @icon-color-error; 120 | } 121 | } 122 | 123 | span { 124 | display: inline-block; 125 | font-size: @normal-font-size; 126 | line-height: 22px; 127 | vertical-align: middle; 128 | color: #666; 129 | } 130 | } 131 | 132 | .fm-dialog-bottom { 133 | text-align: right; 134 | cursor: pointer; 135 | padding: 0 20px; 136 | margin-bottom: 20px; 137 | 138 | &.fm-dialog-bottom-center { 139 | text-align: center; 140 | } 141 | 142 | span { 143 | display: inline-block; 144 | line-height: 30px; 145 | text-align: center; 146 | font-size: @mid-font-size; 147 | 148 | &.fm-confirm { 149 | min-width: 60px; 150 | height: 30px; 151 | border-radius: 15px; 152 | background: @background-color-light-1; 153 | color: @color-white; 154 | margin-left: 20px; 155 | font-size: 14px; 156 | 157 | &:hover { 158 | opacity: 0.8; 159 | } 160 | 161 | &.disabled { 162 | cursor: not-allowed; 163 | border: 1px solid #d9d9d9; 164 | background: #f5f5f5; 165 | color: #ccc; 166 | } 167 | } 168 | 169 | &.fm-cancel { 170 | font-size: 14px; 171 | color: @color-regular-1; 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /theme-default/src/index.less: -------------------------------------------------------------------------------- 1 | @import "./base.less"; 2 | @import "./button.less"; 3 | @import "./dialog.less"; 4 | @import "./toast.less"; 5 | @import "./notification.less"; 6 | @import "./loading.less"; 7 | @import "./collapse.less"; 8 | @import "./collapse-item.less"; 9 | @import "./tabs.less"; 10 | @import "./tab-panel.less"; 11 | @import "./rate.less"; 12 | @import "./carousel.less"; 13 | @import "./input.less"; 14 | @import "./checkbox.less"; 15 | @import "./radio.less"; 16 | @import "./switch.less"; 17 | @import "./option.less"; 18 | @import "./select.less"; 19 | @import "./radio-button-group.less"; 20 | -------------------------------------------------------------------------------- /theme-default/src/input.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-input { 4 | position: relative; 5 | font-size: @normal-font-size; 6 | display: inline-block; 7 | 8 | &.fm-input-large { 9 | min-width: 210px; 10 | } 11 | 12 | &.fm-input-medium { 13 | min-width: 180px; 14 | } 15 | 16 | &.fm-input-small { 17 | min-width: 150px; 18 | } 19 | 20 | &.fm-input-mini { 21 | min-width: 120px; 22 | } 23 | 24 | &.fm-input-disabled { 25 | .fm-inner-input { 26 | background-color: @background-color-disabled; 27 | border-color: @border-regular-color; 28 | color: @color-placeholder; 29 | cursor: not-allowed; 30 | } 31 | } 32 | } 33 | 34 | .fm-inner-input { 35 | appearance: none; 36 | background-color: @background-color-white; 37 | background-image: none; 38 | border-radius: @border-radius-max; 39 | box-sizing: border-box; 40 | display: inline-block; 41 | font-size: inherit; 42 | color: @color-regular-1; 43 | line-height: 1; 44 | outline: none; 45 | width: 100%; 46 | padding: 0 15px; 47 | border: @border-width-base solid @border-regular-color; 48 | 49 | &:hover { 50 | border-color: @border-regular-color-1; 51 | } 52 | 53 | &.fm-inner-input-large { 54 | height: 40px; 55 | } 56 | 57 | &.fm-inner-input-medium { 58 | height: 36px; 59 | } 60 | 61 | &.fm-inner-input-small { 62 | font-size: 12px; 63 | height: 32px; 64 | } 65 | 66 | &.fm-inner-input-mini { 67 | font-size: 12px; 68 | height: 28px; 69 | } 70 | } 71 | 72 | .fm-inner-input-group-prepend { 73 | background-color: #f5f7fa; 74 | color: #909399; 75 | vertical-align: middle; 76 | display: table-cell; 77 | position: relative; 78 | border: 1px solid #dcdfe6; 79 | border-radius: 4px; 80 | padding: 0 20px; 81 | width: 1px; 82 | white-space: nowrap; 83 | border-right: 0; 84 | border-top-right-radius: 0; 85 | border-bottom-right-radius: 0; 86 | } 87 | 88 | .fm-inner-input-group-append { 89 | background-color: #f5f7fa; 90 | color: #909399; 91 | vertical-align: middle; 92 | display: table-cell; 93 | position: relative; 94 | border: 1px solid #dcdfe6; 95 | border-radius: 4px; 96 | padding: 0 20px; 97 | width: 1px; 98 | white-space: nowrap; 99 | border-left: 0; 100 | border-top-left-radius: 0; 101 | border-bottom-left-radius: 0; 102 | } 103 | 104 | .fm-input-group { 105 | display: inline-table; 106 | border-collapse: separate; 107 | 108 | & > .fm-inner-input { 109 | vertical-align: middle; 110 | display: table-cell; 111 | } 112 | 113 | &.fm-input-group-prepend { 114 | & > .fm-inner-input { 115 | border-top-left-radius: 0; 116 | border-bottom-left-radius: 0; 117 | } 118 | } 119 | 120 | &.fm-input-group-append { 121 | & > .fm-inner-input { 122 | border-top-right-radius: 0; 123 | border-bottom-right-radius: 0; 124 | } 125 | } 126 | } 127 | 128 | .fm-inner-suffix-icon { 129 | position: absolute; 130 | width: 20px; 131 | height: 100%; 132 | right: 5px; 133 | top: 0; 134 | text-align: center; 135 | font-size: @normal-font-size; 136 | line-height: 16px; 137 | cursor: pointer; 138 | } 139 | 140 | .fm-input-icon { 141 | display: inline-block; 142 | content: ''; 143 | width: 14px; 144 | height: 14px; 145 | vertical-align: middle; 146 | } 147 | 148 | .fm-input-clear-icon { 149 | position: relative; 150 | top: 50%; 151 | left: 50%; 152 | transform: translate(-50%, -50%); 153 | display: inline-block; 154 | content: ''; 155 | width: 14px; 156 | height: 14px; 157 | border-radius: 50%; 158 | background: #b8bcc5; 159 | margin-top: -7px; 160 | margin-right: 7px; 161 | 162 | &::before, &::after { 163 | top: 3px; 164 | position: absolute; 165 | content: ''; 166 | width: 1px; 167 | height: 7px; 168 | background: @background-color-white; 169 | } 170 | 171 | &::before { 172 | transform: rotate(45deg); 173 | } 174 | 175 | &::after { 176 | transform: rotate(-45deg); 177 | } 178 | 179 | &:hover { 180 | background: #909399; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /theme-default/src/loading.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-loading-wrap { 4 | position: absolute; 5 | z-index: @z-index-normal; 6 | top: 50%; 7 | left: 50%; 8 | transform: translate(-50%, -50%); 9 | background: @background-color-white; 10 | text-align: center; 11 | 12 | .fm-loading-text { 13 | font-weight: @font-weight-normal; 14 | color: @color-regular-2; 15 | font-size: @normal-font-size; 16 | } 17 | 18 | & > img { 19 | vertical-align: middle; 20 | padding: 5px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /theme-default/src/notification.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-notification-fade-enter, 4 | .fm-notification-fade-leave-active { 5 | opacity: 0; 6 | margin-right: -100%; 7 | } 8 | 9 | .fm-notification { 10 | position: fixed; 11 | z-index: @z-index-popper; 12 | top: 80px; 13 | right: 20px; 14 | background: @background-color-white; 15 | display: block; 16 | vertical-align: middle; 17 | border-radius: 6px; 18 | text-align: left; 19 | white-space: normal; 20 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15); 21 | border: 1px solid rgba(230, 230, 230, 1); 22 | padding: 10px 20px 18px; 23 | min-height: 80px; 24 | min-width: 380px; 25 | transition: all 0.3s; 26 | 27 | .fm-notification-header { 28 | .fm-notification-icon { 29 | font-size: 36px; 30 | line-height: 36px; 31 | vertical-align: middle; 32 | display: table-cell; 33 | 34 | &.success { 35 | color: @icon-color-success; 36 | } 37 | 38 | &.info { 39 | color: @icon-color-info; 40 | } 41 | 42 | &.warning { 43 | color: @icon-color-warning; 44 | } 45 | 46 | &.error { 47 | color: @icon-color-error; 48 | } 49 | } 50 | 51 | .fm-notification-title { 52 | font-size: @font-weight-mid; 53 | font-family: PingFangSC-Semibold, sans-serif; 54 | line-height: 22px; 55 | color: #333; 56 | vertical-align: middle; 57 | display: table-cell; 58 | padding-left: 5px; 59 | font-weight: bold; 60 | } 61 | } 62 | 63 | .fm-notification-message { 64 | font-size: 14px; 65 | font-family: PingFangSC-Regular, sans-serif; 66 | line-height: 20px; 67 | color: #666; 68 | display: block; 69 | max-width: 305px; 70 | vertical-align: middle; 71 | word-break: break-word; 72 | margin-left: 40px; 73 | } 74 | 75 | .fm-notification-close-icon { 76 | position: absolute; 77 | top: 10px; 78 | right: 10px; 79 | cursor: pointer; 80 | font-size: 24px; 81 | line-height: 24px; 82 | width: 24px; 83 | height: 24px; 84 | border-radius: 50%; 85 | color: #767676; 86 | text-align: center; 87 | vertical-align: middle; 88 | 89 | &:hover { 90 | background: #f5f5f5; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /theme-default/src/option.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/theme-default/src/option.less -------------------------------------------------------------------------------- /theme-default/src/radio-button-group.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-radio-button-group { 4 | display: inline-block; 5 | vertical-align: middle; 6 | font-size: 0; 7 | } 8 | 9 | .fm-radio-button { 10 | display: inline-block; 11 | vertical-align: middle; 12 | font-size: @min-font-size; 13 | cursor: pointer; 14 | color: @color-regular-1; 15 | line-height: 25px; 16 | // height: 25px; 17 | text-align: center; 18 | padding-left: 5px; 19 | padding-right: 5px; 20 | border-top: @border-width-base solid @border-regular-color; 21 | border-bottom: @border-width-base solid @border-regular-color; 22 | border-left: @border-width-base solid @border-regular-color; 23 | background: @background-color-white; 24 | 25 | &:first-child { 26 | border-top-left-radius: @border-width-normal; 27 | border-bottom-left-radius: @border-width-normal; 28 | } 29 | 30 | &:last-child { 31 | border-right: @border-width-base solid @border-regular-color; 32 | border-top-right-radius: @border-width-normal; 33 | border-bottom-right-radius: @border-width-normal; 34 | } 35 | 36 | &.active { 37 | border: none; 38 | border-top: @border-width-base solid @background-color-light-5; 39 | border-bottom: @border-width-base solid @background-color-light-5; 40 | border-radius: @border-width-normal; 41 | background-color: @background-color-light-5; 42 | color: @color-white; 43 | 44 | + .fm-radio-button { 45 | border-left: none; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /theme-default/src/radio.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-radio { 4 | position: relative; 5 | display: inline-block; 6 | color: @color-regular-1; 7 | font-size: @normal-font-size; 8 | cursor: pointer; 9 | white-space: nowrap; 10 | user-select: none; 11 | 12 | &.is-disabled { 13 | cursor: not-allowed; 14 | 15 | .fm-radio-label { 16 | color: @color-regular; 17 | } 18 | } 19 | } 20 | 21 | .fm-radio-input { 22 | position: relative; 23 | top: -1px; 24 | display: inline-block; 25 | box-sizing: border-box; 26 | background: @background-color-white; 27 | line-height: 1; 28 | white-space: nowrap; 29 | outline: none; 30 | cursor: pointer; 31 | border-radius: 50%; 32 | vertical-align: middle; 33 | 34 | &.fm-radio-small { 35 | width: 16px; 36 | height: 16px; 37 | } 38 | 39 | .fm-radio-inner { 40 | position: relative; 41 | display: inline-block; 42 | box-sizing: border-box; 43 | width: 100%; 44 | height: 100%; 45 | z-index: @z-index-normal; 46 | border-radius: 50%; 47 | border: @border-width-base solid @border-regular-color; 48 | 49 | &::after { 50 | position: absolute; 51 | content: ''; 52 | left: 50%; 53 | top: 50%; 54 | width: 6px; 55 | height: 6px; 56 | border-radius: 50%; 57 | background-color: #fff; 58 | transform: translate(-50%, -50%) scale(0); 59 | transition: transform .15s cubic-bezier(.71, -.46, .88, .6); 60 | } 61 | } 62 | 63 | &.fm-radio-medium { 64 | width: 20px; 65 | height: 20px; 66 | 67 | .fm-radio-inner::after { 68 | width: 8px; 69 | height: 8px; 70 | } 71 | } 72 | 73 | &.fm-radio-large { 74 | width: 24px; 75 | height: 24px; 76 | 77 | .fm-radio-inner::after { 78 | width: 10px; 79 | height: 10px; 80 | } 81 | } 82 | 83 | &.is-disabled { 84 | background: @background-color-disabled; 85 | cursor: not-allowed; 86 | 87 | .fm-radio-inner { 88 | border: @border-width-base solid #edeef2; 89 | } 90 | } 91 | 92 | &.is-checked { 93 | .fm-radio-inner { 94 | background: @background-color-light-1; 95 | border-color: @border-primary-color; 96 | 97 | &::after { 98 | transform: translate(-50%, -50%) scale(1); 99 | } 100 | } 101 | 102 | &.is-disabled { 103 | cursor: not-allowed; 104 | 105 | .fm-radio-inner { 106 | background: #eceff5; 107 | border: @border-width-base solid #edeef2; 108 | 109 | &::after { 110 | transform: translate(-50%, -50%) scale(1); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | .fm-radio-original { 118 | opacity: 0; 119 | outline: none; 120 | position: absolute; 121 | margin: 0; 122 | width: 0; 123 | height: 0; 124 | left: -999px; 125 | } 126 | 127 | .fm-radio-label { 128 | display: inline-block; 129 | padding-left: 5px; 130 | line-height: 1; 131 | font-size: @normal-font-size; 132 | } 133 | -------------------------------------------------------------------------------- /theme-default/src/rate.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "fmiconfont"; 3 | src: url('//at.alicdn.com/t/font_498037_qkagtul9gjozjjor.eot?t=1512529692256'); 4 | /* IE9 */ 5 | src: 6 | url('//at.alicdn.com/t/font_498037_qkagtul9gjozjjor.eot?t=1512529692256#iefix') format('embedded-opentype'), 7 | /* IE6-IE8 */ 8 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAXgAAsAAAAACGQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW8kf1Y21hcAAAAYAAAABkAAABnM7EabpnbHlmAAAB5AAAAfgAAAIodwKuDWhlYWQAAAPcAAAAMQAAADYPukQyaGhlYQAABBAAAAAgAAAAJAffA4JobXR4AAAEMAAAABAAAAAQD+n//WxvY2EAAARAAAAACgAAAAoBigDObWF4cAAABEwAAAAfAAAAIAETAF1uYW1lAAAEbAAAAUUAAAJtPlT+fXBvc3QAAAW0AAAAKwAAAEAlyByKeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sU4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDzjYG7438AQw9zI0AIUZgTJAQAkyQx7eJzFkNENwCAIRI9qDWk6Sj+bDtSvjuDErGEP9McJPPMELkQMAHYAiVwkA/JB4HrpSvgJR/gZD2vl2RirFdPWpswl0aGRbf6yFCyTrBs964z7HpXvuw74RSsd36tpB+kHFucPq3icHY+9b9NAGMbvvcudv2K78dk+x26cT9uFpokSgjMgEhVYihiQmBgZOjCAxFQhZUgHKEgdstEZIfFPVGJDiAqpAyMSLWSBHTEAF6y+eob3GR7p90MUodU5OSYB4mgDDdAtdBchYJvQsnANmtmohzfBa1JPuBbJ2llTabd65DqIFnP9YT5KBVOYDRbEcKU5zLMezuDqaIKvwdCvAVSj8J6TrDtkAXqQxc/kbfwavHp73Z5syZ3u1B02uLpXdpyq4xyqjFIV45JtwSPha1TTmXxD7dA7rl/CdShXs/DOfbMROQ9ejB7XEqEBzOfAo4b1dloJK0Vmoc+dqrJmqkFotjsu7C2NgJdr6XdUHC5cX5EV2UUR6iMkWlk65lmajxOeDwWnMdjAfZYllBHhKyTJBSXpFJI0M4ApnxXFwOMfumbBjYVSUxZw09L0n2MsTHkmv9LDUxUGAIYBMMDaqTwrs/k+K+/SYrC91CPSh45pyi99EulL+c6qsKMjgrdPVFN+O2DsAGJTPbmMDSjJv0bBimD1bzUrEbKPsqJcMMYgLjAtEAVpH9KWDaywyNPiL0QmAE9mutCfGm0fuKtYH9ZMvEW488vrur8dTgbYtN9bqluIdvBDKHEu/3gR7ZLA/ljpCnnu2fpLXX+u2R7Eout8sgOyQcP/7Qph8HicY2BkYGAA4mWRRg7x/DZfGbhZGEDgmm+DDIz+//d/DQszcyOQy8HABBIFABwcCqwAAAB4nGNgZGBgbvjfwBDDwvD/7/+/LMwMQBEUwAIAoJEGaAQAAAAD6QAABAAAAAQA//0AAAAAAHYAzgEUAAB4nGNgZGBgYGEIZGBlAAEmIOYCQgaG/2A+AwAREgFxAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgIWRiZGZkYWRlYGxgqO4JLFINz8tjR3CyGNgAABOMQaRAA==') format('woff'), 9 | url('//at.alicdn.com/t/font_498037_qkagtul9gjozjjor.ttf?t=1512529692256') format('truetype'), 10 | /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 11 | url('//at.alicdn.com/t/font_498037_qkagtul9gjozjjor.svg?t=1512529692256#iconfont') format('svg'); 12 | /* iOS 4.1- */ 13 | } 14 | 15 | .fm-iconfont { 16 | font-family: "fmiconfont" !important; 17 | font-size: 20px; 18 | margin-right: 6px; 19 | transition: .3s; 20 | color: #ffbe58; 21 | font-style: normal; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .fm-icon-star-off:before { 27 | content: "\e606"; 28 | } 29 | 30 | .fm-icon-star-on:before { 31 | content: "\e608"; 32 | } 33 | .fm-rate-icon-item { 34 | font-size: 0; 35 | display: inline-block; 36 | position: relative; 37 | } 38 | .fm-score { 39 | font-size: 16px; 40 | } 41 | -------------------------------------------------------------------------------- /theme-default/src/select.less: -------------------------------------------------------------------------------- 1 | @import 'common/var.less'; 2 | 3 | .fm-zoom-in-top-enter-active, 4 | .fm-zoom-in-top-leave-active { 5 | opacity: 1; 6 | transform: scaleY(1); 7 | transition: @all-transition; 8 | transform-origin: center top; 9 | } 10 | 11 | .fm-zoom-in-top-enter, 12 | .fm-zoom-in-top-leave-active { 13 | opacity: 0; 14 | transform: scaleY(0); 15 | } 16 | 17 | .fm-select { 18 | position: relative; 19 | display: inline-block; 20 | vertical-align: top; 21 | user-select: none; 22 | min-width: 180px; 23 | height: 40px; 24 | cursor: pointer; 25 | background: @background-color-white; 26 | border-radius: @border-radius-normal; 27 | border-bottom: @border-width-base solid @background-color-light-4; 28 | 29 | &.is-disabled { 30 | background: @background-color-disabled; 31 | cursor: not-allowed; 32 | padding-left: 6px; 33 | 34 | &:hover { 35 | border-color: @background-color-light-4; 36 | } 37 | 38 | .fm-selected-trigger { 39 | cursor: not-allowed; 40 | } 41 | } 42 | 43 | .fm-selected-trigger { 44 | position: relative; 45 | display: block; 46 | font-size: @mid-font-size; 47 | cursor: pointer; 48 | margin: 0; 49 | overflow: hidden; 50 | padding: 9px 20px 9px 0; 51 | line-height: 22px; 52 | } 53 | 54 | .fm-select-icon { 55 | position: absolute; 56 | top: 50%; 57 | margin-top: -2px; 58 | right: 6px; 59 | content: ''; 60 | width: 0; 61 | height: 0; 62 | border: 6px solid transparent; 63 | border-top-color: #767676; 64 | transition: all .3s linear; 65 | transform-origin: center; 66 | 67 | &.active { 68 | margin-top: -8px; 69 | transform: rotate(180deg); 70 | } 71 | } 72 | } 73 | 74 | .fm-selectable-list-wrap { 75 | position: absolute; 76 | width: 100%; 77 | z-index: @z-index-popper; 78 | background-color: @background-color-white; 79 | box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1); 80 | box-sizing: border-box; 81 | margin: 5px 0; 82 | overflow: hidden; 83 | border-radius: 6px; 84 | 85 | p { 86 | padding: 6px 0; 87 | margin: 0; 88 | text-align: center; 89 | color: @color-regular-3; 90 | font-size: @normal-font-size; 91 | } 92 | } 93 | 94 | .fm-selectable-list { 95 | position: relative; 96 | padding: 5px 0; 97 | margin: 0; 98 | box-sizing: border-box; 99 | max-height: 275px; 100 | width: 100%; 101 | overflow-x: hidden; 102 | overflow-y: auto; 103 | font-size: @normal-font-size; 104 | 105 | .fm-select-option { 106 | position: relative; 107 | white-space: nowrap; 108 | overflow: hidden; 109 | text-overflow: ellipsis; 110 | cursor: pointer; 111 | height: 40px; 112 | width: 100%; 113 | padding: 5px 10px; 114 | line-height: 22px; 115 | font-size: 16px; 116 | box-sizing: border-box; 117 | color: #333; 118 | 119 | &.hover { 120 | background-color: #f0f0f0; 121 | } 122 | 123 | &.selected { 124 | position: relative; 125 | color: @color-primary; 126 | 127 | i { 128 | position: absolute; 129 | right: 16px; 130 | top: 8px; 131 | } 132 | } 133 | } 134 | } 135 | 136 | .focus-border { 137 | position: absolute; 138 | bottom: 0; 139 | left: 0; 140 | width: 0; 141 | height: 1px; 142 | transition: 0.4s; 143 | } 144 | 145 | .focus-border-active { 146 | width: 100%; 147 | height: 2px; 148 | transition: 0.4s; 149 | background-color: @color-primary; 150 | } 151 | -------------------------------------------------------------------------------- /theme-default/src/switch.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-switch { 4 | display: inline-block; 5 | position: relative; 6 | box-sizing: border-box; 7 | font-size: @normal-font-size; 8 | line-height: 22px; 9 | height: 22px; 10 | min-width: 46px; 11 | vertical-align: middle; 12 | cursor: pointer; 13 | 14 | .fm-switch-input { 15 | display: none; 16 | } 17 | } 18 | 19 | .fm-switch-label { 20 | position: absolute; 21 | z-index: @z-index-normal; 22 | top: 0; 23 | right: 0; 24 | bottom: 0; 25 | left: 0; 26 | border-radius: 12px; 27 | background: @background-color-disabled; 28 | transition: background .3s; 29 | 30 | &.is-checked { 31 | background: @background-color-primary; 32 | } 33 | 34 | &.is-disabled { 35 | cursor: not-allowed; 36 | opacity: 0.8; 37 | } 38 | } 39 | 40 | .fm-switch-btn { 41 | position: absolute; 42 | z-index: @z-index-regular; 43 | top: 50%; 44 | margin-top: -8px; 45 | width: 16px; 46 | height: 16px; 47 | border-radius: 50%; 48 | background: @background-color-white; 49 | transition: left .3s; 50 | } 51 | -------------------------------------------------------------------------------- /theme-default/src/tab-panel.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmfe/fm-vue-ui/abf32d041b5dce268b273309eae9152a802be076/theme-default/src/tab-panel.less -------------------------------------------------------------------------------- /theme-default/src/tabs.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-tabs-wrap { 4 | position: relative; 5 | background-color: @background-color-white; 6 | } 7 | 8 | .fm-tabs-nav-list { 9 | position: relative; 10 | border-bottom: @border-width-base solid @border-regular-color-3; 11 | height: 45px; 12 | font-size: 0; 13 | } 14 | 15 | .fm-tabs-content { 16 | position: relative; 17 | overflow: hidden; 18 | z-index: @z-index-normal; 19 | padding-top: 15px; 20 | background-color: @background-color-white; 21 | } 22 | 23 | .fm-tabs-nav-item { 24 | position: relative; 25 | display: inline-block; 26 | margin-right: 1px; 27 | width: 110px; 28 | font-size: 0; 29 | color: @color-regular-2; 30 | font-weight: @font-weight-max; 31 | text-align: center; 32 | cursor: pointer; 33 | overflow: hidden; 34 | &:hover { 35 | color: @color-primary; 36 | } 37 | &.fm-tabs-nav-panel { 38 | height: 43px; 39 | line-height: 43px; 40 | font-size: @normal-font-size; 41 | &:after { 42 | content: ''; 43 | position: absolute; 44 | top: 50%; 45 | right: -1px; 46 | height: 12px; 47 | border: none; 48 | width: 1px; 49 | margin-top: -6px; 50 | background-color: @background-color-light-4; 51 | } 52 | &:last-child:after { 53 | display: none; 54 | } 55 | &.active { 56 | height: 45px; 57 | color: @color-primary; 58 | border-bottom: @border-width-normal solid @color-primary; 59 | } 60 | } 61 | &.fm-tabs-nav-card { 62 | height: 45px; 63 | line-height: 45px; 64 | margin-right: 0; 65 | font-size: @normal-font-size; 66 | border: @border-width-base solid @border-regular-color-3; 67 | border-right: none; 68 | &:after { 69 | display: none; 70 | } 71 | &:first-child { 72 | border-top-left-radius: @border-radius-max; 73 | } 74 | &:last-child { 75 | border-right: @border-width-base solid @border-regular-color-3; 76 | border-top-right-radius: @border-radius-max; 77 | } 78 | &.active { 79 | color: @color-primary; 80 | border-bottom-color: @border-white-color; 81 | background-color: @background-color-white; 82 | } 83 | } 84 | .fm-tab-delete-icon { 85 | position: absolute; 86 | top: 2px; 87 | right: 2px; 88 | width: 12px; 89 | height: 12px; 90 | cursor: pointer; 91 | background-color: @background-color-light-1; 92 | &::before, &::after { 93 | position: absolute; 94 | top: 3px; 95 | content: ''; 96 | height: 7px; 97 | width: 1px; 98 | background-color: @background-color-white; 99 | } 100 | &::before { 101 | transform: rotate(45deg); 102 | } 103 | &::after { 104 | transform: rotate(-45deg); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /theme-default/src/toast.less: -------------------------------------------------------------------------------- 1 | @import "./common/var.less"; 2 | 3 | .fm-toast-fade-enter, 4 | .fm-toast-fade-leave-active { 5 | opacity: 0; 6 | margin-top: -100%; 7 | } 8 | 9 | .fm-toast { 10 | position: fixed; 11 | z-index: @z-index-popper; 12 | top: 60px; 13 | left: 50%; 14 | background: @background-color-white; 15 | transform: translateX(-50%); 16 | display: block; 17 | vertical-align: middle; 18 | border-radius: 6px; 19 | text-align: left; 20 | white-space: normal; 21 | min-width: 140px; 22 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15); 23 | border: 1px solid rgba(230, 230, 230, 1); 24 | padding: 12px 20px; 25 | transition: all 0.3s; 26 | box-sizing: border-box; 27 | 28 | .fm-toast-icon { 29 | font-size: 28px; 30 | vertical-align: middle; 31 | display: table-cell; 32 | 33 | &.success { 34 | color: @icon-color-success; 35 | } 36 | 37 | &.info { 38 | color: @icon-color-info; 39 | } 40 | 41 | &.warning { 42 | color: @icon-color-warning; 43 | } 44 | 45 | &.error { 46 | color: @icon-color-error; 47 | } 48 | } 49 | 50 | .fm-toast-message { 51 | font-size: 14px; 52 | color: #333; 53 | line-height: 20px; 54 | display: table-cell; 55 | max-width: 300px; 56 | vertical-align: middle; 57 | word-break: break-word; 58 | padding-left: 5px; 59 | } 60 | } 61 | --------------------------------------------------------------------------------