├── .editorconfig ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE.md └── dependabot.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── docs-translations ├── en-US │ └── README.md └── zh-TW │ └── README.md ├── gulpfile.js ├── package.json ├── project ├── _var.scss ├── demo.scss ├── logic │ └── _logic.scss └── widget │ ├── _button.scss │ ├── _dialog.scss │ ├── _dropdownMenu.scss │ ├── _mask.scss │ ├── _tab.scss │ ├── _textField.scss │ └── _widget.scss ├── qmui.config.js ├── qmui.merge.rule.js ├── qmui ├── _function.scss ├── _qmui.scss ├── _reset.scss └── mixin │ ├── _adaptation.scss │ ├── _mixin.scss │ └── tool │ ├── _calculate.scss │ ├── _effect.scss │ ├── _enhance.scss │ └── _tool.scss ├── stylelint.config.js ├── workflow ├── Mix.js ├── TimeFormat.js ├── Util.js ├── basicTasks │ ├── clean.js │ ├── include.js │ ├── list.js │ ├── merge.js │ ├── proxy.js │ ├── readToolMethod.js │ ├── reload.js │ ├── sass.js │ ├── server.js │ └── version.js ├── calculateMargin.js ├── initProject.js ├── start.js └── watch.js └── yarn.lock /.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 = false 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | charset = utf-8 14 | 15 | # Tab indentation (no size specified) 16 | indent_style = space 17 | indent_size = 4 18 | 19 | # Others 20 | trim_trailing_whitespace = true 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es6': true 6 | }, 7 | 'extends': 'eslint:recommended', 8 | 'parserOptions': { 9 | 'sourceType': 'module' 10 | }, 11 | 'rules': { 12 | 'accessor-pairs': 'error', 13 | 'array-bracket-spacing': [ 14 | 'error', 15 | 'never' 16 | ], 17 | 'array-callback-return': 'error', 18 | 'arrow-body-style': 'error', 19 | 'arrow-parens': 'error', 20 | 'arrow-spacing': 'error', 21 | 'block-scoped-var': 'error', 22 | 'block-spacing': 'error', 23 | 'brace-style': [ 24 | 'error', 25 | '1tbs' 26 | ], 27 | 'callback-return': 'error', 28 | 'camelcase': 'error', 29 | 'comma-spacing': 'off', 30 | 'comma-style': [ 31 | 'error', 32 | 'last' 33 | ], 34 | 'complexity': 'error', 35 | 'computed-property-spacing': [ 36 | 'error', 37 | 'never' 38 | ], 39 | 'consistent-return': 'off', 40 | 'consistent-this': 'error', 41 | 'curly': 'error', 42 | 'default-case': 'error', 43 | 'dot-location': [ 44 | 'error', 45 | 'property' 46 | ], 47 | 'dot-notation': 'error', 48 | 'eol-last': 'error', 49 | 'eqeqeq': 'error', 50 | 'func-names': 'off', 51 | 'func-style': 'off', 52 | 'generator-star-spacing': 'error', 53 | 'global-require': 'off', 54 | 'guard-for-in': 'off', 55 | 'handle-callback-err': 'error', 56 | 'id-blacklist': 'error', 57 | 'id-length': ['error', {'exceptions': ['_']}], 58 | 'id-match': 'error', 59 | 'indent': 'error', 60 | 'init-declarations': 'off', 61 | 'jsx-quotes': 'error', 62 | 'key-spacing': 'error', 63 | 'keyword-spacing': 'off', 64 | 'linebreak-style': [ 65 | 'error', 66 | 'unix' 67 | ], 68 | 'lines-around-comment': 'off', 69 | 'max-depth': [2, 10], 70 | 'max-len': 'off', 71 | 'max-nested-callbacks': 'error', 72 | 'max-params': 'error', 73 | 'max-statements': 'off', 74 | 'max-statements-per-line': 'error', 75 | 'new-cap': 'error', 76 | 'new-parens': 'error', 77 | 'newline-after-var': 'off', 78 | 'newline-before-return': 'off', 79 | 'newline-per-chained-call': 'off', 80 | 'no-alert': 'error', 81 | 'no-array-constructor': 'error', 82 | 'no-bitwise': 'error', 83 | 'no-caller': 'error', 84 | 'no-catch-shadow': 'error', 85 | 'no-confusing-arrow': 'error', 86 | 'no-continue': 'error', 87 | 'no-div-regex': 'error', 88 | 'no-duplicate-imports': 'error', 89 | 'no-else-return': 'error', 90 | 'no-empty-function': 'error', 91 | 'no-eq-null': 'error', 92 | 'no-eval': 'error', 93 | 'no-extend-native': 'error', 94 | 'no-extra-bind': 'error', 95 | 'no-extra-label': 'error', 96 | 'no-extra-parens': 'error', 97 | 'no-floating-decimal': 'error', 98 | 'no-implicit-coercion': 'error', 99 | 'no-implicit-globals': 'error', 100 | 'no-implied-eval': 'error', 101 | 'no-inline-comments': 'off', 102 | 'no-inner-declarations': [ 103 | 'error', 104 | 'functions' 105 | ], 106 | 'no-invalid-this': 'error', 107 | 'no-iterator': 'error', 108 | 'no-label-var': 'error', 109 | 'no-labels': 'error', 110 | 'no-lone-blocks': 'error', 111 | 'no-lonely-if': 'error', 112 | 'no-loop-func': 'error', 113 | 'no-magic-numbers': 'off', 114 | 'no-mixed-requires': 'off', 115 | 'no-multi-spaces': 'off', 116 | 'no-multi-str': 'error', 117 | 'no-multiple-empty-lines': 'error', 118 | 'no-native-reassign': 'error', 119 | 'no-negated-condition': 'off', 120 | 'no-nested-ternary': 'error', 121 | 'no-new': 'error', 122 | 'no-new-func': 'error', 123 | 'no-new-object': 'error', 124 | 'no-new-require': 'error', 125 | 'no-new-wrappers': 'error', 126 | 'no-octal-escape': 'error', 127 | 'no-param-reassign': 'off', 128 | 'no-path-concat': 'error', 129 | 'no-plusplus': [ 130 | 'error', 131 | { 132 | 'allowForLoopAfterthoughts': true 133 | } 134 | ], 135 | 'no-process-env': 'error', 136 | 'no-process-exit': 'error', 137 | 'no-proto': 'error', 138 | 'no-restricted-globals': 'error', 139 | 'no-restricted-imports': 'error', 140 | 'no-restricted-modules': 'error', 141 | 'no-restricted-syntax': 'error', 142 | 'no-return-assign': 'error', 143 | 'no-script-url': 'error', 144 | 'no-self-compare': 'error', 145 | 'no-sequences': 'off', 146 | 'no-shadow': 'error', 147 | 'no-shadow-restricted-names': 'error', 148 | 'no-spaced-func': 'error', 149 | 'no-sync': 'off', 150 | 'no-ternary': 'off', 151 | 'no-throw-literal': 'error', 152 | 'no-trailing-spaces': 'off', 153 | 'no-undef-init': 'error', 154 | 'no-undefined': 'error', 155 | 'no-underscore-dangle': 'off', 156 | 'no-unmodified-loop-condition': 'error', 157 | 'no-unneeded-ternary': 'error', 158 | 'no-unsafe-finally': 'error', 159 | 'no-unused-expressions': 'off', 160 | 'no-unused-vars': ['error', { 'varsIgnorePattern': 'reload' }], 161 | 'no-use-before-define': 'off', 162 | 'no-useless-call': 'error', 163 | 'no-useless-computed-key': 'error', 164 | 'no-useless-concat': 'error', 165 | 'no-useless-constructor': 'error', 166 | 'no-useless-escape': 'error', 167 | 'no-var': 'off', 168 | 'no-void': 'error', 169 | 'no-warning-comments': 'off', 170 | 'no-whitespace-before-property': 'error', 171 | 'no-with': 'error', 172 | 'object-curly-spacing': [ 173 | 'error', 174 | 'never' 175 | ], 176 | 'object-property-newline': 'error', 177 | 'object-shorthand': 'off', 178 | 'one-var': 'off', 179 | 'one-var-declaration-per-line': 'error', 180 | 'operator-assignment': 'error', 181 | 'operator-linebreak': 'error', 182 | 'padded-blocks': 'off', 183 | 'prefer-arrow-callback': 'off', 184 | 'prefer-const': 'error', 185 | 'prefer-reflect': [2, { exceptions: ['delete', 'apply'] }], 186 | 'prefer-rest-params': 'off', 187 | 'prefer-spread': 'off', 188 | 'prefer-template': 'off', 189 | 'quote-props': 'off', 190 | 'quotes': [ 191 | 'error', 192 | 'single' 193 | ], 194 | 'radix': 'error', 195 | 'require-jsdoc': 'off', 196 | 'require-yield': 'error', 197 | 'semi': 'off', 198 | 'semi-spacing': [ 199 | 'error', 200 | { 201 | 'after': true, 202 | 'before': false 203 | } 204 | ], 205 | 'sort-imports': 'error', 206 | 'sort-vars': 'off', 207 | 'space-before-blocks': 'off', 208 | 'space-before-function-paren': 'off', 209 | 'space-in-parens': [ 210 | 'error', 211 | 'never' 212 | ], 213 | 'space-infix-ops': 'error', 214 | 'space-unary-ops': 'error', 215 | 'spaced-comment': [ 216 | 'error', 217 | 'always' 218 | ], 219 | 'strict': 'error', 220 | 'template-curly-spacing': 'error', 221 | 'valid-jsdoc': 'error', 222 | 'vars-on-top': 'off', 223 | 'wrap-iife': 'error', 224 | 'wrap-regex': 'error', 225 | 'yield-star-spacing': 'error', 226 | 'yoda': [ 227 | 'error', 228 | 'never' 229 | ] 230 | } 231 | }; 232 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 运行环境 ### 2 | 3 | - [x] 操作系统版本:`macOS (10.x)` / `Windows (7/8/10)` / `Linux` / `其他(自行填写具体系统)` 4 | - [x] QMUI Web 版本:`1.x.x` 5 | - [x] Node.js 版本:`4.x.x` 6 | 7 | ### 具体问题描述 ### 8 | 9 | #### 问题截图 #### 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Others 47 | .DS_Store 48 | .idea 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | install: 3 | - yarn 4 | cache: 5 | directories: 6 | - node_modules 7 | node_js: 8 | - "14" 9 | before_install: 10 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.15.2 11 | - export PATH="$HOME/.yarn/bin:$PATH" 12 | before_script: 13 | - cp qmui.config.js ../qmui.config.js 14 | - yarn install 15 | script: 16 | - gulp initProject 17 | - gulp list 18 | - gulp sass 19 | - gulp include 20 | - gulp clean 21 | - gulp merge 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Tencent is pleased to support the open source community by making QMUI Web available. 2 | Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 3 | If you have downloaded a copy of the QMUI Web binary from Tencent, please note that the QMUI Web binary 4 | is licensed under the MIT License. 5 | If you have downloaded a copy of the QMUI Web source code from Tencent, please note that QMUI Web source 6 | code is licensed under the MIT License. Your integration of QMUI Web into your own projects may require compliance with the MIT License. 7 | A copy of the MIT License is included in this file. 8 | 9 | 10 | Terms of the MIT License: 11 | -------------------------------------------------------------------- 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 14 | associated documentation files (the "Software"), to deal in the Software without restriction, 15 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 16 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 21 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | Other dependencies and licenses: 26 | 27 | Open Source Software Licensed Under Apache -2.0: 28 | ---------------------------------------------------------------------------------------- 29 | 1. browser-sync v. 2.18.12 30 | Copyright (c) 2015 Shane Osbourne 31 | 32 | Terms of the Apache -2.0 License: 33 | -------------------------------------------------------------------- 34 | Licensed under the Apache License, Version 2.0 (the "License"); 35 | You may not use this file except in compliance with the License. 36 | You may obtain a copy of the License at 37 | 38 | http://www.apache.org/licenses/LICENSE-2.0 39 | 40 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and limitations under the License. 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Banner 3 |

4 | 5 | # QMUI Web [![Version Number](https://img.shields.io/npm/v/generator-qmui.svg?style=flat)](https://github.com/Tencent/QMUI_Web/ "Version Number") 6 | > 一个旨在提高 UI 开发效率、快速产生项目 UI 的前端框架 7 | > 8 | > 官网:[http://qmuiteam.com/web](http://qmuiteam.com/web) 9 | > 10 | > 下载 Demo:[https://github.com/QMUI/QMUIDemo_Web/releases](https://github.com/QMUI/QMUIDemo_Web/releases) 11 | 12 | [[English]](https://github.com/Tencent/QMUI_Web/tree/master/docs-translations/en-US/README.md) / [[简体中文]](https://github.com/Tencent/QMUI_Web/blob/master/README.md) / [[繁體中文]](//github.com/Tencent/QMUI_Web/tree/master/docs-translations/zh-TW/README.md) 13 | 14 | [![Build Status](https://travis-ci.org/Tencent/QMUI_Web.svg?branch=master)](https://travis-ci.org/Tencent/QMUI_Web "Build Status") 15 | [![Build status](https://ci.appveyor.com/api/projects/status/1h6de3rq6x45nnse?svg=true 16 | )](https://ci.appveyor.com/project/kayo5994/qmui-web) 17 | [![QMUI Team Name](https://img.shields.io/badge/Team-QMUI-brightgreen.svg?style=flat)](https://github.com/QMUI "QMUI Team") 18 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT "Feel free to contribute.") 19 | 20 | QMUI Web 是一个专注 Web UI 开发,帮助开发者快速实现特定的一整套设计的框架。框架主要由一个强大的 SASS 方法合集与内置的工作流构成。通过 QMUI Web,开发者可以很轻松地提高 Web UI 开发的效率,同时保持了项目的高可维护性与稳健。如果你需要方便地控制项目的整体样式,或者需要应对频繁的界面变动,那么 QMUI Web 框架将会是你最好的解决方案。 21 | 22 | ## 功能特性 23 | 24 | ### 基础配置与组件 25 | 通过内置的公共组件和对应的 SASS 配置表,你只需修改简单的配置即可快速实现所需样式的组件。([QMUI SASS 配置表和公共组件如何帮忙开发者快速搭建项目基础 UI?](https://github.com/Tencent/QMUI_Web/wiki/Q&A#qmui-sass-%E9%85%8D%E7%BD%AE%E8%A1%A8%E5%92%8C%E5%85%AC%E5%85%B1%E7%BB%84%E4%BB%B6%E5%A6%82%E4%BD%95%E5%B8%AE%E5%BF%99%E5%BC%80%E5%8F%91%E8%80%85%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%A1%80-ui)) 26 | 27 | ### SASS 增强支持 28 | QMUI Web 包含70个 SASS mixin/function/extend,涉及布局、外观、动画、设备适配、数值计算以及 SASS 原生能力增强等多个方面,可以大幅提升开发效率。 29 | 30 | ### 完善的内置工作流 31 | QMUI Web 内置的工作流拥有从初始化项目到变更文件的各种自动化处理,包含了模板引擎,雪碧图处理,图片集中管理与自动压缩,静态资源合并、压缩与变更以及冗余文件清理等功能。 32 | 33 | ### 扩展组件 34 | QMUI Web 除了内置的公共组件外,还通过扩展的方式提供了常用的扩展组件,如等高左右双栏,文件上传按钮,树状选择菜单。 35 | 36 | ## 环境配置 37 | 请确保安装 [Node.js](https://nodejs.org/)(建议 14.0 或以上版本),并用以下命令全局安装 gulp: 38 | 39 | ```bash 40 | #安装 gulp 41 | npm install --global gulp 42 | ``` 43 | 44 | ## 快速开始 45 | 推荐使用 [Yeoman](http://yeoman.io/) 脚手架 [generator-qmui](https://github.com/QMUI/generator-qmui) 安装和配置 QMUI Web。该工具可以帮助你完成 QMUI Web 的所有安装和配置。 46 | 47 | ```bash 48 | #安装 Yeoman,如果本地已安装可以忽略 49 | npm install -g yo 50 | #安装 QMUI 的模板 51 | npm install -g generator-qmui 52 | #在项目根目录执行以下命令 53 | yo qmui 54 | ``` 55 | 效果预览 56 | 57 | ### 完成后生成的项目目录结构 58 | ```bash 59 | 项目根目录 60 | ├─public // 静态资源目录,由 gulp 生成 61 | │ ├─js // 静态资源 js 文件 62 | │ └─style // 静态资源 UI 文件 63 | │ ├─css // 静态资源 css 文件 64 | │ └─images // 静态资源 images 文件 65 | ├─UI_dev // 实际进行开发的样式目录 66 | │ ├─project // 项目相关 SASS 与 images 文件,由 gulp 生成 67 | │ │ ├─images // 项目相关图片文件 68 | │ │ ├─logic // 项目相关逻辑样式 69 | │ │ └─widget // 项目相关公共组件样式 70 | │ └─qmui_web // QMUI Web 主源码应放置在这一层目录 71 | ├─UI_html // 静态模板目录 72 | └─UI_html_result // 静态模板 gulp 处理后的版本,用于前端拼接最终的模板 73 | ``` 74 | 75 | 对于需要有更强定制性的开发者,请参考[创建新项目(高级)](http://qmuiteam.com/web/page/start.html#qui_createProject) 76 | 77 | ## 工作流任务列表 78 | 79 | ```bash 80 | #在 UI_dev/qmui_web 中执行以下命令可以查看工作流的任务列表及说明 81 | gulp list 82 | ``` 83 | 84 | 也可以查看文档中的[详细说明](http://qmuiteam.com/web/page/scaffold.html)。 85 | 86 | ## 完善框架 87 | 如果有意见反馈或者功能建议,欢迎创建 [Issue](https://github.com/Tencent/QMUI_Web/issues) 或发送 [Pull Request](https://github.com/Tencent/QMUI_Web/pulls),调试与修改框架请先阅读[文档](http://qmuiteam.com/web/page/start.html#qui_frameworkImprove),感谢你的支持和贡献。 88 | 89 | 设计稿 Sketch 源文件可在 [Dribbble](https://dribbble.com/shots/2895907-QMUI-Logo) 上获取。 90 | 91 | ## QMUI Web Desktop 92 | 93 | 推荐配合使用的桌面 App:[QMUI Web Desktop](https://github.com/Tencent/QMUI_Web_desktop)。它可以管理基于 QMUI Web 进行开发的项目,通过 GUI 界面处理 QMUI Web 的服务开启/关闭,使框架的使用变得更加便捷,并提供了编译提醒,出错提醒,进程关闭提醒等额外的功能。 94 | 95 | QMUI Web Desktop 96 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor file 2 | # http://www.appveyor.com/docs/lang/nodejs-iojs 3 | # http://www.appveyor.com/docs/appveyor-yml 4 | 5 | version: "{build}" 6 | 7 | clone_depth: 10 8 | 9 | # Test against the latest version of this Node.js version 10 | environment: 11 | nodejs_version: "14" 12 | 13 | # Install scripts. (runs after repo cloning) 14 | install: 15 | # Get the latest stable version of Node.js or io.js 16 | - ps: Install-Product node $env:nodejs_version 17 | - cp qmui.config.js ../qmui.config.js 18 | - npm install -g gulp-cli 19 | # install modules 20 | - npm install 21 | 22 | # Post-install test scripts. 23 | test_script: 24 | # Output useful info for debugging. 25 | - node --version 26 | - npm --version 27 | # run tests 28 | - gulp initProject 29 | - gulp list 30 | - gulp sass 31 | - gulp include 32 | - gulp clean 33 | - gulp merge 34 | 35 | # Don't actually build. 36 | build: off 37 | 38 | cache: 39 | - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache 40 | - node_modules -> package.json # local npm modules -------------------------------------------------------------------------------- /docs-translations/en-US/README.md: -------------------------------------------------------------------------------- 1 |

2 | Banner 3 |

4 | 5 | # QMUI Web [![Version Number](https://img.shields.io/npm/v/generator-qmui.svg?style=flat)](https://github.com/Tencent/QMUI_Web/ "Version Number") 6 | > A front-end framework to make web UI development faster and easier. 7 | > 8 | > Official Website:[http://qmuiteam.com/web](http://qmuiteam.com/web) 9 | > 10 | > Demo:[https://github.com/QMUI/QMUIDemo_Web/releases](https://github.com/QMUI/QMUIDemo_Web/releases) 11 | 12 | [[English]](https://github.com/Tencent/QMUI_Web/tree/master/docs-translations/en-US/README.md) / [[简体中文]](https://github.com/Tencent/QMUI_Web/blob/master/README.md) / [[繁體中文]](//github.com/Tencent/QMUI_Web/tree/master/docs-translations/zh-TW/README.md) 13 | 14 | [![Build Status](https://travis-ci.org/Tencent/QMUI_Web.svg?branch=master)](https://travis-ci.org/Tencent/QMUI_Web "Build Status") 15 | [![Build status](https://ci.appveyor.com/api/projects/status/1h6de3rq6x45nnse?svg=true 16 | )](https://ci.appveyor.com/project/kayo5994/qmui-web) 17 | [![QMUI Team Name](https://img.shields.io/badge/Team-QMUI-brightgreen.svg?style=flat)](https://github.com/QMUI "QMUI Team") 18 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT "Feel free to contribute.") 19 | 20 | This framework consists of a collection of SASS methods and a built-in workflow, which can help you easily improve the efficiency, maintainability and robustness of your web UI development. Especially, when you want to adjust appearance of your website globally or deal with frequent UI design alteration, QMUI Web will be your best choice. 21 | 22 | ## Features 23 | 24 | ### Components and Configuration 25 | You can easily adjust global appearance of basic built-in components by editing a SASS config file. ([How do QMUI config file and basic components contribute to your UI development?](https://github.com/Tencent/QMUI_Web/wiki/Q&A#qmui-sass-%E9%85%8D%E7%BD%AE%E8%A1%A8%E5%92%8C%E5%85%AC%E5%85%B1%E7%BB%84%E4%BB%B6%E5%A6%82%E4%BD%95%E5%B8%AE%E5%BF%99%E5%BC%80%E5%8F%91%E8%80%85%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%A1%80-ui)) 26 | 27 | ### SASS Enhancement 28 | QMUI Web ships with more than 70 SASS Mixin, Function and Extend, dealing with layout, appearance, animation, device adaptation, math calculation and other SASS enhancement, which will make your development faster and easier. 29 | ### Automatic Processing 30 | QMUI Web has a built-in workflow and tools to automate a lot of work, including the Sprite image, html template engine, image management and compression, static resource merging and compression, and redundant file cleanup. 31 | 32 | ### Extended Component 33 | In addition to the built-in basic components, we also provide several extended components such as file upload button, tree menu, etc. 34 | 35 | ## Setup 36 | ure to install [Node.js](https://nodejs.org/) (recommend 14.0 or later) and then install Gulp globally with the following command: 37 | 38 | ```bash 39 | #Install gulp 40 | npm install --global gulp 41 | ``` 42 | ## Getting Started 43 | It is recommend for you to use a [Yeoman](http://yeoman.io/) project named [generator-qmui](https://github.com/QMUI/generator-qmui) to install and configure QMUI Web. 44 | 45 | ```bash 46 | #Install Yeoman. If you have already installed, please ignore this step. 47 | npm install -g yo 48 | #Install QMUI Template 49 | npm install -g generator-qmui 50 | #Execute the following command in the root directory of the project 51 | yo qmui 52 | ``` 53 | 效果预览 54 | 55 | ### Project Structure Generated After Completion 56 | ```bash 57 | Root Directory 58 | ├─public // Static resource directory, generated by gulp 59 | │ ├─js // Javascript 60 | │ └─style // All UI files generate here 61 | │ ├─css // All styles files generate here 62 | │ └─images // Images generate here 63 | ├─UI_dev // Development directory 64 | │ ├─project // SASS and images files 65 | │ │ ├─images // Image source directory 66 | │ │ ├─logic // SASS file of each module 67 | │ │ └─widget // SASS file of components 68 | │ └─qmui_web // QMUI Web Source Code 69 | ├─UI_html // HTML files 70 | └─UI_html_result // HTML files compiled by gulp 71 | ``` 72 | 73 | If you need more customization, please refer to [Creating Project (Advanced)](http://qmuiteam.com/web/page/start.html#qui_createProject) 74 | 75 | ## Task List in Build-In Workflow 76 | 77 | ```bash 78 | gulp list 79 | ``` 80 | 81 | ## Make Contributions 82 | You can create [issues](https://github.com/Tencent/QMUI_Web/issues) or send [pull requests](https://github.com/Tencent/QMUI_Web/pulls) if you have any feedback or suggestion. 83 | Please read the [documentation](http://qmuiteam.com/web/page/start.html#qui_frameworkImprove) before debugging or modifying this framework. 84 | 85 | Thanks very much for your support and contributions. 86 | 87 | Design file (Sketch) is available on [Dribbble](https://dribbble.com/shots/2895907-QMUI-Logo). 88 | 89 | ## QMUI Web Desktop 90 | 91 | If you prefer visual interface rather than CLI, it is recommended to try an additional desktop application: [QMUI Web Desktop](https://github.com/Tencent/QMUI_Web_desktop). You can manage projects based on QMUI Web, toggle QMUI Web services, and get compilation or error message in time through it. 92 | 93 | QMUI Web Desktop 94 | -------------------------------------------------------------------------------- /docs-translations/zh-TW/README.md: -------------------------------------------------------------------------------- 1 |

2 | Banner 3 |

4 | 5 | # QMUI Web [![Version Number](https://img.shields.io/npm/v/generator-qmui.svg?style=flat)](https://github.com/Tencent/QMUI_Web/ "Version Number") 6 | > 一個旨在提高 UI 開發效率、快速產生項目 UI 的前端框架 7 | > 8 | > 官網:[http://qmuiteam.com/web](http://qmuiteam.com/web) 9 | > 10 | > 下載 Demo:[https://github.com/QMUI/QMUIDemo_Web/releases](https://github.com/QMUI/QMUIDemo_Web/releases) 11 | 12 | [[English]](https://github.com/Tencent/QMUI_Web/tree/master/docs-translations/en-US/README.md) / [[简体中文]](https://github.com/Tencent/QMUI_Web/blob/master/README.md) / [[繁體中文]](//github.com/Tencent/QMUI_Web/tree/master/docs-translations/zh-TW/README.md) 13 | 14 | [![Build Status](https://travis-ci.org/Tencent/QMUI_Web.svg?branch=master)](https://travis-ci.org/Tencent/QMUI_Web "Build Status") 15 | [![Build status](https://ci.appveyor.com/api/projects/status/1h6de3rq6x45nnse?svg=true 16 | )](https://ci.appveyor.com/project/kayo5994/qmui-web) 17 | [![QMUI Team Name](https://img.shields.io/badge/Team-QMUI-brightgreen.svg?style=flat)](https://github.com/QMUI "QMUI Team") 18 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT "Feel free to contribute.") 19 | 20 | QMUI Web 是一個專註 Web UI 開發,幫助開發者快速實現特定的一整套設計的框架。框架主要由一個強大的 SASS 方法合集與內置的工作流構成。通過 QMUI Web,開發者可以很輕松地提高 Web UI 開發的效率,同時保持了項目的高可維護性與穩健。如果你需要方便地控制項目的整體樣式,或者需要應對頻繁的界面變動,那麽 QMUI Web 框架將會是你最好的解決方案。 21 | 22 | ## 功能特性 23 | 24 | ### 基礎配置與組件 25 | 通過內置的公共組件和對應的 SASS 配置表,你只需修改簡單的配置即可快速實現所需樣式的組件。([QMUI SASS 配置表和公共組件如何幫忙開發者快速搭建項目基礎 UI?](https://github.com/Tencent/QMUI_Web/wiki/Q&A#qmui-sass-%E9%85%8D%E7%BD%AE%E8%A1%A8%E5%92%8C%E5%85%AC%E5%85%B1%E7%BB%84%E4%BB%B6%E5%A6%82%E4%BD%95%E5%B8%AE%E5%BF%99%E5%BC%80%E5%8F%91%E8%80%85%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E9%A1%B9%E7%9B%AE%E5%9F%BA%E7%A1%80-ui)) 26 | 27 | ### SASS 增強與支援 28 | QMUI Web 包含70個 SASS mixin/function/extend,涉及布局、外觀、動畫、設備適配、數值計算以及 SASS 原生能力增強等多個方面,可以大幅提升開發效率。 29 | 30 | ### 腳手架(自動化任務執行工具) 31 | QMUI Web 內置的工作流擁有從初始化項目到變更文件的各種自動化處理,包含了模板引擎,雪碧圖處理,圖片集中管理與自動壓縮,靜態資源合並、壓縮與變更以及冗余文件清理等功能。 32 | 33 | ### 擴展組件 34 | QMUI Web 除了內置的公共組件外,還通過擴展的方式提供了常用的擴展組件,如等高左右雙欄,文件上傳按鈕,樹狀選擇菜單。 35 | 36 | ## 環境配置 37 | 请确保安装 [Node.js](https://nodejs.org/)(推薦 14.0 或以上版本),并用以下命令把 gulp 安装到全域环境: 38 | 39 | ```bash 40 | #安裝 gulp 41 | npm install --global gulp 42 | ``` 43 | ## 快速開始 44 | 推薦使用 [Yeoman](http://yeoman.io/) 腳手架 [generator-qmui](https://github.com/QMUI/generator-qmui) 安裝和配置 QMUI Web。該工具可以幫助你完成 QMUI Web 的所有安裝和配置。 45 | 46 | ```bash 47 | #安裝 Yeoman,如果本地已安裝可以忽略 48 | npm install -g yo 49 | #安裝 QMUI 的模板 50 | npm install -g generator-qmui 51 | #在項目根目錄執行以下命令 52 | yo qmui 53 | ``` 54 | 效果預覽 55 | 56 | ### 完成後生成的項目目錄結構 57 | ```bash 58 | 项目根目录 59 | ├─public // 靜態資源目錄,由 gulp 生成 60 | │ ├─js // 靜態資源 js 文件 61 | │ └─style // 靜態資源 UI 文件 62 | │ ├─css // 靜態資源 css 文件 63 | │ └─images // 靜態資源 images 文件 64 | ├─UI_dev // 實際進行開發的樣式目錄 65 | │ ├─project // 項目相關 SASS 與 images 文件,由 gulp 生成 66 | │ │ ├─images // 項目相關圖片文件 67 | │ │ ├─logic // 項目相關邏輯樣式 68 | │ │ └─widget // 項目相關公共組件樣式 69 | │ └─qmui_web // QMUI Web 主源碼應放置在這一層目錄 70 | ├─UI_html // 靜態模板目錄 71 | └─UI_html_result // 靜態模板 gulp 處理後的版本,用於前端拼接最終的模板 72 | ``` 73 | 74 | 對於需要有更強定制性的開發者,請參考[創建新項目(進階)](http://qmuiteam.com/web/page/start.html#qui_createProject) 75 | 76 | ## 工作流任務列表 77 | 78 | ```bash 79 | #在 UI_dev/qmui_web 中執行以下命令可以查看工作流的任務列表及說明 80 | gulp list 81 | ``` 82 | 83 | 也可以查看文檔中的[詳細說明](http://qmuiteam.com/web/page/scaffold.html)。 84 | 85 | ## 完善框架 86 | 如果有意見反饋或者功能建議,歡迎創建 [Issue](https://github.com/Tencent/QMUI_Web/issues) 或發送 [Pull Request](https://github.com/Tencent/QMUI_Web/pulls),調試與修改框架請先閱讀[文檔](http://qmuiteam.com/web/page/start.html#qui_frameworkImprove),感謝你的支持和貢獻。 87 | 88 | 設計稿 Sketch 源文件可在 [Dribbble](https://dribbble.com/shots/2895907-QMUI-Logo) 上獲取。 89 | 90 | ## QMUI Web Desktop 91 | 92 | 推薦配合使用的桌機應用程式:[QMUI Web Desktop](https://github.com/Tencent/QMUI_Web_desktop)。它可以管理基於 QMUI Web 進行開發的項目,通過 GUI 界面處理 QMUI Web 的服務開啟/關閉,使框架的使用變得更加便捷,並提供了編譯提醒,出錯提醒,進程關閉提醒等額外的功能。 93 | 94 | QMUI Web Desktop 95 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // gulpfile.js QMUI Web Gulp 工作流 17 | const gulp = require('gulp'); 18 | const fs = require('fs'); 19 | const mix = new (require('./workflow/Mix.js'))(); 20 | 21 | // 载入基础任务 22 | const basicTaskPath = 'workflow/basicTasks'; 23 | const combinedTaskPath = 'workflow'; 24 | 25 | const basicTaskPathFilterCallback = (file) => file.match(/js$/); // 排除非 JS 文件,如 Vim 临时文件 26 | 27 | fs.readdirSync(basicTaskPath).filter(basicTaskPathFilterCallback).sort().forEach((file) => { 28 | require('./' + basicTaskPath + '/' + file)(gulp, mix); 29 | }); 30 | 31 | // 载入复合任务 32 | 33 | // 载入 watch 任务 34 | require('./' + combinedTaskPath + '/watch')(gulp, mix); 35 | 36 | // 载入自定义任务 37 | if (mix.config.customTasks) { 38 | Object.keys(mix.config.customTasks).forEach((customTaskName) => { 39 | require('./' + mix.config.customTasks[customTaskName])(gulp, mix); 40 | }); 41 | } 42 | 43 | // 载入 start 和 initProject 任务 44 | ['start', 'initProject'].forEach((file) => { 45 | require('./' + combinedTaskPath + '/' + file)(gulp, mix); 46 | }); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qmui_web", 3 | "version": "3.3.0", 4 | "description": "一个旨在提高 UI 开发效率、快速产生项目 UI 的前端框架", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/Tencent/QMUI_Web.git" 8 | }, 9 | "main": "gulpfile.js", 10 | "dependencies": { 11 | "ansi-colors": "^4.1.3", 12 | "autoprefixer": "^9.8.6", 13 | "beeper": "^2.1.0", 14 | "browser-sync": "^2.27.11", 15 | "color-support": "^1.1.3", 16 | "del": "^5.1.0", 17 | "fancy-log": "^2.0.0", 18 | "gulp": "^4.0.2", 19 | "gulp-better-sass-inheritance": "^0.0.3", 20 | "gulp-clean-css": "^4.3.0", 21 | "gulp-concat": "^2.6.1", 22 | "gulp-dart-sass": "^1.0.2", 23 | "gulp-debug": "^4.0.0", 24 | "gulp-file-include": "^2.3.0", 25 | "gulp-file-sync": "^2.1.0", 26 | "gulp-htmlmin": "^5.0.1", 27 | "gulp-if": "^3.0.0", 28 | "gulp-imagemin": "^8.0.0", 29 | "gulp-load-plugins": "^2.0.8", 30 | "gulp-plumber": "^1.2.1", 31 | "gulp-postcss": "^9.0.1", 32 | "gulp-rename": "^2.0.0", 33 | "gulp-replace": "^1.1.4", 34 | "gulp-sourcemaps": "^3.0.0", 35 | "gulp-uglify": "^3.0.2", 36 | "imagemin-pngquant": "^9.0.2", 37 | "js-md5": "^0.7.3", 38 | "lodash": "^4.17.21", 39 | "lodash.template": "^4.5.0", 40 | "minimatch": "^5.1.0", 41 | "mkdirp": "^1.0.4", 42 | "path": "^0.12.7", 43 | "postcss-lazysprite": "^1.8.2", 44 | "postcss-svg-sprite": "^1.0.6", 45 | "sass": "^1.57.1", 46 | "set-value": "^4.1.0", 47 | "static-eval": "^2.1.0", 48 | "through2": "^4.0.2", 49 | "yargs": "^17.6.2" 50 | }, 51 | "scripts": { 52 | "lint": "./node_modules/.bin/eslint ." 53 | }, 54 | "devDependencies": { 55 | "eslint": "^8.31.0", 56 | "sassdoc": "^2.7.4", 57 | "stylelint-wechat-work-css": "^0.5.0" 58 | }, 59 | "keywords": [ 60 | "QMUI" 61 | ], 62 | "author": "QMUI Team", 63 | "license": "MIT", 64 | "bugs": { 65 | "url": "https://github.com/Tencent/QMUI_Web/issues" 66 | }, 67 | "homepage": "http://qmuiteam.com/web/" 68 | } 69 | -------------------------------------------------------------------------------- /project/_var.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * var.scss 变量 4 | * @author Kayo&Clearwu 5 | * @date 2014-11-14 6 | * 7 | * --- function ---- 8 | * #function QMUI 功能相关 9 | * 10 | * --- common ---- 11 | * #common 通用 12 | 13 | * --- component ---- 14 | * #button 按钮组件 15 | * #dialog 对话框组件 16 | * #mask 遮罩层组件 17 | * #dropdownMenu 下拉菜单组件 18 | * #tab 选项卡组件 19 | * #inputText、#textarea 文本输入组件 20 | * 21 | */ 22 | // 计算的工具方法 23 | @import "../qmui/mixin/tool/_calculate"; 24 | 25 | // #common 通用 26 | $common_fontFamily: "Helvetica Neue", Helvetica, Verdana, san-serif; 27 | $common_fontSize: 14px; 28 | $common_body_background: #FFF; 29 | $common_body_color: #000; 30 | $common_color_link: #2685D2; 31 | $common_color_linkTapHighlight: rgba(105, 186, 255, .13); 32 | $common_color_separator: #CCCCCE; 33 | $common_color_border: $common_color_separator; 34 | $common_color_placeholder: #ADB4BE; 35 | $common_color_placeholderFocus: $common_color_placeholder; 36 | $common_zIndex_1: 10; 37 | $common_zIndex_2: 20; 38 | $common_zIndex_3: 30; 39 | $common_zIndex_4: 40; 40 | $common_zIndex_5: 50; 41 | $common_zIndex_6: 60; 42 | $common_zIndex_7: 70; 43 | $common_zIndex_8: 80; 44 | $common_zIndex_9: 90; 45 | $common_zIndex_10: 100; 46 | 47 | // #button 按钮组件 48 | $btn_minWidth: 46px; 49 | $btn_height: 30px; 50 | $btn_lineHeight: $btn_height; 51 | $btn_padding: 0 12px; 52 | $btn_background: #04C9E8; 53 | $btn_border: 1px solid transparent; 54 | $btn_fontSize: 13px; 55 | $btn_color: #FFF; 56 | 57 | // #dialog 对话框组件 58 | $dialog_zIndex: $common_zIndex_10; 59 | $dialog_minWidth: 420px; // 对话框的最小宽度 60 | $dialog_background: #fff; 61 | $dialog_border: none; 62 | $dialog_radius: 5px; 63 | 64 | $dialog_head_background: #00C0E1; 65 | $dialog_head_borderBottom: none; // 对话框头部的下边线 66 | 67 | $dialog_title_height: 40px; 68 | $dialog_title_fontSize: 14px; 69 | $dialog_title_fontWeight: bold; 70 | $dialog_title_color: #FFF; 71 | $dialog_title_lineHeight: $dialog_title_height; 72 | 73 | $dialog_close_top: 5px; 74 | $dialog_close_right: 6px; 75 | 76 | $dialog_body_background: #fff; 77 | 78 | $dialog_foot_background: $dialog_background; // 对话框底部的背景,默认与对话框的背景一样 79 | $dialog_foot_borderTop: $dialog_head_borderBottom; // 对话框底部的上边线,样式默认与对话框边线一样 80 | 81 | // #mask 遮罩层组件 82 | $mask_zIndex: $common_zIndex_9; 83 | $maskWrap_zIndex: $mask_zIndex; 84 | $mask_background: #000; 85 | $mask_opacity: .5; 86 | 87 | // #dropdownMenu 下拉菜单组件 88 | $dropdownMenu_zIndex: $common_zIndex_3; 89 | $dropdownMenu_background: #fff; 90 | $dropdownMenu_border: 1px solid $common_color_border; 91 | $dropdownMenu_ulPadding: 12px 0; 92 | $dropdownMenu_itemLink_height: 32px; 93 | $dropdownMenu_itemLink_lineHeight: $dropdownMenu_itemLink_height; 94 | $dropdownMenu_itemLink_padding: 0 15px; 95 | $dropdownMenu_itemLink_color: #000; 96 | $dropdownMenu_split_margin: 5px 0; 97 | $dropdownMenu_split_borderTop: 1px solid #ccc; 98 | 99 | // #tab 选项卡组件 100 | $tab_background: #fff; 101 | $tab_head_background: $tab_background; 102 | $tabNav_padding: 0 16px; 103 | $tabNav_item_margin: 0 24px -1px 0; 104 | $tabNav_itemLink_height: 43px; 105 | $tabNav_itemLink_lineHeight: $tabNav_itemLink_height; 106 | $tabNav_itemLink_padding: 0; 107 | $tabNav_itemLink_background: transparent; 108 | $tabNav_itemLink_color: #858C96; 109 | 110 | // #inputText、#textarea 文本输入组件 111 | $textField_width: 270px; 112 | $textField_lineHeight: 22px; 113 | $textField_padding: 12px 16px; 114 | $textField_background: #fff; 115 | $textField_border: 1px solid $common_color_border; 116 | $textField_borderRadius: 5px; 117 | 118 | $inputText_height: $textField_lineHeight; 119 | 120 | $textarea_height: $textField_lineHeight * 3; 121 | -------------------------------------------------------------------------------- /project/demo.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /** 4 | * main.scss 项目总样式文件 5 | * @author Kayo 6 | * @date 2014-10-31 7 | * 8 | */ 9 | 10 | // 业务公共变量 11 | @import "_var"; 12 | 13 | // 引入 QMUI 14 | @import "../qmui/_qmui"; 15 | 16 | // 业务公共组件 17 | @import "widget/_widget"; 18 | 19 | // 业务逻辑代码 20 | @import "logic/_logic"; 21 | -------------------------------------------------------------------------------- /project/logic/_logic.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _logic.scss 业务逻辑代码 4 | * @author Kayo 5 | * @date 2014-10-31 6 | * 7 | */ 8 | -------------------------------------------------------------------------------- /project/widget/_button.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _button.scss 按钮组件 4 | * @author Kayo 5 | * @date 2014-11-04 6 | * 7 | * .qui_btn 8 | * + .qui_btn_Ghost 9 | * 10 | */ 11 | 12 | // .qui_btn 13 | .qui_btn { 14 | display: inline-block; 15 | margin: 0; // input = button有默认 margin 值,这里需要重置 16 | box-sizing: content-box; 17 | min-width: $btn_minWidth; 18 | height: $btn_height; 19 | padding: $btn_padding; 20 | background: $btn_background; 21 | border: $btn_border; 22 | // line-height 的偏移值依赖于不同字体 23 | line-height: $btn_lineHeight + 2; 24 | line-height: $btn_lineHeight + 1 \9\0; 25 | outline: none; 26 | cursor: pointer; 27 | text-align: center; 28 | font-size: $btn_fontSize; 29 | color: $btn_color; 30 | user-select: none; 31 | border-radius: 2px; 32 | 33 | &:hover { 34 | background-color: #05D7F7; 35 | } 36 | &:active, 37 | &_Active { 38 | background-color: #04B1CC; 39 | } 40 | &[Disabled] { 41 | opacity: .5; 42 | } 43 | } 44 | 45 | a.qui_btn { 46 | text-decoration: none; 47 | 48 | &:hover { 49 | text-decoration: none; 50 | } 51 | } 52 | 53 | .a[title = "1"] { 54 | 55 | } 56 | 57 | .qui_btn_Ghost { 58 | background-color: transparent; 59 | border: 1px solid #04C9E8; 60 | color: #04C9E8; 61 | 62 | &:hover { 63 | background-color: transparent; 64 | border-color: #05D7F7; 65 | color: #05D7F7; 66 | } 67 | &:active, 68 | &_Active { 69 | background-color: transparent; 70 | border-color: #04B1CC; 71 | color: #04B1CC; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /project/widget/_dialog.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _dialog.scss 对话框组件 4 | * @author Kayo 5 | * @date 2014-11-03 6 | * 7 | * .qui_dialog 8 | * > .qui_dialog_head 对话框的头部 9 | * > .qui_dialog_title 对话框的标题 10 | * > [.qui_dialog_close] 可选。对话框的关闭按钮 11 | * > .qui_dialog_cnt 对话框的内容区域 12 | * > [.qui_dialog_foot] 可选。对话框的底部 13 | */ 14 | 15 | // .qui_dialog 16 | .qui_dialog { 17 | position: absolute; 18 | z-index: $dialog_zIndex; 19 | min-width: $dialog_minWidth; 20 | background: $dialog_background; 21 | border: $dialog_border; 22 | border-radius: $dialog_radius; 23 | box-shadow: 0 2px 20px 0 rgba(0, 0, 0, .15); 24 | } 25 | 26 | .qui_dialog_head { 27 | padding: 0 12px; 28 | background: $dialog_head_background; 29 | border-bottom: $dialog_head_borderBottom; 30 | border-radius: $dialog_radius $dialog_radius 0 0; 31 | } 32 | 33 | .qui_dialog_title { 34 | height: $dialog_title_height; 35 | line-height: $dialog_title_lineHeight; 36 | font-size: $dialog_title_fontSize; 37 | font-weight: $dialog_title_fontWeight; 38 | color: $dialog_title_color; 39 | } 40 | 41 | .qui_dialog_close { 42 | position: absolute; 43 | top: $dialog_close_top; 44 | right: $dialog_close_right; 45 | padding: 5px; // 把点击区域做大 46 | line-height: 1; 47 | font-size: 18px; 48 | color: #fff; 49 | 50 | &:hover { 51 | color: #d6d9de; 52 | text-decoration: none; 53 | } 54 | &:active { 55 | color: #eaecee; 56 | } 57 | } 58 | 59 | .qui_dialog_body { 60 | padding: 23px 30px 30px 37px; 61 | background: $dialog_body_background; 62 | font-size: 14px; 63 | color: #353C46; 64 | } 65 | 66 | .qui_dialog_foot { 67 | padding: 17px 12px; 68 | background: $dialog_foot_background; 69 | border-top: $dialog_foot_borderTop; 70 | border-radius: 0 0 $dialog_radius $dialog_radius; 71 | text-align: right; 72 | line-height: 25px; 73 | } 74 | 75 | .qui_dialog_foot .qui_btn { 76 | margin-left: 12px; 77 | } 78 | -------------------------------------------------------------------------------- /project/widget/_dropdownMenu.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _dropdownMenu.scss 下拉菜单组件 4 | * @author clearwu 5 | * @date 2014-11-11 6 | * 7 | * .qui_dropdownMenu 8 | * 9 | */ 10 | 11 | // .qui_dropdownMenu 12 | .qui_dropdownMenu { 13 | position: absolute; 14 | z-index: $dropdownMenu_zIndex; 15 | min-width: 206px; 16 | background: $dropdownMenu_background; 17 | border: $dropdownMenu_border; 18 | box-shadow: 0 1px 2px rgba(0, 0, 0, .2); 19 | border-radius: 4px; 20 | } 21 | 22 | .qui_dropdownMenu ul { 23 | padding: $dropdownMenu_ulPadding; 24 | } 25 | 26 | .qui_dropdownMenu_itemLink { 27 | display: block; 28 | height: $dropdownMenu_itemLink_height; 29 | line-height: $dropdownMenu_itemLink_lineHeight; 30 | padding: $dropdownMenu_itemLink_padding; 31 | color: $dropdownMenu_itemLink_color; 32 | 33 | &:hover { 34 | background-color: #F5F5F5; 35 | text-decoration: none; 36 | } 37 | &:active { 38 | background-color: #EBEBEB; 39 | } 40 | } 41 | 42 | .qui_dropdownMenu_split { 43 | display: block; 44 | height: 0; 45 | line-height: 0; 46 | font-size: 0; 47 | margin: $dropdownMenu_split_margin; 48 | border-top: $dropdownMenu_split_borderTop; 49 | } 50 | -------------------------------------------------------------------------------- /project/widget/_mask.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _mask.scss 遮罩层组件 4 | * @author Kayo 5 | * @date 2014-11-11 6 | * 7 | * .qui_maskWrap 遮罩层的包裹容器,包裹遮罩层以及需要弹出的内容(如对话框) 8 | * > .qui_mask 9 | */ 10 | 11 | // 遮罩层的包裹容器,包裹遮罩层以及需要弹出的内容(如对话框) 12 | .qui_maskWrap { 13 | position: relative; 14 | z-index: $maskWrap_zIndex; 15 | } 16 | 17 | .qui_mask { 18 | position: fixed; 19 | top: 0; 20 | right: 0; 21 | bottom: 0; 22 | left: 0; 23 | z-index: $mask_zIndex; 24 | background: $mask_background; 25 | opacity: $mask_opacity; 26 | filter: alpha(opacity=$mask_opacity * 100); 27 | } 28 | -------------------------------------------------------------------------------- /project/widget/_tab.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _tab.scss 选项卡组件 4 | * @author clearwu 5 | * @date 2014-11-12 6 | * 7 | * .qui_tab 8 | * > .qui_tab_title 9 | * > .qui_tabNav 10 | * > .qui_tabNav_item 11 | * > .qui_tabNav_itemLink 12 | * > .qui_tab_cnt 13 | * 14 | */ 15 | 16 | // .qui_tab 17 | .qui_tab { 18 | background: $tab_background; 19 | border: 1px solid #DEE0E2; 20 | } 21 | 22 | .qui_tab_title { 23 | background: $tab_head_background; 24 | } 25 | 26 | .qui_tabNav { 27 | @include clear; 28 | padding: $tabNav_padding; 29 | border-bottom: 1px solid #DEE0E2; 30 | } 31 | 32 | .qui_tabNav_item { 33 | float: left; 34 | margin: $tabNav_item_margin; 35 | border-bottom: 3px solid transparent; 36 | font-size: 16px; 37 | 38 | &_Curr { 39 | border-color: #04C9E8; 40 | 41 | .qui_tabNav_itemLink { 42 | color: #04C9E8; 43 | } 44 | } 45 | } 46 | 47 | .qui_tabNav_itemLink { 48 | display: block; 49 | height: $tabNav_itemLink_height; 50 | line-height: $tabNav_itemLink_lineHeight; 51 | padding: $tabNav_itemLink_padding; 52 | background: $tabNav_itemLink_background; 53 | color: $tabNav_itemLink_color; 54 | 55 | &:hover { 56 | color: #04C9E8; 57 | text-decoration: none; 58 | 59 | } 60 | } 61 | 62 | .qui_tab_cnt { 63 | min-height: 100px; 64 | padding: 16px 24px; 65 | } 66 | -------------------------------------------------------------------------------- /project/widget/_textField.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /** 3 | * _textField.scss 文本输入组件 4 | * @author clearwu 5 | * @date 2014-11-13 6 | * 7 | * .qui_inputText 8 | * .qui_textarea 9 | * 10 | */ 11 | 12 | // placeholder 颜色重置 13 | // Webkit 14 | input::-webkit-input-placeholder, 15 | textarea::-webkit-input-placeholder { 16 | color: $common_color_placeholder; 17 | } 18 | 19 | input:focus::-webkit-input-placeholder, 20 | textarea:focus::-webkit-input-placeholder { 21 | color: $common_color_placeholderFocus; 22 | } 23 | 24 | // Firefox < 19 25 | input:-moz-placeholder, 26 | textarea:-moz-placeholder { 27 | color: $common_color_placeholder; 28 | } 29 | 30 | input:focus:-moz-placeholder, 31 | textarea:focus:-moz-placeholder { 32 | color: $common_color_placeholderFocus; 33 | } 34 | 35 | // Firefox > 19 36 | input::-moz-placeholder, 37 | textarea::-moz-placeholder { 38 | color: $common_color_placeholder; 39 | } 40 | 41 | input:focus::-moz-placeholder, 42 | textarea:focus::-moz-placeholder { 43 | color: $common_color_placeholderFocus; 44 | } 45 | 46 | // IE10 47 | input:-ms-input-placeholder, 48 | textarea:-ms-input-placeholder { 49 | color: $common_color_placeholder; 50 | } 51 | 52 | input:focus:-ms-input-placeholder, 53 | textarea:focus:-ms-input-placeholder { 54 | color: $common_color_placeholder; 55 | } 56 | 57 | %textField { 58 | display: block; 59 | width: $textField_width; 60 | line-height: $textField_lineHeight; 61 | padding: $textField_padding; 62 | background: $textField_background; 63 | border: $textField_border; 64 | border-radius: $textField_borderRadius; 65 | box-sizing: content-box; 66 | } 67 | 68 | .qui_inputText, 69 | .qui_textarea { 70 | font-size: 15px; 71 | color: #353C46; 72 | } 73 | 74 | // .qui_inputText 75 | .qui_inputText { 76 | @extend %textField; 77 | height: $inputText_height; 78 | &::-ms-clear { 79 | display: none; // 去除 IE10 中的输入框清除按钮效果 80 | } 81 | } 82 | 83 | // .qui_textarea 84 | .qui_textarea { 85 | @extend %textField; 86 | height: $textarea_height; 87 | } 88 | -------------------------------------------------------------------------------- /project/widget/_widget.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * _widget.scss 业务公共组件 4 | * @author Kayo 5 | * @date 2014-10-31 6 | * 7 | */ 8 | 9 | 10 | // 按钮 11 | @import "_button"; 12 | 13 | // 对话框 14 | @import "_dialog"; 15 | 16 | // 遮罩层 17 | @import "_mask"; 18 | 19 | // 下拉菜单 20 | @import "_dropdownMenu"; 21 | 22 | // 选项卡 23 | @import "_tab"; 24 | 25 | // 文本输入 26 | @import "_textField"; 27 | -------------------------------------------------------------------------------- /qmui.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * 项目相关部分代码,复制后应首先进行这些配置 4 | * 5 | */ 6 | 'project': 'Demo', 7 | 'prefix': 'dm', 8 | 'resultCssFileName': 'main.scss', 9 | 'cleanFileType': ['../.sass-cache', '../.sass-cache/**/*'], 10 | 'needsSourceMaps': false, 11 | 'needsImagesMinAndSync': true, 12 | 13 | /** 14 | * 项目的路径配置,建议尽量使用推荐的路径,若要修改,请保持与 config.rb 中的指向的目录保持一致,但需要注意因为相对位置不同(这里是以 qmui_web 目录为 Base Path),所以这里的值应该比 config.rb 中的多了一个 ../ 15 | * 16 | */ 17 | 'paths': { 18 | 'htmlSourcePath': ['../../UI_html/**/*.html'], 19 | 'imagesSourcePath': '../project/images', 20 | 'htmlResultPath': '../../UI_html_result', 21 | 'imagesResultPath': '../../public/style/images', 22 | 'independentImagesDirectory': '/independent', 23 | 'styleResultPath': '../../public/style/css' 24 | }, 25 | 26 | /** 27 | * BrowerSync 设置 28 | * 29 | */ 30 | 'browserSync': { 31 | // browserSync 的模式,本地模式、代理模式或者关闭(server/proxy/close) 32 | 'browserSyncMod': 'server', 33 | // 自定义端口 34 | 'browserSyncPort': 3030, 35 | // 是否显示 BrowserSync 的日志 36 | 'browserSyncShowLog': false, 37 | // server 开启后的默认路径 38 | 'browserSyncStartPath': '/web', 39 | 'browserSyncHost': '', 40 | 'browserSyncWatchPath': ['../../UI_html_result/*.html', '../../public/**/*'], 41 | // 自定义路由,server 模式下方可产生作用 42 | 'browserSyncServerRoute': { 43 | '/public': '../../public', 44 | '/web': '../../UI_html_result' 45 | }, 46 | // 自定义代理源地址,proxy 模式下方可产生作用 47 | 'browserSyncProxy': '' 48 | }, 49 | 50 | /** 51 | * 模板 include 引擎 52 | * 53 | */ 54 | 'template': { 55 | 'openIncludeFunction': true, 56 | 'includePrefix': '@@' 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /qmui.merge.rule.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'comment': '以下是示例内容,使用时需要修改为项目实际需要的内容并必须删除本段注释', 3 | '../public/js/all.js': ['../public/js/prettify.js', '../public/js/lang-css.js', '../public/js/main.js'], 4 | '../public/style/css/main.css': ['../public/style/css/logic.css', '../public/style/css/widget.css'] 5 | }; 6 | -------------------------------------------------------------------------------- /qmui/_function.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * _function.scss 4 | * @author Kayo 5 | * @date 2014-11-17 6 | */ 7 | 8 | .qui_txtNormal { 9 | font-weight: normal !important; 10 | } 11 | 12 | .qui_txtBold { 13 | font-weight: bold !important; 14 | } 15 | 16 | .qui_txtLeft { 17 | text-align: left !important; 18 | } 19 | 20 | .qui_txtRight { 21 | text-align: right !important; 22 | } 23 | 24 | .qui_txtUnderline { 25 | text-decoration: underline !important; 26 | } 27 | 28 | .qui_txtUnderlineNone { 29 | text-decoration: none !important; 30 | } 31 | 32 | .qui_txtOverflow { 33 | @include text-ellipsis; 34 | } 35 | 36 | .qui_clear { 37 | @include clear; 38 | } 39 | 40 | .qui_txtNowrap { 41 | white-space: nowrap !important; 42 | } 43 | 44 | .qui_layoutLeft { 45 | float: left !important; 46 | } 47 | 48 | .qui_layoutRight { 49 | float: right !important; 50 | } 51 | 52 | .qui_displayNone { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /qmui/_qmui.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | @charset "utf-8"; 17 | /** 18 | * _qmui.scss QMUI 总样式文件 19 | * @author Kayo 20 | * @date 2014-10-31 21 | * 22 | */ 23 | 24 | 25 | // 常用 mixin 封装 26 | @import "mixin/_mixin"; 27 | 28 | // CSS Reset 29 | @import "_reset"; 30 | 31 | // 常用样式(原子类) 32 | @import "_function"; 33 | -------------------------------------------------------------------------------- /qmui/_reset.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * _reset.scss 4 | * @author Kayo 5 | * @date 2014-10-30 6 | * 7 | * #reset 8 | * #html5 9 | */ 10 | 11 | /* #reset */ 12 | body, dl, dd, h1, h2, h3, h4, h5, h6, p, pre, form, fieldset, legend { 13 | margin: 0; 14 | } 15 | 16 | ul, ol, fieldset { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | th, td { 22 | padding: 0; 23 | } 24 | 25 | table { 26 | font-size: inherit; 27 | } 28 | 29 | fieldset, img { 30 | border: 0; 31 | } 32 | 33 | ul, ol, li { 34 | list-style: none; 35 | } 36 | 37 | body { 38 | font-size: $common_fontSize; 39 | line-height: 1.5; 40 | background: $common_body_background; 41 | color: $common_body_color; 42 | } 43 | 44 | h1, h2, h3, h4 { 45 | font-size: 18px; 46 | font-weight: normal; 47 | } 48 | 49 | body, input, textarea, select, button { 50 | font-family: $common_fontFamily; 51 | outline: none; 52 | // stylelint-disable 53 | // For Webkit kernel on mobile 54 | -webkit-text-size-adjust: none; 55 | // stylelint-enable 56 | } 57 | 58 | input, textarea, select, button { 59 | font-size: inherit; // form control's default font in webkit is "-webkit-small-control" 60 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // remove widget tap highlighted in mobile safari 61 | } 62 | 63 | a { 64 | color: $common_color_link; 65 | text-decoration: none; 66 | -webkit-tap-highlight-color: $common_color_linkTapHighlight; 67 | } 68 | 69 | :focus { 70 | outline: none; 71 | } 72 | 73 | /* #html5 HTML5 元素的支持 */ 74 | article, aside, details, 75 | figcaption, figure, 76 | footer, header, hgroup, 77 | main, nav, section, 78 | summary { 79 | display: block; 80 | } 81 | 82 | audio, canvas, video { 83 | display: inline-block; 84 | } 85 | -------------------------------------------------------------------------------- /qmui/mixin/_adaptation.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | //// 4 | /// 平台与浏览器适配相关方法 5 | /// @author Clear, Molice, Zhoon, Kayo,Jeff 6 | /// @group 兼容性封装 7 | /// @date 2014-08-19 8 | //// 9 | 10 | /// 清除浮动 11 | /// 12 | /// @group 布局 13 | /// @name clear 14 | @mixin clear { 15 | &:after { 16 | clear: both; 17 | content: "."; 18 | display: block; 19 | line-height: 0; 20 | font-size: 0; 21 | visibility: hidden; 22 | } 23 | } 24 | 25 | %clear { 26 | @include clear; 27 | } 28 | 29 | /// 单行省略号 30 | /// 31 | /// @group 外观 32 | /// @name text-ellipsis 33 | @mixin text-ellipsis { 34 | overflow: hidden; 35 | white-space: nowrap; 36 | text-overflow: ellipsis; 37 | word-break: break-all; 38 | //在IE9的中,如果之前已经设置了word-wrap:break-word,则这里的white-space:nowrap会失效,所以要在这里加上word-wrap:normal来以防万一 39 | word-wrap: normal; 40 | } 41 | 42 | %text-ellipsis { 43 | @include text-ellipsis; 44 | } 45 | 46 | /// 多行省略号 47 | /// 48 | /// @group 外观 49 | /// @name text-multiLine-ellipsis 50 | /// @param {Number} $line - 文字的行数 51 | /// @param {Measure} $lineHeight - 文字行高 52 | /// @throw 不支持多行省略的浏览器降级处理为结尾处没有省略号,直接裁剪掉。 53 | @mixin text-multiLine-ellipsis($line: 2, $lineHeight: 20px) { 54 | line-height: $lineHeight; 55 | overflow: hidden; 56 | height: $lineHeight * $line; 57 | // stylelint-disable 58 | display: -webkit-box; 59 | display: -moz-box; 60 | text-overflow: ellipsis; 61 | -webkit-line-clamp: $line; 62 | -moz-line-clamp: $line; 63 | line-clamp: $line; 64 | -webkit-box-orient: vertical; 65 | -moz-box-orient: vertical; 66 | -webkit-text-size-adjust: none; 67 | // stylelint-enable 68 | box-orient: vertical; 69 | } 70 | 71 | /// 在长单词或 URL 地址内部进行换行,适用于以中文为主混有英文的文字排版 72 | /// 73 | /// @group 外观 74 | /// @name text-breakWord 75 | @mixin text-breakWord { 76 | word-wrap: break-word; 77 | word-break: break-word; 78 | } 79 | 80 | %text-breakWord { 81 | @include text-breakWord; 82 | } 83 | 84 | /// 适配多倍屏的 CSS 选择器 85 | /// 86 | /// @group 设备适配 87 | /// @name screenResolution 88 | /// @param {Number} $num - 需要适配的屏幕倍数 89 | @mixin screenResolution($num) { 90 | @media (-webkit-min-device-pixel-ratio: $num), (min--moz-device-pixel-ratio: $num), (min-device-pixel-ratio: $num), (min-resolution: #{$num}dppx), (min-resolution: #{$num * 96}dpi) { 91 | @content; 92 | } 93 | } 94 | 95 | /// 适配 IE 10 及以上版本的 CSS 选择器,需要针对 IE10 或以上版本的样式可以写在这里 96 | /// 97 | /// @group 设备适配 98 | /// @name screenForIE10AndLater 99 | @mixin screenForIE10AndLater { 100 | @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { 101 | @content; 102 | } 103 | } 104 | 105 | /// 单独适配 IE 8 CSS 选择器,需要仅针对 IE 8 的样式可以写在这里 106 | /// 107 | /// @group 设备适配 108 | /// @name forIE8 109 | @mixin forIE8 { 110 | @media \0screen { 111 | @content; 112 | } 113 | } 114 | 115 | /// 单独适配 IE 9 CSS 选择器,需要仅针对 IE 9(不包括 IE 10 等更高版本) 的样式可以写在这里 116 | /// 117 | /// @group 设备适配 118 | /// @name forIE9 119 | @mixin forIE9 { 120 | @media all and (min-width: 0\0) and (min-resolution: .001dpcm) { 121 | @content; 122 | } 123 | } 124 | 125 | /// 半透明背景颜色 126 | /// 127 | /// @group 外观 128 | /// @name bgWithOpacity 129 | /// @param {Color} $color - 背景色的颜色值 130 | /// @param {Number} $alpha - 背景色的透明度 131 | @mixin bgWithOpacity($color, $alpha) { 132 | background-color: rgba($color, $alpha); 133 | @include forIE8 { 134 | filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#{ie-hex-str(rgba($color, $alpha))}, endcolorstr=#{ie-hex-str(rgba($color, $alpha))}); 135 | } 136 | } 137 | 138 | /// 跨浏览器的渐变背景,垂直渐变,自上而下 139 | /// 140 | /// @group 外观 141 | /// @name gradient-vertical 142 | /// @param {Color} $start-color [#555] - 渐变的开始颜色 143 | /// @param {Color} $end-color [#333] - 渐变的结束颜色 144 | /// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位 145 | /// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位 146 | @mixin gradient-vertical($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { 147 | background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); 148 | background-repeat: repeat-x; 149 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#{ie-hex-str($start-color)}", endColorstr="#{ie-hex-str($end-color)}", GradientType=0); // IE9 and down 150 | } 151 | 152 | /// 跨浏览器的渐变背景,水平渐变,自左而右 153 | /// 154 | /// @group 外观 155 | /// @name gradient-horizontal 156 | /// @param {Color} $start-color [#555] - 渐变的开始颜色 157 | /// @param {Color} $end-color [#333] - 渐变的结束颜色 158 | /// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位 159 | /// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位 160 | @mixin gradient-horizontal($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { 161 | background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); 162 | background-repeat: repeat-x; 163 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#{ie-hex-str($start-color)}", endColorstr="#{ie-hex-str($end-color)}", GradientType=1); // IE9 and down 164 | } 165 | 166 | /// 跨浏览器的渐变背景,带角度 167 | /// 168 | /// @group 外观 169 | /// @name gradient-on-axis 170 | /// @param {Degree} $axis-degree [135deg] - 渐变的轴 171 | /// @param {Color} $start-color [#555] - 渐变的开始颜色 172 | /// @param {Color} $end-color [#333] - 渐变的结束颜色 173 | /// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位 174 | /// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位 175 | @mixin gradient-on-axis($axis-degree: 0, $start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { 176 | background-image: linear-gradient($axis-degree, $start-color $start-percent, $end-color $end-percent); 177 | background-repeat: repeat-x; 178 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#{ie-hex-str($start-color)}", endColorstr="#{ie-hex-str($end-color)}", GradientType=0); // IE9 and down 179 | } 180 | 181 | /// 跨浏览器的渐变背景,垂直渐变,自上而下,支持三个渐变点 182 | /// 183 | /// @group 外观 184 | /// @name gradient-vertical-threeColor 185 | /// @param {Color} $start-color [#555] - 渐变的开始颜色 186 | /// @param {Color} $middle-color [#444] - 渐变的中间颜色 187 | /// @param {Color} $end-color [#333] - 渐变的结束颜色 188 | /// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位 189 | /// @param {Number} $start-percent [50%] - 渐变的中间位置,需要以百分号为单位 190 | /// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位 191 | @mixin gradient-vertical-threeColor($start-color: #555, $middle-color: #444, $end-color: #333, $start-percent: 0%, $middle-percent: 50%, $end-percent: 100%) { 192 | background-image: linear-gradient(to bottom, $start-color $start-percent, $middle-color $middle-percent, $end-color $end-percent); 193 | background-repeat: repeat-x; 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#{ie-hex-str($start-color)}", endColorstr="#{ie-hex-str($end-color)}", GradientType=0); // IE9 and down 195 | } 196 | 197 | /// 跨浏览器的渐变背景,水平渐变,自左而右 198 | /// 199 | /// @group 外观 200 | /// @name gradient-horizontal-threeColor 201 | /// @param {Color} $start-color [#555] - 渐变的开始颜色 202 | /// @param {Color} $middle-color [#444] - 渐变的中间颜色 203 | /// @param {Color} $end-color [#333] - 渐变的结束颜色 204 | /// @param {Number} $start-percent [0%] - 渐变的开始位置,需要以百分号为单位 205 | /// @param {Number} $start-percent [50%] - 渐变的中间位置,需要以百分号为单位 206 | /// @param {Number} $end-percent [100%] - 渐变的结束位置,需要以百分号为单位 207 | @mixin gradient-horizontal-threeColor($start-color: #555, $middle-color: #444, $end-color: #333, $start-percent: 0%, $middle-percent: 50%, $end-percent: 100%) { 208 | background-image: linear-gradient(to right, $start-color $start-percent, $middle-color $middle-percent, $end-color $end-percent); 209 | background-repeat: repeat-x; 210 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#{ie-hex-str($start-color)}", endColorstr="#{ie-hex-str($end-color)}", GradientType=1); // IE9 and down 211 | } 212 | 213 | /// 基于渐变实现的垂直方向点画线 214 | /// @throw 如果要实现的是一个物理像素粗细的线,建议放在一个单独的 DOM 上,方便加上 scale 来实现,否则就不需要顾虑直接用到任意 DOM 上即可,加上 background-position 控制位置,background-color / background-position 等属性必须在该 mixin 的 include 之后开始写。 215 | /// 216 | /// @group 外观 217 | /// @name gradient-vertical-dashed-line 218 | /// @param {Number} $dash-dot-width [2px] - 点画线里点的尺寸 219 | /// @param {Number} $dash-space-width [2px] - 点画线里点与点间隔尺寸 220 | /// @param {Color} $dash-dot-color [$common_color_separator] - 点画线里点的颜色 221 | /// @param {Number} $line-width [1px] - 点画线粗细 222 | @mixin gradient-vertical-dashed-line($dash-dot-width: 2px, $dash-space-width: 2px, $dash-dot-color: $common_color_separator, $line-width: 1px) { 223 | @include gradient-vertical-threeColor($start-color: $dash-dot-color, $middle-color: $dash-dot-color, $end-color: transparent, $start-percent: 0, $middle-percent: $dash-dot-width, $end-percent: $dash-dot-width); 224 | background-size: $line-width ($dash-dot-width + $dash-space-width); 225 | background-repeat: repeat-y; 226 | } 227 | 228 | /// 基于渐变实现的水平方向点画线 229 | /// @throw 如果要实现的是一个物理像素粗细的线,建议放在一个单独的 DOM 上,方便加上 scale 来实现,否则就不需要顾虑直接用到任意 DOM 上即可,加上 background-position 控制位置,background-color / background-position 等属性必须在该 mixin 的 include 之后开始写。 230 | /// 231 | /// @group 外观 232 | /// @name gradient-horizontal-dashed-line 233 | /// @param {Number} $dash-dot-width [2px] - 点画线里点的尺寸 234 | /// @param {Number} $dash-space-width [2px] - 点画线里点与点间隔尺寸 235 | /// @param {Color} $dash-dot-color [$common_color_separator] - 点画线里点的颜色 236 | /// @param {Number} $line-width [1px] - 点画线粗细 237 | @mixin gradient-horizontal-dashed-line($dash-dot-width: 2px, $dash-space-width: 2px, $dash-dot-color: $common_color_separator, $line-width: 1px) { 238 | @include gradient-horizontal-threeColor($start-color: $dash-dot-color, $middle-color: $dash-dot-color, $end-color: transparent, $start-percent: 0, $middle-percent: $dash-dot-width, $end-percent: $dash-dot-width); 239 | background-size: ($dash-dot-width + $dash-space-width) $line-width; 240 | background-repeat: repeat-x; 241 | } 242 | -------------------------------------------------------------------------------- /qmui/mixin/_mixin.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * _mixin.scss 4 | * @author Kayo 5 | * @date 2014-10-31 6 | * 7 | */ 8 | 9 | // 平台与浏览器适配相关方法 10 | @import "adaptation"; 11 | 12 | // 常用工具方法 13 | @import "tool/tool"; 14 | -------------------------------------------------------------------------------- /qmui/mixin/tool/_calculate.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @use "sass:math"; 4 | 5 | //// 6 | /// 辅助数值计算的工具方法 7 | /// @author Kayo 8 | /// @group 数值计算 9 | /// @date 2015-08-23 10 | //// 11 | 12 | /// 获取 CSS 长度值属性(例如:margin,padding,border-width 等)在某个方向的值 13 | /// 14 | /// @name getLengthDirectionValue 15 | /// @param {String} $property - 记录着长度值的 SASS 变量 16 | /// @param {String} $direction - 需要获取的方向,可选值为 top,right,bottom,left,horizontal,vertical,其中 horizontal 和 vertical 分别需要长度值的左右或上下方向值相等,否则会报 Warning。 17 | /// @example 18 | /// // UI 界面的一致性往往要求相似外观的组件保持距离、颜色等元素统一,例如: 19 | /// // 搜索框和普通输入框分开两种结构处理,但希望搜索框的搜索 icon 距离左边的空白与 20 | /// // 普通输入框光标距离左边的空白保持一致,就需要获取普通输入框的 padding-left 21 | /// $textField_padding: 4px 5px; 22 | /// .dm_textField { 23 | /// padding: $textField_padding; 24 | /// } 25 | /// .dm_searchInput { 26 | /// position: relative; 27 | /// ... 28 | /// } 29 | /// .dm_searchInput_icon { 30 | /// position: absolute; 31 | /// left: getLengthDirectionValue($textField_padding, left); 32 | /// ... 33 | /// } 34 | @function getLengthDirectionValue($property, $direction) { 35 | // 声明变量 36 | $top: 0; 37 | $right: 0; 38 | $bottom: 0; 39 | $left: 0; 40 | // 获取 $property 列表值中值的个数,从而判断是哪种 CSS length 的写法 41 | $propertyLength: length($property); 42 | @if $propertyLength == 1 { 43 | $top: $property; 44 | $right: $property; 45 | $bottom: $property; 46 | $left: $property; 47 | } @else if $propertyLength == 2 { 48 | $top: nth($property, 1); 49 | $right: nth($property, 2); 50 | $bottom: nth($property, 1); 51 | $left: nth($property, 2); 52 | } @else if $propertyLength == 3 { 53 | $top: nth($property, 1); 54 | $right: nth($property, 2); 55 | $bottom: nth($property, 3); 56 | $left: nth($property, 2); 57 | } @else if $propertyLength == 4 { 58 | $top: nth($property, 1); 59 | $right: nth($property, 2); 60 | $bottom: nth($property, 3); 61 | $left: nth($property, 4); 62 | } @else { 63 | @return 0; 64 | } 65 | 66 | // 根据参数中的方向值输出需要的结果 67 | @if $direction == top { 68 | @return $top; 69 | } @else if $direction == right { 70 | @return $right; 71 | } @else if $direction == bottom { 72 | @return $bottom; 73 | } @else if $direction == left { 74 | @return $left; 75 | } @else if $direction == horizontal { 76 | @if $left != $right { 77 | @warn "左边(#{$left})与右边(#{$right})的值并不相等,不应该直接使用 horizontal 这个方向"; 78 | } 79 | @return $left; 80 | } @else if $direction == vertical { 81 | @if $top != $bottom { 82 | @warn "上边(#{$top})与下边(#{$bottom})的值并不相等,不应该直接使用 vertical 这个方向"; 83 | } 84 | @return $top; 85 | } @else { 86 | @return 0; 87 | } 88 | } 89 | 90 | /// 获取两个 CSS 长度值的中间值并取整,通常可用于子元素在父元素中需要居中时计算两者高度差 91 | /// 92 | /// @name getLengthMaxIntegerCenter 93 | /// @param {Number | String} $parent - 较大的长度值 94 | /// @param {Number | String} $child - 较小的长度值 95 | @function getLengthMaxIntegerCenter($parent, $child) { 96 | $center: math.div($parent - $child, 2); 97 | // 注意这里的取整使用 ceil 而不是 floor 并不是随意写的,这是模拟现代浏览器对于小数点长度值的表现而定的。 98 | // 例如,margin-top: 10.5px 在现代浏览器中会表现为 margin-top: 11px 而不是 margin-top: 10px 99 | // 又例如,margin-top: -10.5px 在现代浏览器的表现等同于 margin-top: -10px 而不是 margin-top: -11px 100 | // 即小数长度值会被当成不小于该小数的下一个整数去处理,也就是 ceil 的效果。所以不要随意改成 floor,其他长度值方法也应该如此处理 101 | @return ceil($center); 102 | } 103 | 104 | /// 获取数值的n次幂的值 105 | /// 106 | /// @name pow 107 | /// @param {Number} $number - 底数 108 | /// @param {Number} $pow - 幂数 109 | /// @example 110 | /// pow(10, 5) => 100000 111 | /// pow(10, -1) => 0.1 112 | @function pow($number, $pow) { 113 | $result: 1; 114 | @if $pow > 0 { 115 | @for $i from 1 through $pow { 116 | $result: $result * $number; 117 | } 118 | } @else if $pow < 0 { 119 | @for $i from $pow to 0 { 120 | $result: $result / $number; 121 | } 122 | } 123 | @return $result; 124 | } 125 | 126 | /// 获取数值的开平方值 127 | /// 128 | /// @name sqrt 129 | /// @param {Number} $number - 待开平方的数值 130 | /// @example 131 | /// sqrt(2) => 1.414214 132 | @function sqrt($num) { 133 | $temp:1; 134 | @while abs($temp - $num / $temp) > 1e-6 { 135 | $temp: ($temp + $num / $temp) / 2; 136 | } 137 | @return $temp; 138 | } 139 | 140 | /// 将数值格式化为指定小数位数的数字。 141 | /// 142 | /// @name toFixed 143 | /// @param {Number} $number - 待格式化的数值 144 | /// @param {Number} $precision [0] - 精确度(精确到小数点后几位) 145 | /// @param {String} $type [round] - 格式化方式("round":"四舍五入","floor":"向下取整","ceil":"向上取整") 146 | /// @example 147 | /// toFixed(3.1415926535898) => 3.14 148 | /// toFixed(3.1415926535898, 4, floor) => 3.1415 149 | /// toFixed(3.1415926535898, 4, ceil) => 3.1416 150 | /// toFixed(-3.1415926535898, 4, floor) => -3.1416 151 | /// toFixed(-3.1415926535898, 4, ceil) => -3.1415 152 | /// toFixed(3.1415926535898px) => 3.14px 153 | @function toFixed($number, $precision: 0, $type: round) { 154 | $result: null; 155 | @if $type == round { 156 | $result: round($number * pow(10, $precision)) / pow(10, $precision); 157 | } @else if $type == floor { 158 | $result: floor($number * pow(10, $precision)) / pow(10, $precision); 159 | } @else if $type == ceil { 160 | $result: ceil($number * pow(10, $precision)) / pow(10, $precision); 161 | } @else { 162 | @warn "type参数输入有误,请选择输入'round'、'floor'、'ceil'其中一个"; 163 | $result: $number; 164 | } 165 | @return $result; 166 | } 167 | 168 | /// 阶乘计算 169 | /// 170 | /// @name factorial 171 | /// @param {Number} $number - 待进行阶乘计算的数值 172 | /// @example 173 | /// factorial(4) => 4 * 3 * 2 * 1 => 24 174 | @function factorial($number) { 175 | $value: 1; 176 | @if $number > 0 { 177 | @for $i from 1 through $number { 178 | $value: $value * $i; 179 | } 180 | } 181 | @return $value; 182 | } 183 | 184 | /// 获取 π 的值(11位小数精度) 185 | /// 186 | /// @name pi 187 | @function pi() { 188 | @return 3.14159265359; 189 | } 190 | 191 | /// 通过角度计算弧度 192 | /// 193 | /// @name rad 194 | /// @param {Number} $angle - 需要被转换为弧度的角度值 195 | /// @example 196 | /// rad(180deg) -> 3.14159 197 | /// rad(45deg) -> 0.7854 198 | @function rad($angle) { 199 | $unit: unit($angle); 200 | $unitless: $angle / ($angle * 0 + 1); 201 | @if $unit == deg { 202 | $unitless: $unitless / 180 * pi(); 203 | } 204 | @return $unitless; 205 | } 206 | 207 | /// 计算 sin 三角函数 208 | /// 209 | /// @name sin 210 | /// @param {Number} $angle - 需要进行 sin 计算的角度值 211 | /// @example 212 | /// sin(45deg) -> 0.70711 213 | /// sin(90deg) -> 1 214 | @function sin($angle) { 215 | $sin: 0; 216 | $angle: rad($angle); 217 | @for $i from 0 through 10 { 218 | $sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / factorial(2 * $i + 1); 219 | } 220 | @return $sin; 221 | } 222 | 223 | /// 计算 cos 三角函数 224 | /// 225 | /// @name cos 226 | /// @param {Number} $angle - 需要进行 cos 计算的角度值 227 | /// @example 228 | /// cos(45deg) -> 0.70711 229 | /// cos(90deg) -> 0 230 | @function cos($angle) { 231 | $cos: 0; 232 | $angle: rad($angle); 233 | @for $i from 0 through 10 { 234 | $cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / factorial(2 * $i); 235 | } 236 | @return $cos; 237 | } 238 | 239 | /// 计算 tan 三角函数 240 | /// 241 | /// @name tan 242 | /// @param {Number} $angle - 需要进行 tan 计算的角度值 243 | /// @example 244 | /// tan(45deg) -> 1 245 | /// tan(50deg) -> 1.19175 246 | @function tan($angle) { 247 | @return sin($angle) / cos($angle); 248 | } 249 | -------------------------------------------------------------------------------- /qmui/mixin/tool/_effect.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @use "sass:math"; 4 | 5 | //// 6 | /// 辅助编写样式效果的工具方法 7 | /// @author Kayo 8 | /// @group 样式特效 9 | /// @date 2015-08-23 10 | //// 11 | 12 | /// 产生正方形的宽高 13 | /// 14 | /// @name square 15 | /// @param {Measure} $length - 宽高的长度 16 | @mixin square($length) { 17 | width: $length; 18 | height: $length; 19 | } 20 | 21 | /// 利用 absolute 把指定元素水平垂直居中布局,适用于已知元素宽高的情况下 22 | /// 23 | /// @name absoluteCenter 24 | /// @param {Measure} $width - 元素的宽度 25 | /// @param {Measure} $height - 元素的高度 26 | @mixin absoluteCenter($width, $height) { 27 | position: absolute; 28 | left: 50%; 29 | top: 50%; 30 | margin: math.div(-$height, 2) 0 0 math.div(-$width, 2); 31 | } 32 | 33 | %triangleCommonStyle { 34 | display: block; 35 | content: " "; 36 | width: 0; 37 | line-height: 0; 38 | font-size: 0; 39 | border-style: solid; 40 | border-color: transparent; 41 | } 42 | 43 | /// CSS Border 三角形 44 | /// 45 | /// @name triangle 46 | /// @param {Measure} $width - 三角形的底边的宽 47 | /// @param {Measure} $height - 三角形的高 48 | /// @param {String} $direction - 三角形的方向(即与底边相对的顶点指向的方向) 49 | /// @param {Color} $borderColor - 三角形的边框色 50 | /// @throw 由于方法内包含了有 $width / 2 的计算,因此如果 $width 的值为奇数,则实际上计算出的三角形会偏小,建议 $width 不要使用奇数。 51 | @mixin triangle($width, $height, $direction, $borderColor) { 52 | @extend %triangleCommonStyle; 53 | /* 向上小三角 */ 54 | @if $direction == top { 55 | border-width: $height math.div($width, 2); 56 | border-top: 0; 57 | border-bottom-color: $borderColor; 58 | } 59 | /* 向下小三角 */ 60 | @else if $direction == bottom { 61 | border-width: $height math.div($width, 2); 62 | border-bottom: 0; 63 | border-top-color: $borderColor; 64 | } 65 | /* 向左小三角 */ 66 | @else if $direction == left { 67 | border-width: math.div($width, 2) $height; 68 | border-left: 0; 69 | border-right-color: $borderColor; 70 | } 71 | /* 向右小三角 */ 72 | @else if $direction == right { 73 | border-width: math.div($width, 2) $height; 74 | border-right: 0; 75 | border-left-color: $borderColor; 76 | } 77 | } 78 | 79 | /// 用以生成十字架图标 80 | /// 81 | /// @name cross 82 | /// @param {Measure} $crossLength [26px] - 十字架的大小 83 | /// @param {Measure} $crossLineThickness [2px] - 十字架线条的粗细 84 | /// @param {Color} $crossLineColor [#2685d2] - 十字架的颜色 85 | @mixin cross($crossLength: 26px, $crossLineThickness: 2px, $crossLineColor: $common_color_link) { 86 | position: relative; 87 | @include square($crossLength); 88 | &:before, 89 | &:after { 90 | content: ""; 91 | font-size: 0; 92 | line-height: 0; 93 | position: absolute; 94 | background-color: $crossLineColor; 95 | } 96 | &:before { 97 | left: getLengthMaxIntegerCenter($crossLength, $crossLineThickness); 98 | top: 0; 99 | width: $crossLineThickness; 100 | height: 100%; 101 | } 102 | &:after { 103 | left: 0; 104 | top: getLengthMaxIntegerCenter($crossLength, $crossLineThickness); 105 | width: 100%; 106 | height: $crossLineThickness; 107 | } 108 | } 109 | 110 | /// 使得指定的元素产生 Block Formatting Contexts 或 hasLayout 111 | /// 112 | /// @name bfc 113 | @mixin bfc { 114 | overflow: hidden; 115 | zoom: 1; 116 | } 117 | 118 | // borderStyleForOnePixel 是 onePixelBorder 内部使用的方法 119 | @mixin borderStyleForOnePixel($direction: all, $color:#dedede) { 120 | border-style: solid; 121 | border-color: $color; 122 | @if $direction == all { 123 | border-width: 1px; 124 | } @else if $direction == top { 125 | border-width: 1px 0 0 0; 126 | } @else if $direction == bottom { 127 | border-width: 0 0 1px 0; 128 | } @else if $direction == left { 129 | border-width: 0 0 0 1px; 130 | } @else if $direction == right { 131 | border-width: 0 1px 0 0; 132 | } @else if $direction == horizontal { 133 | border-width: 0 1px; 134 | } @else if $direction == vertical { 135 | border-width: 1px 0; 136 | } @else if $direction == none { 137 | border-width: 0; 138 | } 139 | } 140 | 141 | /// 在移动设备上生成 1px 宽的边框,direction 支持 all, top, bottom, left, right, horizontal, vertical, none 8个 direction 值,position 支持 outside 和 inside 两个值 142 | /// 143 | /// @name onePixelBorder 144 | /// @param {String} $direction [all] - 边框的方向,支持 all(所有方向),top(上边框),right(右边框),bottom(下边框),left(左边框),horizontal(左右边框),vertical(上下边框),none(无边框) 145 | /// @param {Color} $color - 边框的颜色 146 | /// @param {String} $position [outside] - 边框的位置,支持 outside 和 inside 147 | /// @param {Number} $borderRadius [0] - 边框的圆角 148 | /// @param {Number} $offset 水平缩进值 149 | /// @throw 在多倍屏下,本方法会利用元素的 ::after 做效果,因此需要注意使用了该方法后 ::after 则尽量避免添加样式,以免影响效果 150 | @mixin onePixelBorder($direction: all, $color: $common_color_border, $position: outside, $borderRadius: 0, $borderStyle: solid, $offset: 0) { 151 | @include borderStyleForOnePixel($direction, $color); 152 | border-radius: $borderRadius; 153 | border-style: $borderStyle; 154 | @include screenResolution(2) { 155 | position: relative; 156 | border: 0; 157 | &:after { 158 | content: ""; 159 | position: absolute; 160 | top: 0; 161 | left: $offset; 162 | @if $offset == 0 { 163 | width: 200%; 164 | } @else { 165 | width: calc(200% - #{$offset * 2 * 2}); 166 | } 167 | height: 200%; 168 | border-radius: $borderRadius * 2; 169 | @include borderStyleForOnePixel($direction, $color); 170 | border-style: $borderStyle; 171 | transform: scale(.5); 172 | transform-origin: 0 0; 173 | @if $position == inside { 174 | box-sizing: border-box; 175 | } 176 | pointer-events: none; 177 | } 178 | } 179 | @include screenResolution(3) { 180 | &:after { 181 | @if $offset == 0 { 182 | width: 300%; 183 | } @else { 184 | width: calc(300% - #{$offset * 3 * 2}); 185 | } 186 | height: 300%; 187 | border-radius: $borderRadius * 3; 188 | transform: scale(math.div(1, 3)); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /qmui/mixin/tool/_enhance.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | //// 4 | /// 原生增强,补充 SASS 原生语法缺少的能力 5 | /// @author Kayo 6 | /// @group Sass 原生增强 7 | /// @date 2016-06-18 8 | //// 9 | 10 | /// 字符串 replace 方法,用于在字符串中用一些字符替换另一些字符 11 | /// 12 | /// @name str-replace 13 | /// @param {String} $string - 需要进行查找的字符串 14 | /// @param {String} $search - 规定需要被替换的子字符串 15 | /// @param {String} $replace [''] - 替换文本 16 | /// @example 17 | /// str-replace("QMUI Web", " Web") => "QMUI" 18 | /// str-replace("QMUI Web", "Web", "iOS") => "QMUI iOS" 19 | /// str-replace("QMUI Web", "Web", "Android") => "QMUI Android" 20 | @function str-replace($string, $search, $replace: "") { 21 | $index: str-index($string, $search); 22 | 23 | @if $index { 24 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 25 | } 26 | 27 | @return $string; 28 | } 29 | 30 | /// 加亮颜色(以百分比的形式加入加色) 31 | /// 32 | /// @param {Color} $color - 需要被加亮的颜色值 33 | /// @param {Number} $percentage - 需要增加的白色的百分比 34 | @function tint($color, $percentage) { 35 | @return mix(white, $color, $percentage); 36 | } 37 | 38 | /// 加暗颜色(以百分比的形式加入黑色) 39 | /// 40 | /// @param {Color} $color - 需要被加暗的颜色值 41 | /// @param {Number} $percentage - 需要增加的黑色的百分比 42 | @function shade($color, $percentage) { 43 | @return mix(black, $color, $percentage); 44 | } 45 | -------------------------------------------------------------------------------- /qmui/mixin/tool/_tool.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * _tool.scss 常用工具方法 4 | * @author Kayo 5 | * @date 2015-08-23 6 | * 7 | */ 8 | 9 | // 辅助编写样式效果的工具方法 10 | @import "effect"; 11 | 12 | // 辅助数值计算的工具方法 13 | @import "calculate"; 14 | 15 | // 原生增强,补充 SASS 原生语法缺少的能力 16 | @import "enhance"; 17 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stylelint Config for `QMUI Web` 3 | * 4 | * https://stylelint.io/ 5 | */ 6 | 7 | module.exports = { 8 | 'ignoreFiles': [''], 9 | 'plugins': [ 10 | 'stylelint-wechat-work-css' 11 | ], 12 | 'rules': { 13 | /** 14 | * Plungins 15 | */ 16 | // @include 黑名单 17 | 'wechat-work/unused-mixins': 18 | [ 19 | '/^transition/', 20 | '/^transform/', 21 | '/^translate/', 22 | '/^scale/', 23 | '/^rotate/', 24 | '/^animation/', 25 | 'box-sizing', 26 | 'box_sizing', 27 | 'inlineBlock', 28 | 'box-shadow', 29 | 'box_shadow', 30 | 'opacity', 31 | 'keyframes' 32 | ], 33 | 'wechat-work/comments-in-header': false, // 文件头部需要有注释 @date 和 @author,QMUI Web 源码中使用的是 SassDoc 的注释格式,不被识别,因此设置为 false 34 | 'wechat-work/selector-namespace-follow-filename': [true, { 35 | 'fileDirWhiteList': ['widget', 'qmui'], 36 | 'filenameWhitelist': [] 37 | }], // 业务CSS 的命名空间需要跟随文件名(关于命名空间请浏览器:http://qmuiteam.com/web/page/codeNorm.html#qui_htmlAndCSSNorm) 38 | 'wechat-work/unused-nested-selector-namespace': true, // 不建议在嵌套中使用 qui_ 开头的类 39 | 40 | /** 41 | * Color 42 | */ 43 | 'color-hex-length': 'short', // 指定十六进制颜色是否使用缩写 44 | // 禁止使用颜色名称 45 | 'color-named': ['never', { 46 | 'ignore': ['inside-function'] 47 | }], 48 | 49 | /** 50 | * Function 51 | */ 52 | 'function-comma-space-after': 'always', // 函数内的内容,逗号之后要求有一个空格或禁止有空白 53 | 54 | /** 55 | * Number 56 | */ 57 | 'number-leading-zero': 'never', // 禁止书写小数的前导 0 58 | 'number-no-trailing-zeros': true, // 禁止数字中的拖尾 0 59 | 60 | /** 61 | * String 62 | */ 63 | 'string-quotes': 'double', // 字符串用双引号 64 | 65 | /** 66 | * Length 67 | */ 68 | 'length-zero-no-unit': true, // 长度值为0时,禁止带单位 69 | 70 | /** 71 | * Block 72 | */ 73 | 'block-opening-brace-space-before': 'always', // 尖括号前必须有空格 74 | 75 | /** 76 | * Selector 77 | */ 78 | 'selector-max-id': 0, // 选择器不能为 id 79 | 'selector-combinator-space-after': 'always', // 在关系选择符之后要求有一个空格或禁止有空白 80 | 'selector-max-compound-selectors': 4, // 限制复合选择器的数量 81 | // 选择器命名规范 82 | 'selector-class-pattern': ['^((?!js)[a-z][a-zA-Z0-9]*)(_[a-zA-Z0-9]+)*$', { 83 | 'resolveNestedSelectors': true 84 | }], 85 | 86 | /** 87 | * Declaration 88 | */ 89 | 'declaration-colon-space-after': 'always', // 在冒号之后要求有一个空格或禁止有空白 90 | 'declaration-block-semicolon-newline-after': 'always', // 在声明块的分号之后要求有一个换行符或禁止有空白 91 | // 不能用的规则,目前有 border: none; 建议为 border: 0;或 border: 0 none; 92 | 'declaration-property-value-blacklist': { 93 | 'border': ['none'] 94 | }, 95 | 96 | /** 97 | * Property 98 | */ 99 | 'property-no-vendor-prefix': true, // 禁止使用带浏览器前缀的属性 100 | 101 | /** 102 | * Value 103 | */ 104 | 'value-no-vendor-prefix': true, // 禁止使用带浏览器前缀的属性值 105 | 'value-list-comma-space-after': 'always', // 在属性值的内容中,逗号之后要求有一个空格或禁止有空白 106 | 107 | /** 108 | * At-rule 109 | */ 110 | 'at-rule-no-vendor-prefix': true, // 禁止 at(@) 规则使用浏览器前缀 111 | 'keyframe-declaration-no-important': true, // 禁止在 keyframe 声明中使用 !important 112 | 113 | /** 114 | * Rule 115 | */ 116 | // 除特殊情况,规则前必须有空行 117 | 'rule-empty-line-before': ['always', { 118 | 'ignore': ['after-comment', 'inside-block'] 119 | }], 120 | 121 | /** 122 | * Comment 123 | */ 124 | 'comment-no-empty': true, // 禁止空注释,CSS 规则内注释不受限制 125 | 'comment-whitespace-inside': 'always', // 注释前后需要有空格 126 | 127 | /** 128 | * General / Sheet 129 | */ 130 | 'max-nesting-depth': 4, // 限制允许嵌套的深度 131 | 'indentation': 4 // 缩进,4 spaces 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /workflow/Mix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 声明插件以及配置文件的依赖 17 | const plugins = require('gulp-load-plugins')({ 18 | rename: { 19 | 'gulp-file-include': 'include', 20 | 'gulp-merge-link': 'merge', 21 | 'gulp-better-sass-inheritance': 'sassInheritance' 22 | } 23 | }); 24 | const packageInfo = require('../package.json'); 25 | const browserSync = require('browser-sync').create(); 26 | const reload = browserSync.reload; 27 | const colors = require('ansi-colors'); 28 | const defaultsDeep = require('lodash/defaultsDeep'); 29 | const util = new (require('./Util'))(); 30 | 31 | let configDefault; 32 | let configUser = {}; 33 | 34 | // 读取项目配置表 35 | try { 36 | configDefault = require('../../qmui.config.js'); 37 | } catch (error) { 38 | util.log(colors.red('QMUI Config: ') + '找不到项目配置表,请按照 http://qmuiteam.com/web/index.html 的说明进行项目配置'); 39 | } 40 | 41 | try { 42 | configUser = require('../../qmui.config.user.js'); 43 | } catch (error) { 44 | // 没有个人用户配置,无需额外处理 45 | } 46 | 47 | // 创建 common 对象 48 | class Mix { 49 | constructor() { 50 | this.plugins = plugins; 51 | this.config = defaultsDeep(configUser, configDefault); 52 | this.packageInfo = packageInfo; 53 | this.browserSync = browserSync; 54 | this.reload = reload; 55 | 56 | this.timeFormat = new (require('./TimeFormat'))(); 57 | this.util = util; 58 | 59 | this.tasks = {}; 60 | } 61 | 62 | /** 63 | * 增加任务说明的接口。 64 | * @param {String} name QMUI 工作流中任务的名字。 65 | * @param {String} description 任务的简短介绍。 66 | * @param {Object} options 任务的参数。 67 | * @returns {undefined} 68 | */ 69 | addTaskDescription(name, description, options) { 70 | this.tasks[name] = { 71 | description: description, 72 | options: options 73 | }; 74 | } 75 | 76 | /** 77 | * 获取所有任务。 78 | * @returns {Array} 返回 QMUI 工作流中所有任务。 79 | */ 80 | getAllTask() { 81 | return this.tasks; 82 | } 83 | 84 | /** 85 | * 获取单个任务的信息。 86 | * @param {String} name 需要获取的 QMUI 工作流任务的名字。 87 | * @returns {Object} 返回 QMUI 工作流中某个指定的任务。 88 | */ 89 | getTask(name) { 90 | return this.tasks[name]; 91 | } 92 | } 93 | 94 | 95 | module.exports = Mix; 96 | -------------------------------------------------------------------------------- /workflow/TimeFormat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | const padStart = require('lodash/padStart'); 16 | 17 | // 工具方法 18 | class TimeFormat { 19 | checkDateFormat(date) { 20 | return padStart(date.toString(), 2, '0'); 21 | } 22 | 23 | getCurrentTime() { 24 | const time = new Date(); 25 | return `${this.checkDateFormat(time.getHours())}:${this.checkDateFormat(time.getMinutes())}:${this.checkDateFormat(time.getSeconds())}`; 26 | } 27 | } 28 | 29 | module.exports = TimeFormat; 30 | -------------------------------------------------------------------------------- /workflow/Util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | const beeper = require('beeper'); 16 | const colors = require('ansi-colors'); 17 | const fancyLog = require('fancy-log'); 18 | const argv = require('yargs').argv; 19 | const supportsColor = require('color-support'); 20 | 21 | /** 22 | * 创建工具类,放置工具方法。 23 | */ 24 | class Util { 25 | constructor() { 26 | this.beep = beeper; 27 | this.colors = colors; 28 | } 29 | 30 | 31 | addColor(str, type) { 32 | if (supportsColor() && (typeof argv.color === 'undefined' || argv.color)) { 33 | if (type === 'warn') { 34 | return this.colors.yellow(str); 35 | } else if (type === 'error') { 36 | return this.colors.red(str); 37 | } else if (type === 'info') { 38 | return this.colors.gray(str); 39 | } 40 | return this.colors.green(str); 41 | } 42 | return str; 43 | } 44 | 45 | log(tag, content) { 46 | if (arguments.length > 1) { 47 | fancyLog(this.addColor(`QMUI ${tag}: `, 'log') + content); 48 | } else { 49 | fancyLog(arguments[0]); 50 | } 51 | } 52 | 53 | warn(tag, content) { 54 | if (arguments.length > 1) { 55 | fancyLog(this.addColor(`QMUI ${tag}: `, 'warn') + content); 56 | } else { 57 | fancyLog(arguments[0]); 58 | } 59 | } 60 | 61 | error(tag, content) { 62 | if (arguments.length > 1) { 63 | fancyLog(this.addColor(`QMUI ${tag}: `, 'error') + content); 64 | } else { 65 | fancyLog(arguments[0]); 66 | } 67 | } 68 | } 69 | 70 | module.exports = Util; 71 | -------------------------------------------------------------------------------- /workflow/basicTasks/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 清理多余文件 17 | const del = require('del'); 18 | 19 | module.exports = (gulp, mix) => { 20 | 21 | const taskName = 'clean'; 22 | 23 | gulp.task(taskName, (done) => { 24 | // force: true 即允许 del 控制本目录以外的文件 25 | del(mix.config.cleanFileType, {force: true}); 26 | mix.util.log('Clean', `清理所有的 ${mix.config.cleanFileType.join(', ')} 文件`); 27 | 28 | done(); 29 | }); 30 | 31 | // 任务说明 32 | mix.addTaskDescription(taskName, '清理多余文件(清理内容在 config.json 中配置)'); 33 | }; 34 | -------------------------------------------------------------------------------- /workflow/basicTasks/include.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 模板 include 命令,解释被 include 的内容并输出独立的 HTML 文件 17 | const path = require('path'); 18 | 19 | module.exports = (gulp, mix) => { 20 | 21 | const taskName = 'include'; 22 | 23 | gulp.task(taskName, (done) => { 24 | 25 | const _condition = (file) => { 26 | const fileName = path.basename(file.path); 27 | return !fileName.match(/^_/); 28 | }; 29 | 30 | gulp.src(mix.config.paths.htmlSourcePath) 31 | .pipe(mix.plugins.plumber({ 32 | errorHandler: (error) => { 33 | mix.util.error('Include', error); 34 | mix.util.beep(); 35 | } 36 | })) 37 | .pipe(mix.plugins.include({ 38 | prefix: mix.config.template.includePrefix // 模板函数的前缀 39 | })) 40 | .pipe(mix.plugins.if(_condition, gulp.dest(mix.config.paths.htmlResultPath))); 41 | 42 | mix.util.log('Include', `根据 include 标签合并后输出新文件到 ${mix.config.paths.htmlResultPath}`); 43 | 44 | done(); 45 | }); 46 | 47 | // 任务说明 48 | mix.addTaskDescription(taskName, '执行模板 include 编译(建议调用 watch 任务自动监控文件变化并调用)'); 49 | }; 50 | -------------------------------------------------------------------------------- /workflow/basicTasks/list.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** 3 | * Tencent is pleased to support the open source community by making QMUI Web available. 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at 7 | * 8 | * http://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 11 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 12 | * either express or implied. See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | const calculateMargin = require('../calculateMargin.js'); 17 | 18 | // 显示 QMUI Web 的版本号 19 | module.exports = (gulp, mix) => { 20 | 21 | const taskName = 'list'; 22 | 23 | gulp.task(taskName, (done) => { 24 | const marginData = calculateMargin(mix.tasks); 25 | const margin = marginData.margin; 26 | const optionsBuffer = marginData.hasOptions ? ' --' : ''; 27 | 28 | console.log(''); 29 | console.log('Usage'); 30 | console.log(' gulp [TASK] [OPTIONS...]'); 31 | console.log(''); 32 | console.log('Available tasks'); 33 | 34 | Object.keys(mix.getAllTask()).forEach((name) => { 35 | 36 | const help = mix.getTask(name); 37 | 38 | const args = [' ', mix.util.colors.cyan(name)]; 39 | 40 | args.push(new Array(margin - name.length + 1 + optionsBuffer.length).join(' ')); 41 | 42 | if (help.description) { 43 | args.push(help.description); 44 | } 45 | 46 | if (help.options) { 47 | const options = Object.keys(help.options); 48 | options.forEach((option) => { 49 | const optText = help.options[option]; 50 | args.push('\n ' + optionsBuffer + mix.util.colors.cyan(option) + ' '); 51 | 52 | args.push(new Array(margin - option.length + 1).join(' ')); 53 | args.push(optText); 54 | }); 55 | } 56 | 57 | console.log.apply(console, args); 58 | }); 59 | 60 | console.log(''); 61 | 62 | done(); 63 | }); 64 | 65 | // 任务说明 66 | mix.addTaskDescription(taskName, 'QMUI 内置工作流帮助菜单'); 67 | }; 68 | -------------------------------------------------------------------------------- /workflow/basicTasks/merge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 合并变更文件 17 | const path = require('path'); 18 | const argv = require('yargs').argv; 19 | const through = require('through2'); 20 | const minimatch = require('minimatch'); 21 | 22 | module.exports = (gulp, mix) => { 23 | 24 | const taskName = 'merge'; 25 | 26 | const mergeReference = (rules) => { 27 | // 基于 https://github.com/aDaiCode/gulp-merge-link 28 | rules = rules || []; 29 | 30 | const linkRegex = //g; 31 | const scriptRegex = //g; 32 | 33 | const linkTemplate = (href) => ''; 34 | const scriptTemplate = (src) => ''; 35 | 36 | const getReference = (reg, contents) => { 37 | let result; 38 | const references = []; 39 | /* eslint-disable no-cond-assign */ 40 | while (result = reg.exec(contents)) { 41 | references.push({ 42 | match: result[0], 43 | url: result[1].trim().replace(/^\.\//, '') 44 | }); 45 | } 46 | /* eslint-enable no-cond-assign */ 47 | return references; 48 | }; 49 | 50 | const getTemplate = (url) => { 51 | const isScript = (/\.js$/).test(url); 52 | if (isScript) { 53 | return scriptTemplate(url); 54 | } 55 | return linkTemplate(url); 56 | }; 57 | 58 | return through.obj((file, encoding, callback) => { 59 | if (file.isNull() || file.isStream()) { 60 | return callback(null, file); 61 | } 62 | 63 | let contents = String(file.contents); 64 | let references = []; 65 | const replaceList = []; 66 | const flag = {}; 67 | 68 | // 获取所有引用 69 | references = references.concat(getReference(linkRegex, contents)).concat(getReference(scriptRegex, contents)); 70 | 71 | // 循环所有引用,检测是否需要进行处理 72 | for (const key in references) { 73 | const reference = references[key]; 74 | 75 | for (const targetUrl in rules) { 76 | // 把引用与传入的合并规则进行对比,把命中规则的引用进行合并处理 77 | if (!rules.hasOwnProperty(targetUrl)) { 78 | break; 79 | } 80 | const sourceUrls = rules[targetUrl]; 81 | 82 | const sourceUrlFound = sourceUrls.find((sourceUrl) => { 83 | sourceUrl = sourceUrl.trim().replace(/^\.\//, ''); 84 | 85 | return minimatch(reference.url, sourceUrl); 86 | }); 87 | 88 | if (sourceUrlFound) { 89 | replaceList.push({ 90 | match: reference.match, 91 | replace: flag[targetUrl] ? '' : getTemplate(targetUrl) 92 | }); 93 | 94 | flag[targetUrl] = true; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | if (argv.debug) { 101 | mix.util.log('Merge', file.path); 102 | } 103 | 104 | replaceList.forEach((replace) => { 105 | contents = contents.replace(replace.match, replace.replace); 106 | }); 107 | 108 | file.contents = Buffer.from(contents); 109 | 110 | return callback(null, file); 111 | }); 112 | }; 113 | 114 | gulp.task(taskName, (done) => { 115 | // 读取合并规则并保存起来 116 | let mergeRule; 117 | try { 118 | mergeRule = require('../../../qmui.merge.rule.js'); 119 | } catch (error) { 120 | mix.util.error('Merge', '没有找到合并规则文件,请按照 http://qmuiteam.com/web/scaffold.html#qui_scaffoldMerge 的说明进行合并规则配置'); 121 | } 122 | 123 | const replaceProjectParentDirectory = (source) => { 124 | // 转换为以项目根目录为开头的路径形式 125 | const projectParentDirectory = path.resolve('../../..'); 126 | return source.replace(projectParentDirectory, '').replace(/^[\\/]/, ''); 127 | }; 128 | 129 | // 合并文件 130 | for (const sourceFile in mergeRule) { 131 | // 后面变更文件时,需要的是每个文件在 HTML 中书写的路径,即相对模板文件的路径 132 | // 但对合并文件,即 concat 来说,需要的是文件相对 qmui_web 目录的路径,因此需要对合并的结果以及来源文件手工加上一个 '../' 133 | 134 | const resultFile = `../${sourceFile}`; // 合并的结果加上 '../' 135 | const resultFileName = path.basename(resultFile); 136 | const resultFilePath = path.dirname(resultFile); 137 | const value = mergeRule[sourceFile]; // 来源文件原始路径获取 138 | 139 | const childFiles = []; 140 | let childFilesString = ''; // 用于在 Log 中显示 141 | 142 | // 遍历来源文件并给每个文件加上 '../' 143 | for (let index = 0; index < value.length; index++) { 144 | const childFilesRelative = `../${value[index]}`; 145 | childFiles.push(childFilesRelative); 146 | 147 | // 拼接源文件名用于 Log 中显示 148 | if (index === 0) { 149 | childFilesString = replaceProjectParentDirectory(path.resolve(childFilesRelative)); 150 | } else { 151 | childFilesString = `${childFilesString}, ${replaceProjectParentDirectory(path.resolve(childFilesRelative))}`; 152 | } 153 | } 154 | 155 | const condition = (file) => file.path.toString().indexOf('.js') !== -1; 156 | 157 | gulp.src(childFiles) 158 | .pipe(mix.plugins.plumber({ 159 | errorHandler: (error) => { 160 | mix.util.error('Merge', error); 161 | mix.util.beep(); 162 | } 163 | })) 164 | .pipe(mix.plugins.concat(resultFileName)) 165 | .pipe(mix.plugins.if(condition, mix.plugins.uglify(), mix.plugins.cleanCss({compatibility: 'ie8'}))) 166 | .pipe(gulp.dest(resultFilePath)); 167 | 168 | mix.util.log('Merge', `文件 ${childFilesString} 合并压缩为 ${replaceProjectParentDirectory(path.resolve(path.join(resultFilePath, resultFileName)))}`); 169 | } 170 | // 变更文件引用路径 171 | gulp.src(mix.config.paths.htmlResultPath + '/**/*.html') 172 | .pipe(mergeReference(mergeRule)) 173 | .pipe(mix.plugins.htmlmin({ 174 | removeComments: true, 175 | collapseWhitespace: true 176 | })) 177 | .pipe(gulp.dest(mix.config.paths.htmlResultPath)); 178 | mix.util.log('Merge', '文件合并变更已完成'); 179 | 180 | done(); 181 | }); 182 | 183 | // 任务说明 184 | mix.addTaskDescription(taskName, '合并变更文件'); 185 | }; 186 | -------------------------------------------------------------------------------- /workflow/basicTasks/proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // proxy 监视文件改动并重新载入 17 | module.exports = (gulp, mix) => { 18 | 19 | gulp.task('proxy', (done) => { 20 | 21 | const showLog = () => { 22 | if (mix.config.browserSync.browserSyncShowLog) { 23 | return 'info'; 24 | } 25 | return 'silent'; 26 | }; 27 | 28 | mix.browserSync.init({ 29 | open: 'external', 30 | proxy: mix.config.browserSync.browserSyncProxy, 31 | port: mix.config.browserSync.browserSyncPort, 32 | host: mix.config.browserSync.browserSyncHost, 33 | logLevel: showLog(), 34 | logPrefix: mix.util.addColor(mix.timeFormat.getCurrentTime(), 'info'), 35 | startPath: mix.config.browserSync.browserSyncStartPath 36 | }); 37 | gulp.watch(mix.config.browserSync.browserSyncWatchPath).on('all', mix.reload); 38 | 39 | done(); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /workflow/basicTasks/readToolMethod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | const fs = require('fs'); 16 | const sassdoc = require('sassdoc'); 17 | const isEqual = require('lodash/isEqual'); 18 | 19 | // 读取含有工具方法的 Sass 文件列表(Sass 文件需要以 Sassdoc 格式编写注释),并将工具名称集输出为 JS 文件 20 | // 传入 Sass 文件列表,以及待输出的 JS 文件地址 21 | module.exports = (gulp) => { 22 | 23 | gulp.task('readToolMethod', (done) => { 24 | 25 | sassdoc.parse('./qmui/mixin').then((data) => { 26 | if (data.length > 0) { 27 | // 按 group 把数组重新整理成二维数组 28 | const result = []; 29 | let currentGroup = null, 30 | currentGroupArray = null; 31 | for (let itemIndex = 0; itemIndex < data.length; itemIndex++) { 32 | const item = data[itemIndex]; 33 | if (item.group.toString() !== 'abandon') { 34 | // 排除已废弃的工具方法 35 | 36 | // 由于 IE8- 下 default 为属性的保留关键字,会引起错误,因此这里要把参数中这个 default 的 key 从数据里改名 37 | if (item.parameter) { 38 | for (let parameterIndex = 0; parameterIndex < item.parameter.length; parameterIndex++) { 39 | const paraItem = item.parameter[parameterIndex]; 40 | if (paraItem.hasOwnProperty('default')) { 41 | paraItem.defaultValue = paraItem.default; 42 | delete paraItem.default; 43 | } 44 | } 45 | } 46 | 47 | if (!isEqual(item.group, currentGroup)) { 48 | currentGroup = item.group; 49 | currentGroupArray = []; 50 | result.push(currentGroupArray); 51 | } else { 52 | currentGroupArray = result[result.length - 1]; 53 | } 54 | currentGroupArray.push(item); 55 | } 56 | } 57 | result.reverse(); 58 | 59 | // 准备把数组写入到指定文件中 60 | 61 | const _outputPath = '../../data/qmui_method.js'; 62 | 63 | // 写入文件 64 | fs.writeFileSync(_outputPath, `var comments = ${JSON.stringify(result)};`, 'utf8'); 65 | } 66 | }); 67 | 68 | done(); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /workflow/basicTasks/reload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 刷新浏览器 17 | module.exports = (gulp, mix) => { 18 | 19 | gulp.task('reload', (done) => { 20 | mix.reload(); 21 | done(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /workflow/basicTasks/sass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 进行 Sass 编译以及雪碧图处理 17 | const argv = require('yargs').argv; 18 | const lazysprite = require('postcss-lazysprite'); 19 | const svgSprite = require('postcss-svg-sprite'); 20 | const autoprefixer = require('autoprefixer'); 21 | 22 | module.exports = (gulp, mix) => { 23 | const lazySpriteConfig = { 24 | cssSeparator: '_', 25 | imagePath: mix.config.paths.imagesSourcePath, 26 | stylesheetRelative: mix.config.paths.styleResultPath, 27 | stylesheetInput: '../project/', 28 | spritePath: mix.config.paths.imagesResultPath, 29 | smartUpdate: typeof mix.config.needsLazyspriteSmartUpdate !== 'undefined' ? mix.config.needsLazyspriteSmartUpdate : true, 30 | nameSpace: `${mix.config.prefix}_`, 31 | retinaInfix: '_', 32 | outputExtralCSS: true 33 | }; 34 | const svgSpriteConfig = { 35 | imagePath: mix.config.paths.imagesSourcePath, 36 | spriteOutput: mix.config.paths.imagesResultPath, 37 | styleOutput: mix.config.paths.styleResultPath, 38 | nameSpace: `${mix.config.prefix}_` 39 | }; 40 | const styleResultPath = mix.config.paths.styleResultPath; 41 | if (argv.debug) { 42 | lazySpriteConfig.logLevel = 'debug'; 43 | } 44 | 45 | const sassTaskName = 'sass'; 46 | const sassWithCacheTaskName = 'sassWithCache'; 47 | 48 | const sassOptionWithCache = () => ({since: gulp.lastRun(sassWithCacheTaskName)}); 49 | 50 | const sassHandle = (options) => { 51 | options = options || (() => ({})); 52 | return () => 53 | gulp.src('../project/**/*.scss', options()) 54 | .pipe(mix.plugins.sassInheritance({base: '../project/'})) 55 | .pipe(mix.plugins.if(Boolean(argv.debug), mix.plugins.debug({title: 'Sass Debug:'}))) 56 | .pipe(mix.plugins.if(mix.config.needsSourceMaps, mix.plugins.sourcemaps.init())) 57 | .pipe(mix.plugins.dartSass({ 58 | errLogToConsole: true, 59 | indentWidth: 4, 60 | precision: 6, 61 | outputStyle: 'expanded' 62 | }).on('error', mix.plugins.dartSass.logError)) 63 | .pipe(mix.plugins.postcss([lazysprite(lazySpriteConfig), svgSprite(svgSpriteConfig), autoprefixer({ 64 | browsers: ['defaults', 'last 5 versions', '> 5% in CN', 'not ie <= 8', 'iOS > 8'] 65 | })])) 66 | .pipe(mix.plugins.if(mix.config.needsSourceMaps, mix.plugins.sourcemaps.write('./maps'))) // Source Maps 的 Base 输出目录为 style 输出的目录 67 | .pipe(gulp.dest(styleResultPath)) 68 | }; 69 | 70 | gulp.task(sassWithCacheTaskName, sassHandle(sassOptionWithCache)); 71 | 72 | gulp.task(sassTaskName, sassHandle()); 73 | 74 | // 任务说明 75 | mix.addTaskDescription(sassTaskName, '进行 Sass 编译以及雪碧图处理(框架自带 Watch 机制监听 Sass 和图片变化后自行编译,不建议手工调用本方法)'); 76 | }; 77 | -------------------------------------------------------------------------------- /workflow/basicTasks/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // server 监视文件改动并重新载入 17 | module.exports = (gulp, mix) => { 18 | 19 | gulp.task('server', (done) => { 20 | 21 | const showLog = () => { 22 | if (mix.config.browserSync.browserSyncShowLog) { 23 | return 'info'; 24 | } 25 | return 'silent'; 26 | }; 27 | 28 | mix.browserSync.init({ 29 | server: { 30 | // 静态路径根目录 31 | baseDir: mix.config.paths.htmlResultPath, 32 | // 设置路由 33 | routes: mix.config.browserSync.browserSyncServerRoute 34 | }, 35 | logLevel: showLog(), 36 | logPrefix: mix.util.addColor(mix.timeFormat.getCurrentTime(), 'info'), 37 | startPath: mix.config.browserSync.browserSyncStartPath, 38 | port: mix.config.browserSync.browserSyncPort 39 | }); 40 | gulp.watch(mix.config.browserSync.browserSyncWatchPath).on('all', mix.reload); 41 | 42 | done(); 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /workflow/basicTasks/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 显示 QMUI Web 的版本号 17 | module.exports = (gulp, mix) => { 18 | 19 | const taskName = 'version'; 20 | 21 | gulp.task(taskName, (done) => { 22 | mix.util.log('当前项目运行的 QMUI Web 版本号: ' + mix.util.colors.green(mix.packageInfo.version)); 23 | done(); 24 | }); 25 | 26 | // 任务说明 27 | mix.addTaskDescription(taskName, '显示 QMUI Web 的版本信息'); 28 | }; 29 | -------------------------------------------------------------------------------- /workflow/calculateMargin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | /** 16 | * This file was adapted from chmontgomery's gulp-help. 17 | * The original file can be found at: https://github.com/chmontgomery/gulp-help/blob/master/lib/calculate-margin.js. 18 | * 19 | * @param {object} tasksObj - common.tasks 20 | * 21 | * returns: 22 | * margin - length of longest basicTasks / options name 23 | * hasOptions - true if any basicTasks has option(s) 24 | * 25 | * @returns {{margin: number, hasOptions: boolean}} 返回一个合适的缩进距离。 26 | */ 27 | 28 | module.exports = (tasksObj) => { 29 | let hasOptions = false; 30 | const margin = Object.keys(tasksObj).reduce((maxTaskMargin, taskName) => { 31 | let optionsMargin = 0; 32 | let opts; 33 | // if exists, iterate options list to calculate margin for options 34 | if (tasksObj[taskName] && tasksObj[taskName].options) { 35 | const help = tasksObj[taskName] || {options: {}}; 36 | opts = Object.keys(help.options).sort(); 37 | optionsMargin = opts.reduce((maxOptionMargin, opt) => { 38 | // if, at any time while iterating the tasks array, we also iterate an opts array, set hasOptions flag 39 | hasOptions = true; 40 | return maxOptionMargin > opt.length ? maxOptionMargin : opt.length; 41 | }, 0); 42 | } 43 | 44 | if (!tasksObj[taskName] || maxTaskMargin > taskName.length && maxTaskMargin > optionsMargin) { 45 | return maxTaskMargin; 46 | } else if (optionsMargin > taskName.length) { 47 | return optionsMargin; 48 | } 49 | return taskName.length; 50 | }, 0); 51 | return { 52 | margin: margin, 53 | hasOptions: hasOptions 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /workflow/initProject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 创建一个新项目 17 | const fs = require('fs'); 18 | const upperFirst = require('lodash/upperFirst'); 19 | const mkdirp = require('mkdirp'); 20 | const path = require('path'); 21 | const os = require('os'); 22 | 23 | module.exports = (gulp, mix) => { 24 | 25 | gulp.task('initProject', (done) => { 26 | /** 27 | * 创建一个新项目 28 | * 第一步:获取 Project 文件夹中的基本目录结构和公共通用组件并持有它们,但排除了主 scss 文件 demo.scss 29 | * 第二步:修改持有文件中的 qui_ 前缀为新项目的前缀,新前缀值从 qmui.config.js 中读取; 30 | * 第三步:修改持有文件内容注释中的日期为创建项目时的日期; 31 | * 第四步:修改持有文件内容注释中的作者为执行创建项目命令的人(名称从系统账户用户名中获取); 32 | * 第五步:把这些持有的文件复制到上一层目录; 33 | * 第六步:获取主 scss 文件 demo.scss ,并更新其中的 _qmui.scss 的引用路径(因为 demo.scss 被复制到上一层); 34 | * 第七步:重命名 demo.scss,新名称从 qmui.config.js 中读取; 35 | * 第八步:把 demo.scss 复制到上一层目录; 36 | * 第九步:按配置表创建图片目录; 37 | * 第十步:执行 Sass 编译任务,打开浏览器,并打开新复制的 demo.html; 38 | */ 39 | 40 | // 需要遍历的文件 41 | const sourceArr = ['project/**/*']; 42 | // 额外排除 demo.scss,后面单独重命名再拷贝 43 | sourceArr.push('!project/demo.scss'); 44 | sourceArr.push('!project/_var.scss'); 45 | 46 | // 获取当天的日期,并统一格式为 'yyyy-mm-dd',替换掉 demo 注释中的文件创建日期 47 | // gulp-replace 的正则引擎似乎对 $ 和 ^ 不支持,只能忽略开头和结尾的判断 48 | const dateRegex = /[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))/g; 49 | const currentDate = new Date(); 50 | const currentYear = currentDate.getFullYear(); 51 | const currentMonth = mix.timeFormat.checkDateFormat(currentDate.getMonth() + 1); 52 | const currentDay = mix.timeFormat.checkDateFormat(currentDate.getDate()); 53 | const formattingDate = `${currentYear}-${currentMonth}-${currentDay}`; 54 | const targetQMUIStylePath = `../${path.resolve('.').replace(/\\/g, '/').split('/').pop()}/qmui/_qmui`; 55 | const targetQMUICalculatePath = `../${path.resolve('.').replace(/\\/g, '/').split('/').pop()}/qmui/mixin/tool/_calculate`; 56 | const authorName = upperFirst(path.basename(os.homedir())); 57 | 58 | // 执行创建项目的任务 59 | gulp.src(sourceArr) 60 | .pipe(mix.plugins.replace('qui_', mix.config.prefix + '_')) 61 | .pipe(mix.plugins.replace(dateRegex, formattingDate)) 62 | .pipe(mix.plugins.replace(/@author .*([\r\n])/, '@author ' + authorName + '$1')) 63 | .pipe(gulp.dest('../project')); 64 | 65 | gulp.src(['project/demo.scss']) 66 | .pipe(mix.plugins.replace('../qmui/_qmui', targetQMUIStylePath)) 67 | .pipe(mix.plugins.replace('demo.scss', mix.config.resultCssFileName)) 68 | .pipe(mix.plugins.replace(dateRegex, formattingDate)) 69 | .pipe(mix.plugins.replace(/@author .*([\r\n])/, '@author ' + authorName + '$1')) 70 | .pipe(mix.plugins.rename(mix.config.resultCssFileName)) 71 | .pipe(gulp.dest('../project')); 72 | 73 | gulp.src(['project/_var.scss']) 74 | .pipe(mix.plugins.replace('../qmui/mixin/tool/_calculate', targetQMUICalculatePath)) 75 | .pipe(mix.plugins.replace(dateRegex, formattingDate)) 76 | .pipe(mix.plugins.replace(/@author .*([\r\n])/, '@author ' + authorName + '$1')) 77 | .pipe(gulp.dest('../project')); 78 | 79 | // 创建公共图片目录 80 | if (!fs.existsSync(mix.config.paths.imagesSourcePath)) { 81 | mkdirp(mix.config.paths.imagesSourcePath); 82 | } 83 | 84 | // 创建独立图片目录 85 | const independentImagesSourcePath = mix.config.paths.imagesSourcePath + mix.config.paths.independentImagesDirectory; 86 | if (!fs.existsSync(independentImagesSourcePath)) { 87 | mkdirp(independentImagesSourcePath); 88 | } 89 | 90 | mix.util.log('Create Project', '项目创建完毕,接下来会按配置执行一次 Default Task'); 91 | 92 | done(); 93 | }); 94 | 95 | // 执行创建新项目任务 96 | const taskName = 'init'; 97 | 98 | gulp.task(taskName, gulp.series('initProject', 'default')); 99 | 100 | // 任务说明 101 | mix.addTaskDescription(taskName, '创建一个新项目'); 102 | }; 103 | -------------------------------------------------------------------------------- /workflow/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // Gulp 服务入口 17 | const argv = require('yargs').argv; 18 | const spawn = require('child_process').spawn; 19 | const os = require('os'); 20 | 21 | module.exports = (gulp, mix) => { 22 | 23 | // 判断 browserSync 的值是否正确 24 | if (mix.config.browserSync.browserSyncMod !== 'server' && mix.config.browserSync.browserSyncMod !== 'proxy' && mix.config.browserSync.browserSyncMod !== 'close') { 25 | gulp.task('main', (done) => { 26 | mix.util.error('Config', `Config 中的 browserSyncMod 仅支持 ${mix.plugins.util.colors.yellow('server')}, ${mix.plugins.util.colors.yellow('proxy')}, ${mix.plugins.util.colors.yellow('close')} 三个值`); 27 | done(); 28 | }); 29 | } else { 30 | // 常规启动任务 31 | const mainTasks = ['include', 'sass', 'watch']; 32 | 33 | // 根据 broserSync 的类型加入对应的任务 34 | if (mix.config.browserSync.browserSyncMod === 'server' || mix.config.browserSync.browserSyncMod === 'proxy') { 35 | mainTasks.push(mix.config.browserSync.browserSyncMod); 36 | } 37 | 38 | // 加入用户自定义任务 39 | if (mix.config.customTasks) { 40 | Object.keys(mix.config.customTasks).forEach((customTaskName) => { 41 | mainTasks.push(customTaskName); 42 | }); 43 | } 44 | 45 | gulp.task('main', gulp.series(mainTasks)); 46 | } 47 | 48 | const taskName = 'default'; 49 | 50 | if (os.platform() === 'linux' || os.platform() === 'darwin') { 51 | 52 | gulp.task('start', (done) => { 53 | if (argv.debug) { 54 | mix.util.log('Debug: ', 'QMUI 进入 Debug 模式'); 55 | } 56 | 57 | let mainTaskProcess; // 记录当前 gulp 运行时的进程 58 | 59 | const restart = () => { 60 | if (mainTaskProcess) { 61 | mainTaskProcess.kill(); 62 | } 63 | 64 | const mainTask = ['main']; 65 | if (typeof argv.color !== 'undefined' && !argv.color) { 66 | mainTask.push('--no-color'); 67 | } 68 | mainTaskProcess = spawn('gulp', mainTask, {stdio: 'inherit'}); 69 | }; 70 | 71 | gulp.watch('package.json').on('all', () => { 72 | mix.util.log(''); 73 | mix.util.warn('Update', '检测到 QMUI Web 的 npm 包,为了避免出现错误,建议你停止目前的 gulp,请使用 npm install 命令更新后再启动 gulp'); 74 | mix.util.beep(3); 75 | }); 76 | 77 | gulp.watch(['gulpfile.js', 'workflow', 'workflow/**/*']).on('all', () => { 78 | mix.util.log(''); 79 | if (argv.debug) { 80 | mix.util.warn('Debug', '目前为 Debug 模式,检测到工作流源码有被更新,将自动重启 gulp'); 81 | mix.util.beep(3); 82 | restart(); 83 | } else { 84 | mix.util.warn('Update', '检测到工作流源码有被更新,建议你停止目前的 gulp 任务,再重新启动 gulp,以载入最新的代码。如果 npm 包也需要更新,请先更新 npm 包再重启 gulp'); 85 | mix.util.beep(3); 86 | 87 | } 88 | }); 89 | 90 | // 获取第一次进入时 gulp 的进程 91 | const mainTask = ['main']; 92 | if (argv.debug) { 93 | mainTask.push('--debug'); 94 | } 95 | if (typeof argv.color !== 'undefined' && !argv.color) { 96 | mainTask.push('--no-color'); 97 | } 98 | mainTaskProcess = spawn('gulp', mainTask, {stdio: 'inherit'}); 99 | 100 | done(); 101 | }); 102 | 103 | // 默认任务 104 | gulp.task(taskName, gulp.parallel('start')); 105 | } else { 106 | gulp.task(taskName, gulp.parallel('main')); 107 | } 108 | 109 | // 任务说明 110 | mix.addTaskDescription(taskName, '默认任务,自动执行一次 include 和 sass 任务,并调用 watch 任务', { 111 | 'debug': 'debug 模式下 gulpfile.js 有变动时会自动重启 default 任务' 112 | }); 113 | }; 114 | -------------------------------------------------------------------------------- /workflow/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making QMUI Web available. 3 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance 5 | * with the License. You may obtain a copy of the License at 6 | * 7 | * http://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 16 | // 文件监控 17 | const path = require('path'); 18 | const pngquant = require('imagemin-pngquant'); 19 | const md5 = require('js-md5'); 20 | 21 | // 逻辑变量 22 | let justAddedImage = [], 23 | justBeforeAddedImage = []; // 记录压缩的图片 24 | 25 | module.exports = (gulp, mix) => { 26 | 27 | const taskName = 'watch'; 28 | 29 | gulp.task(taskName, (done) => { 30 | 31 | mix.util.log('Watch', 'QMUI 进入自动监听'); 32 | 33 | // 图片管理(图片文件夹操作同步以及图片文件自动压缩) 34 | 35 | // 公共方法 36 | const imageMinOnSameDir = (dir) => { 37 | gulp.src(dir) 38 | .pipe(mix.plugins.plumber({ 39 | errorHandler: (error) => { 40 | mix.util.error('Min Image', error); 41 | mix.util.beep(); 42 | } 43 | })) 44 | .pipe(mix.plugins.imagemin({ 45 | progressive: true, 46 | svgoPlugins: [{removeViewBox: false}], 47 | use: [pngquant()] 48 | })) 49 | .pipe(gulp.dest(path.dirname(dir))); 50 | }; 51 | 52 | // 独立图片部分 53 | 54 | // 自动同步独立图片文件夹的操作 55 | const independentImagesSourcePath = mix.config.paths.imagesSourcePath + mix.config.paths.independentImagesDirectory; 56 | const independentImagesResultPath = mix.config.paths.imagesResultPath + mix.config.paths.independentImagesDirectory; 57 | let shouldOutputEmptyLineForSyncImage; 58 | 59 | // 如果有需要,则在执行同步图片任务之前输出一个空行 60 | const outputEmptyForSyncImageIfNeed = () => { 61 | if (shouldOutputEmptyLineForSyncImage) { 62 | mix.util.log(''); 63 | shouldOutputEmptyLineForSyncImage = false; 64 | } 65 | }; 66 | 67 | const independentImages = (independentDone) => { 68 | shouldOutputEmptyLineForSyncImage = true; 69 | mix.plugins.fileSync(independentImagesSourcePath, independentImagesResultPath, { 70 | ignore: ['.DS_Store', '.svn', '.git'], 71 | beforeAddFileCallback (_fullPathSrc) { 72 | const absoluteMinImageFilePath = path.resolve(_fullPathSrc); 73 | const absoluteMinImageFilePathMd5 = md5(absoluteMinImageFilePath); 74 | 75 | if (!justBeforeAddedImage.includes(absoluteMinImageFilePathMd5)) { 76 | // 这里为了避免发生“增加图片到 public/images 或修改 public/images 原有的图片,触发压缩图片,因此图片又被修改,再次触发压缩图片”的情况发生, 77 | // 做了一个判断,压缩一张图片时会标记下来,当再次发生图片改变时会判断这张图片是否为刚刚压缩过的图片,如果是则不执行该次压缩图片的逻辑 78 | // 如果不是,则说明准备处理另一张图片了,这时清空标记,进入下一张图片的处理 79 | justBeforeAddedImage.push(absoluteMinImageFilePathMd5); 80 | outputEmptyForSyncImageIfNeed(); 81 | mix.util.log('Min Image', `对 ${absoluteMinImageFilePath} 进行图片压缩`); 82 | imageMinOnSameDir(absoluteMinImageFilePath); 83 | } 84 | }, 85 | beforeUpdateFileCallback (_fullPathSrc) { 86 | const absoluteMinImageFilePath = path.resolve(_fullPathSrc); 87 | const absoluteMinImageFilePathMd5 = md5(absoluteMinImageFilePath); 88 | 89 | if (!justBeforeAddedImage.includes(absoluteMinImageFilePathMd5)) { 90 | justBeforeAddedImage.push(absoluteMinImageFilePathMd5); 91 | outputEmptyForSyncImageIfNeed(); 92 | mix.util.log('Min Image', `对 ${absoluteMinImageFilePath} 进行图片压缩`); 93 | imageMinOnSameDir(absoluteMinImageFilePath); 94 | } else { 95 | justBeforeAddedImage = justBeforeAddedImage.filter((item) => item !== absoluteMinImageFilePathMd5); 96 | } 97 | }, 98 | addFileCallback (_fullPathSrc, _fullPathDist) { 99 | const absoluteMinImageFilePath = path.resolve(_fullPathDist); 100 | const absoluteMinImageFilePathMd5 = md5(absoluteMinImageFilePath); 101 | 102 | if (!justAddedImage.includes(absoluteMinImageFilePathMd5)) { 103 | justAddedImage.push(absoluteMinImageFilePathMd5); 104 | outputEmptyForSyncImageIfNeed(); 105 | mix.util.log('Sync Image', `同步增加文件到 ${absoluteMinImageFilePath}`); 106 | } 107 | }, 108 | deleteFileCallback (_fullPathSrc, _fullPathDist) { 109 | outputEmptyForSyncImageIfNeed(); 110 | mix.util.log('Sync Image', `同步删除文件 ${path.resolve(_fullPathDist)}`); 111 | }, 112 | updateFileCallback (_fullPathSrc, _fullPathDist) { 113 | const absoluteMinImageFilePath = path.resolve(_fullPathDist); 114 | const absoluteMinImageFilePathMd5 = md5(absoluteMinImageFilePath); 115 | 116 | if (!justAddedImage.includes(absoluteMinImageFilePathMd5)) { 117 | justAddedImage.push(absoluteMinImageFilePathMd5); 118 | outputEmptyForSyncImageIfNeed(); 119 | mix.util.log('Sync Image', `同步更新文件到 ${absoluteMinImageFilePath}`); 120 | } else { 121 | justAddedImage = justAddedImage.filter((item) => item !== absoluteMinImageFilePathMd5); 122 | } 123 | } 124 | }); 125 | 126 | independentDone(); 127 | }; 128 | 129 | if (mix.config.needsImagesMinAndSync) { 130 | // 压缩独立图片并同步独立图片到 public 目录 131 | gulp.watch([independentImagesSourcePath, `${independentImagesSourcePath}/**/*`], gulp.series(independentImages)); 132 | } 133 | 134 | // 雪碧图与样式处理 135 | // 监控雪碧图原图和样式,如果有改动,会触发样式编译以及雪碧图生成 136 | 137 | // 普通雪碧图与样式监听 138 | const styleWatchFiles = ['../project/**/*.scss']; 139 | const styleWatchTasks = ['sassWithCache']; 140 | if (mix.config.browserSync.browserSyncMod === 'server' || mix.config.browserSync.browserSyncMod === 'proxy') { 141 | styleWatchTasks.push('reload'); 142 | } 143 | const styleWatch = gulp.watch(styleWatchFiles, gulp.series(styleWatchTasks)); 144 | styleWatch.on('all', () => { 145 | mix.util.log(''); 146 | mix.util.log('Sass', '进行样式编译'); 147 | }); 148 | 149 | const imageWatchFiles = [`${mix.config.paths.imagesSourcePath}/*/*.*`, `!${independentImagesSourcePath}`, `!${independentImagesSourcePath}**/*`]; 150 | const imageSpriteTasks = ['sass']; 151 | if (mix.config.browserSync.browserSyncMod === 'server' || mix.config.browserSync.browserSyncMod === 'proxy') { 152 | imageSpriteTasks.push('reload'); 153 | } 154 | const imageSpriteWatch = gulp.watch(imageWatchFiles, gulp.series(imageSpriteTasks)); 155 | imageSpriteWatch.on('all', () => { 156 | mix.util.log(''); 157 | mix.util.log('Sass', '进行样式编译'); 158 | }); 159 | 160 | // 压缩雪碧图 161 | if (mix.config.needsImagesMinAndSync) { 162 | const minImageWatcher = gulp.watch(mix.config.paths.imagesResultPath + '/*.*'); 163 | minImageWatcher.on('all', (event, filePath) => { 164 | const minImageFile = filePath; 165 | const minImageFilePathMd5 = md5(minImageFile); 166 | // 这里为了避免发生“增加图片到 public/images 或修改 public/images 原有的图片,触发压缩图片,因此图片又被修改,再次触发压缩图片”的情况发生, 167 | // 做了一个判断,压缩一张图片时会标记下来,当再次发生图片改变时会判断这张图片是否为刚刚压缩过的图片,如果是则不执行该次压缩图片的逻辑 168 | // 如果不是,则说明准备处理另一张图片了,这时清空标记,进入下一张图片的处理 169 | if (event !== 'unlink' && !justAddedImage.includes(minImageFilePathMd5)) { 170 | 171 | justAddedImage.push(minImageFilePathMd5); 172 | 173 | mix.util.log('Min Image', `对 ${minImageFile} 进行图片压缩`); 174 | imageMinOnSameDir(minImageFile); 175 | 176 | } else if (justAddedImage.includes(minImageFilePathMd5)) { 177 | justAddedImage = justAddedImage.filter((item) => item !== minImageFilePathMd5); 178 | } 179 | }); 180 | } 181 | 182 | // 模板自动 include 183 | if (mix.config.template.openIncludeFunction) { 184 | const includeWatcher = gulp.watch(mix.config.paths.htmlSourcePath, gulp.series('include')); 185 | includeWatcher.on('all', (event, filePath) => { 186 | mix.util.log(''); 187 | mix.util.log('Include', 'Template ' + filePath + ' was ' + event); 188 | }); 189 | } 190 | 191 | done(); 192 | }); 193 | 194 | // 任务说明 195 | mix.addTaskDescription(taskName, '文件监控,自动执行基本的工作流,包括 Sass 自动编译,雪碧图处理,模板 include 自动编译,图片文件夹操作同步以及图片文件自动压缩'); 196 | }; 197 | --------------------------------------------------------------------------------