├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs └── navigation-bar.gif ├── gulpfile.js ├── package.json ├── src ├── index.js ├── index.json ├── index.wxml └── index.wxss ├── test └── utils.js └── tools ├── build.js ├── checkcomponents.js ├── config.js ├── demo ├── app.js ├── app.json ├── app.wxss ├── package.json ├── pages │ ├── demo1 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo10 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo2 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo3 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo4 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo5 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo6 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo7 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo8 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── demo9 │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss └── project.config.json ├── test └── helper.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["module-resolver", { 4 | "root": ["./src"], 5 | "alias": {} 6 | }] 7 | ], 8 | "presets": [ 9 | ["env", {"loose": true, "modules": "commonjs"}] 10 | ] 11 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb-base', 4 | 'plugin:promise/recommended' 5 | ], 6 | 'parserOptions': { 7 | 'ecmaVersion': 9, 8 | 'ecmaFeatures': { 9 | 'jsx': false 10 | }, 11 | 'sourceType': 'module' 12 | }, 13 | 'env': { 14 | 'es6': true, 15 | 'node': true, 16 | 'jest': true 17 | }, 18 | 'plugins': [ 19 | 'import', 20 | 'node', 21 | 'promise' 22 | ], 23 | 'rules': { 24 | 'arrow-parens': 'off', 25 | 'comma-dangle': [ 26 | 'error', 27 | 'only-multiline' 28 | ], 29 | 'complexity': ['error', 10], 30 | 'func-names': 'off', 31 | 'global-require': 'off', 32 | 'handle-callback-err': [ 33 | 'error', 34 | '^(err|error)$' 35 | ], 36 | 'import/no-unresolved': [ 37 | 'error', 38 | { 39 | 'caseSensitive': true, 40 | 'commonjs': true, 41 | 'ignore': ['^[^.]'] 42 | } 43 | ], 44 | 'import/prefer-default-export': 'off', 45 | 'linebreak-style': 'off', 46 | 'no-catch-shadow': 'error', 47 | 'no-continue': 'off', 48 | 'no-div-regex': 'warn', 49 | 'no-else-return': 'off', 50 | 'no-param-reassign': 'off', 51 | 'no-plusplus': 'off', 52 | 'no-shadow': 'off', 53 | 'no-multi-assign': 'off', 54 | 'no-underscore-dangle': 'off', 55 | 'node/no-deprecated-api': 'error', 56 | 'node/process-exit-as-throw': 'error', 57 | 'object-curly-spacing': [ 58 | 'error', 59 | 'never' 60 | ], 61 | 'operator-linebreak': [ 62 | 'error', 63 | 'after', 64 | { 65 | 'overrides': { 66 | ':': 'before', 67 | '?': 'before' 68 | } 69 | } 70 | ], 71 | 'prefer-arrow-callback': 'off', 72 | 'prefer-destructuring': 'off', 73 | 'prefer-template': 'off', 74 | 'quote-props': [ 75 | 1, 76 | 'as-needed', 77 | { 78 | 'unnecessary': true 79 | } 80 | ], 81 | 'semi': [ 82 | 'error', 83 | 'never' 84 | ] 85 | }, 86 | 'globals': { 87 | 'window': true, 88 | 'document': true, 89 | 'App': true, 90 | 'Page': true, 91 | 'Component': true, 92 | 'Behavior': true, 93 | 'wx': true, 94 | 'getApp': true, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | package-lock.json 4 | 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | miniprogram_dist 12 | miniprogram_dev 13 | node_modules 14 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | package-lock.json 4 | 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | test 12 | tools 13 | docs 14 | miniprogram_dev 15 | node_modules 16 | coverage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 wechat-miniprogram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 小程序组件 navigation-bar 2 | 3 | 小程序自定义导航栏适配 完美解决内容上下不居中问题 4 | 5 | ## Navigation 6 | 7 | Navigation 是小程序的顶部导航组件,当页面配置 navigationStyle 设置为 custom 的时候可以使用此组件替代原生导航栏 8 | 9 | ## 引入组件 10 | 11 | ### 方式一 (建议) 12 | 13 | ```bash 14 | npm install miniprograms-navigation-bar --save 15 | ``` 16 | 17 | - 点击开发者工具中的菜单栏:工具 --> 构建 npm 18 | ![构建 npm](https://user-gold-cdn.xitu.io/2019/8/19/16ca8a4814418214?w=207&h=441&f=png&s=31449) 19 | - 勾选“使用 npm 模块”选项 20 | ![使用 npm 模块](https://user-gold-cdn.xitu.io/2019/8/19/16ca8a4cf34f148e?w=246&h=184&f=png&s=8131) 21 | 在 page.json 中引入组件 22 | 23 | ```bash 24 | { 25 | "usingComponents": { 26 | "navBar": "miniprograms-navigation-bar" 27 | } 28 | } 29 | ``` 30 | 31 | > 备注:如发现一些编译问题,请重启开发工具试试 32 | 33 | ### 方式二 34 | 35 | 复制代码过去,在 page.json 中引入组件,注意引入的路径 36 | 37 | ```bash 38 | { 39 | "usingComponents": { 40 | "navBar": "/components/index" 41 | } 42 | } 43 | ``` 44 | 45 | ## 示例代码 46 | 47 | ```bash 48 | 49 | 50 | ``` 51 | 52 | ## 属性列表 53 | 54 | | 属性 | 类型 | 默认值 | 必填 | 说明 | 55 | | ------------------ | ------------ | ---------- | ---- | ----------------------------------------------------------------------------------------- | 56 | | ext-class | string | | 否 | 添加在组件内部结构的 class,可用于修改组件内部的样式 | 57 | | title | string | | 否 | 导航标题,如果不提供,则名为 center 的 slot 有效 | 58 | | background | string | #ffffff | 否 | 导航背景色 | 59 | | backgroundColorTop | string | background | 否 | 导航下拉下方背景色,详细参考下方 | 60 | | color | string | #000000 | 否 | 导航字体颜色 | 61 | | iconTheme | string | black | 否 | 主题图标和字体颜色,当背景色为深色时,可以设置'white' | 62 | | back | boolean | false | 否 | 是否显示返回按钮,默认点击按钮会执行 navigateBack,如果为 false,则名为 left 的 slot 有效 | 63 | | home | boolean | false | 否 | 是否显示 home 按钮,执行方法自定义,或者看例子 | 64 | | searchBar | boolean | false | 否 | 是否显示搜索框,默认点击按钮会执行 bindsearch,如果为 false,则名为 center 的 slot 有效 | 65 | | searchText | string | 点我搜索 | 否 | 搜索框文字 | 66 | | bindhome | eventhandler | | 否 | 在 home 为 true 时,点击 home 按钮触发此事件 | 67 | | bindback | venthandler | | 否 | 在 back 为 true 时,点击 back 按钮触发此事件,detail 包含 delta | 68 | | bindsearch | eventhandler | | 否 | 在 searchBar 为 true 时,点击 search 按钮触发此事件 | 69 | 70 | 注:backgroundColorTop[见 issue 问题](https://github.com/lingxiaoyi/Taro-navigation-bar/issues/15) 71 | 72 | ## Slot 73 | 74 | | 名称 | 描述 | 75 | | ------ | ------------------------------------------------------------ | 76 | | left | 左侧 slot,在 back 按钮位置显示,当 back 为 false 的时候有效 | 77 | | center | 标题 slot,在标题位置显示,当 title 为空的时候有效 | 78 | | right | 在导航的右侧显示 | 79 | 80 | ## 注意 81 | 82 | - iconTheme 设置为 white 的时候,一定要记得自己去 json 文件设置"navigationBarTextStyle": "white" 83 | - 跳转搜索页面,在 Android 机子会出现文字被键盘弹起顶出 input 框,解决方案页面设置一个死的高度不要高于 windowHeight - navheight 例子中是设置 500px 84 | - input 框文字抖动问题我是借鉴别人写的,可以最大限度减小文字抖动的大小,提升用户体验 85 | - title searchBar slot="right" 如果全部有内容,是这样的先后显示顺序. 86 | - 默认配置满足不了功能的,请使用 slot 功能,见例子 1 6 7 87 | - 由于本人精力有限,只测试了常规的 20 多款手机.如有哪种机型出现问题,请备注机型和小程序版本库.本人会以最快方式解决问题. 88 | - 有什么 bug 和建议,还有功能上的问题请提 pr 89 | 90 | ## 后续 91 | 92 | - 其他功能,规划中,或者留言联系方式微信 zhijunxh 93 | - 还需要其他样子的例子请留言,如果功能比较重要和主流的话,我会考虑第一时间添加 94 | 95 | ## 备注 96 | 97 | - 渐变和动态修改背景色请参考例子 10,ios 机子会出现滚动渐变颜色加载出来不能消失的问题,暂时没想到解决办法,这是微信浏览器渲染的问题,社区里面相关问题[地址](https://developers.weixin.qq.com/community/develop/doc/0000acae7649d80541b896ca957000) 98 | - getMenuButtonBoundingClientRect 胶囊按钮信息获取不到或者此方法报错问题已修复 [获取菜单报错,需要捕捉异常和兼容版本](https://github.com/lingxiaoyi/navigation-bar/issues/14)[导航栏渲染不出来](https://github.com/lingxiaoyi/navigation-bar/issues/13) 99 | 100 | ## 测试信息 101 | 102 | | 手机型号 | 胶囊位置信息 | statusBarHeight | 测试情况 | 103 | | ---------------------- | ------------------- | :-------------: | -------- | 104 | | iPhoneX | 80 32 281 369 48 88 | 44 | 通过 | 105 | | iPhone8 plus | 56 32 320 408 24 88 | 20 | 通过 | 106 | | iphone7 | 56 32 281 368 24 87 | 20 | 通过 | 107 | | iPhone6 plus | 56 32 320 408 24 88 | 20 | 通过 | 108 | | iPhone6 | 56 32 281 368 24 87 | 20 | 通过 | 109 | | HUAWEI SLA-AL00 | 64 32 254 350 32 96 | 24 | 通过 | 110 | | HUAWEI VTR-AL00 | 64 32 254 350 32 96 | 24 | 通过 | 111 | | HUAWEI EVA-AL00 | 64 32 254 350 32 96 | 24 | 通过 | 112 | | HUAWEI EML-AL00 | 68 32 254 350 36 96 | 29 | 通过 | 113 | | HUAWEI VOG-AL00 | 65 32 254 350 33 96 | 25 | 通过 | 114 | | HUAWEI ATU-TL10 | 64 32 254 350 32 96 | 24 | 通过 | 115 | | HUAWEI SMARTISAN OS105 | 64 32 326 422 32 96 | 24 | 通过 | 116 | | XIAOMI MI6 | 59 28 265 352 31 87 | 23 | 通过 | 117 | | XIAOMI MI4LTE | 60 32 254 350 28 96 | 20 | 通过 | 118 | | XIAOMI MIX3 | 74 32 287 383 42 96 | 35 | 通过 | 119 | | REDMI NOTE3 | 64 32 254 350 32 96 | 24 | 通过 | 120 | | REDMI NOTE4 | 64 32 254 350 32 96 | 24 | 通过 | 121 | | REDMI NOTE3 | 55 28 255 351 27 96 | 20 | 通过 | 122 | | REDMI 5plus | 67 32 287 383 35 96 | 28 | 通过 | 123 | | MEIZU M571C | 65 32 254 350 33 96 | 25 | 通过 | 124 | | MEIZU M6 NOTE | 62 32 254 350 30 96 | 22 | 通过 | 125 | | MEIZU MX4 PRO | 62 32 278 374 30 96 | 22 | 通过 | 126 | | OPPO A33 | 65 32 254 350 33 96 | 26 | 通过 | 127 | | OPPO R11 | 58 32 254 350 26 96 | 18 | 通过 | 128 | | VIVO Y55 | 64 32 254 350 32 96 | 24 | 通过 | 129 | | HONOR BLN-AL20 | 64 32 254 350 32 96 | 24 | 通过 | 130 | | HONOR NEM-AL10 | 59 28 265 352 31 87 | 24 | 通过 | 131 | | HONOR BND-AL10 | 64 32 254 350 32 96 | 24 | 通过 | 132 | | HONOR duk-al20 | 64 32 254 350 32 96 | 24 | 通过 | 133 | | SAMSUNG SM-G9550 | 64 32 305 401 32 96 | 24 | 通过 | 134 | | 360 1801-A01 | 64 32 254 350 32 96 | 24 | 通过 | 135 | 136 | ~ 137 | 创作不易,如果对你有帮助,请给个星星 star✨✨ 谢谢 138 | ~ 139 | -------------------------------------------------------------------------------- /docs/navigation-bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/docs/navigation-bar.gif -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const clean = require('gulp-clean'); 3 | 4 | const config = require('./tools/config'); 5 | const BuildTask = require('./tools/build'); 6 | const id = require('./package.json').name || 'miniprogram-custom-component'; 7 | 8 | // build task instance 9 | new BuildTask(id, config.entry); 10 | 11 | // clean the generated folders and files 12 | gulp.task('clean', gulp.series(() => { 13 | return gulp.src(config.distPath, { read: false, allowEmpty: true }) 14 | .pipe(clean()) 15 | }, done => { 16 | if (config.isDev) { 17 | return gulp.src(config.demoDist, { read: false, allowEmpty: true }) 18 | .pipe(clean()); 19 | } 20 | 21 | done(); 22 | })); 23 | // watch files and build 24 | gulp.task('watch', gulp.series(`${id}-watch`)); 25 | // build for develop 26 | gulp.task('dev', gulp.series(`${id}-dev`)); 27 | // build for publish 28 | gulp.task('default', gulp.series(`${id}-default`)); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprograms-navigation-bar", 3 | "version": "2.0.1", 4 | "description": "miniprogram custom component 可自定义返回事件 返回home 搜索框 自定义左中右", 5 | "main": "miniprogram_dist/index.js", 6 | "scripts": { 7 | "dev": "gulp dev --develop", 8 | "watch": "gulp watch --develop --watch", 9 | "build": "gulp", 10 | "dist": "npm run build", 11 | "clean-dev": "gulp clean --develop", 12 | "clean": "gulp clean", 13 | "test": "jest ./test/* --silent --bail", 14 | "coverage": "jest ./test/* --coverage --bail", 15 | "lint": "eslint \"src/**/*.js\"", 16 | "lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\"" 17 | }, 18 | "keywords": [ 19 | "weapp", 20 | "miniprogram", 21 | "navbar", 22 | "navigation-bar", 23 | "navigationbar", 24 | "小程序" 25 | ], 26 | "miniprogram": "miniprogram_dist", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/lingxiaoyi/miniprograms-navigation-bar" 30 | }, 31 | "author": "lingxiaoyi", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "babel-core": "^6.26.3", 35 | "babel-loader": "^7.1.5", 36 | "babel-plugin-module-resolver": "^3.1.1", 37 | "babel-preset-env": "^1.7.0", 38 | "colors": "^1.3.1", 39 | "eslint": "^5.3.0", 40 | "eslint-loader": "^2.1.0", 41 | "gulp": "^4.0.0", 42 | "gulp-clean": "^0.4.0", 43 | "gulp-if": "^2.0.2", 44 | "gulp-install": "^1.1.0", 45 | "gulp-less": "^3.5.0", 46 | "gulp-rename": "^1.4.0", 47 | "gulp-sourcemaps": "^2.6.4", 48 | "j-component": "git+https://github.com/JuneAndGreen/j-component.git", 49 | "jest": "^23.5.0", 50 | "through2": "^2.0.3", 51 | "webpack": "^4.16.5", 52 | "webpack-node-externals": "^1.7.2", 53 | "eslint-config-airbnb-base": "13.1.0", 54 | "eslint-plugin-import": "^2.14.0", 55 | "eslint-plugin-node": "^7.0.1", 56 | "eslint-plugin-promise": "^3.8.0" 57 | }, 58 | "dependencies": {}, 59 | "jest": { 60 | "testEnvironment": "jsdom", 61 | "testURL": "https://jest.test", 62 | "collectCoverageFrom": [ 63 | "src/**/*.js" 64 | ], 65 | "moduleDirectories": [ 66 | "node_modules", 67 | "src" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true, 4 | addGlobalClass: true 5 | }, 6 | properties: { 7 | extClass: { 8 | type: String, 9 | value: '' 10 | }, 11 | background: { 12 | type: String, 13 | value: 'rgba(255, 255, 255, 1)', 14 | observer: '_showChange' 15 | }, 16 | backgroundColorTop: { 17 | type: String, 18 | value: 'rgba(255, 255, 255, 1)', 19 | observer: '_showChangeBackgroundColorTop' 20 | }, 21 | color: { 22 | type: String, 23 | value: 'rgba(0, 0, 0, 1)' 24 | }, 25 | title: { 26 | type: String, 27 | value: '' 28 | }, 29 | searchText: { 30 | type: String, 31 | value: '点我搜索' 32 | }, 33 | searchBar: { 34 | type: Boolean, 35 | value: false 36 | }, 37 | back: { 38 | type: Boolean, 39 | value: false 40 | }, 41 | home: { 42 | type: Boolean, 43 | value: false 44 | }, 45 | iconTheme: { 46 | type: String, 47 | value: 'black' 48 | }, 49 | /* animated: { 50 | type: Boolean, 51 | value: true 52 | }, 53 | show: { 54 | type: Boolean, 55 | value: true, 56 | observer: '_showChange' 57 | }, */ 58 | delta: { 59 | type: Number, 60 | value: 1 61 | } 62 | }, 63 | created() { 64 | this.getSystemInfo() 65 | }, 66 | attached() { 67 | this.setStyle() // 设置样式 68 | }, 69 | data: {}, 70 | pageLifetimes: { 71 | show() { 72 | if (getApp().globalSystemInfo.ios) { 73 | this.getSystemInfo() 74 | this.setStyle() // 设置样式1 75 | } 76 | }, 77 | hide() {} 78 | }, 79 | methods: { 80 | setStyle(life) { 81 | const { 82 | statusBarHeight, 83 | navBarHeight, 84 | capsulePosition, 85 | navBarExtendHeight, 86 | ios, 87 | windowWidth 88 | } = getApp().globalSystemInfo 89 | const {back, home, title} = this.data 90 | const rightDistance = windowWidth - capsulePosition.right // 胶囊按钮右侧到屏幕右侧的边距 91 | const leftWidth = windowWidth - capsulePosition.left // 胶囊按钮左侧到屏幕右侧的边距 92 | 93 | const navigationbarinnerStyle = [ 94 | `color: ${this.data.color}`, 95 | `background: ${this.data.background}`, 96 | `height:${navBarHeight + navBarExtendHeight}px`, 97 | `padding-top:${statusBarHeight}px`, 98 | `padding-right:${leftWidth}px`, 99 | `padding-bottom:${navBarExtendHeight}px` 100 | ].join(';') 101 | let navBarLeft = [] 102 | if ((back && !home) || (!back && home)) { 103 | navBarLeft = [`width:${capsulePosition.width}px`, `height:${capsulePosition.height}px`].join(';') 104 | } else if ((back && home) || title) { 105 | navBarLeft = [ 106 | `width:${capsulePosition.width}px`, 107 | `height:${capsulePosition.height}px`, 108 | `margin-left:${rightDistance}px` 109 | ].join(';') 110 | } else { 111 | navBarLeft = ['width:auto', 'margin-left:0px'].join(';') 112 | } 113 | if (life === 'created') { 114 | this.data = { 115 | navigationbarinnerStyle, 116 | navBarLeft, 117 | navBarHeight, 118 | capsulePosition, 119 | navBarExtendHeight, 120 | ios 121 | } 122 | } else { 123 | this.setData({ 124 | navigationbarinnerStyle, 125 | navBarLeft, 126 | navBarHeight, 127 | capsulePosition, 128 | navBarExtendHeight, 129 | ios 130 | }) 131 | } 132 | }, 133 | _showChange() { 134 | this.setStyle() 135 | }, 136 | // 返回事件 137 | back() { 138 | this.triggerEvent('back', {delta: this.data.delta}) 139 | }, 140 | home() { 141 | this.triggerEvent('home', {}) 142 | }, 143 | search() { 144 | this.triggerEvent('search', {}) 145 | }, 146 | checkRect(rect) { // 胶囊信息4种任一属性为0返回true 147 | return !rect.width || !rect.top || !rect.left || !rect.height 148 | }, 149 | getMenuButtonBoundingClientRect(systemInfo) { 150 | const ios = !!(systemInfo.system.toLowerCase().search('ios') + 1) 151 | let rect 152 | try { 153 | rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null 154 | if (rect === null) { 155 | throw new Error('getMenuButtonBoundingClientRect error') 156 | } 157 | // 取值为0的情况 有可能width不为0 top为0的情况 158 | if (this.checkRect(rect)) { 159 | throw new Error('getMenuButtonBoundingClientRect error') 160 | } 161 | } catch (error) { 162 | let gap = '' // 胶囊按钮上下间距 使导航内容居中 163 | let width = 96 // 胶囊的宽度 164 | if (systemInfo.platform === 'android') { 165 | gap = 8 166 | width = 96 167 | } else if (systemInfo.platform === 'devtools') { 168 | if (ios) { 169 | gap = 5.5 // 开发工具中ios手机 170 | } else { 171 | gap = 7.5 // 开发工具中android和其他手机 172 | } 173 | } else { 174 | gap = 4 175 | width = 88 176 | } 177 | if (!systemInfo.statusBarHeight) { 178 | // 开启wifi的情况下修复statusBarHeight值获取不到 179 | systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20 180 | } 181 | rect = { 182 | // 获取不到胶囊信息就自定义重置一个 183 | bottom: systemInfo.statusBarHeight + gap + 32, 184 | height: 32, 185 | left: systemInfo.windowWidth - width - 10, 186 | right: systemInfo.windowWidth - 10, 187 | top: systemInfo.statusBarHeight + gap, 188 | width 189 | } 190 | } 191 | return rect 192 | }, 193 | getSystemInfo() { 194 | const app = getApp() 195 | if (app.globalSystemInfo && !app.globalSystemInfo.ios) { 196 | return app.globalSystemInfo 197 | } else { 198 | const systemInfo = wx.getSystemInfoSync() 199 | const ios = !!(systemInfo.system.toLowerCase().search('ios') + 1) 200 | const rect = this.getMenuButtonBoundingClientRect(systemInfo) 201 | 202 | let navBarHeight = '' 203 | if (!systemInfo.statusBarHeight) { 204 | systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20 205 | navBarHeight = (function () { 206 | const gap = rect.top - systemInfo.statusBarHeight 207 | return 2 * gap + rect.height 208 | }()) 209 | 210 | systemInfo.statusBarHeight = 0 211 | systemInfo.navBarExtendHeight = 0 // 下方扩展4像素高度 防止下方边距太小 212 | } else { 213 | navBarHeight = (function () { 214 | const gap = rect.top - systemInfo.statusBarHeight 215 | return systemInfo.statusBarHeight + 2 * gap + rect.height 216 | }()) 217 | if (ios) { 218 | systemInfo.navBarExtendHeight = 4 // 下方扩展4像素高度 防止下方边距太小 219 | } else { 220 | systemInfo.navBarExtendHeight = 0 221 | } 222 | } 223 | systemInfo.navBarHeight = navBarHeight // 导航栏高度不包括statusBarHeight 224 | systemInfo.capsulePosition = rect 225 | systemInfo.ios = ios // 是否ios 226 | 227 | app.globalSystemInfo = systemInfo // 将信息保存到全局变量中,后边再用就不用重新异步获取了 228 | 229 | // console.log('systemInfo', systemInfo); 230 | return systemInfo 231 | } 232 | } 233 | } 234 | }) 235 | -------------------------------------------------------------------------------- /src/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } 5 | -------------------------------------------------------------------------------- /src/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {{title}} 24 | 25 | 26 | 27 | {{searchText}} 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/index.wxss: -------------------------------------------------------------------------------- 1 | view, 2 | text, 3 | scroll-view, 4 | input, 5 | button, 6 | image, 7 | cover-view { 8 | box-sizing: border-box; 9 | } 10 | page { 11 | --height: 44px; /* 4*2+32 */ 12 | --right: 97px; /* 10+87 */ 13 | --navBarExtendHeight: 4px; 14 | box-sizing: border-box; 15 | } 16 | .lxy-nav-bar .ios { 17 | --height: 44px; /* 4*2+32 */ 18 | --right: 97px; /* 10+87 */ 19 | --navBarExtendHeight: 4px; 20 | box-sizing: border-box; 21 | } 22 | .lxy-nav-bar .android { 23 | --height: 48px; /* 8*2+32 */ 24 | --right: 96px; /* 10+87 */ 25 | --navBarExtendHeight: 4px; 26 | box-sizing: border-box; 27 | } 28 | .lxy-nav-bar .devtools { 29 | --height: 42px; /* 5*2+32 */ 30 | --right: 88px; /* 10+87 */ 31 | --navBarExtendHeight: 4px; 32 | box-sizing: border-box; 33 | } 34 | .lxy-nav-bar__inner { 35 | position: fixed; 36 | top: 0; 37 | left: 0; 38 | z-index: 5001; 39 | height: var(--height); 40 | display: flex; 41 | align-items: center; 42 | padding-right: var(--right); 43 | width: 100%; 44 | padding-bottom: var(--navBarExtendHeight); 45 | } 46 | .lxy-nav-bar__inner .lxy-nav-bar__left { 47 | position: relative; 48 | width: var(--right); 49 | height: 32px; 50 | /* padding-left: 10px; */ 51 | display: flex; 52 | align-items: center; 53 | } 54 | .lxy-nav-bar__buttons { 55 | height: 100%; 56 | width: 100%; 57 | display: flex; 58 | align-items: center; 59 | border-radius: 16px; 60 | border: 1rpx solid rgba(204, 204, 204, 0.6); 61 | position: relative; 62 | } 63 | .lxy-nav-bar__buttons.android { 64 | border: 1rpx solid rgba(234, 234, 234, 0.6); 65 | } 66 | .lxy-nav-bar__buttons::after { 67 | position: absolute; 68 | content: ''; 69 | width: 1rpx; 70 | height: 18.4px; 71 | background: rgba(204, 204, 204, 0.6); 72 | left: 50%; 73 | top: 50%; 74 | transform: translate(-50%, -50%); 75 | } 76 | .lxy-nav-bar__buttons.android::after { 77 | background: rgba(234, 234, 234, 0.6); 78 | } 79 | .lxy-nav-bar__button { 80 | width: 50%; 81 | height: 100%; 82 | display: flex; 83 | font-size: 12px; 84 | background-repeat: no-repeat; 85 | background-position: center center; 86 | background-size: 1em 2em; 87 | } 88 | 89 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback:active, 90 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome:active { 91 | opacity: 0.5; 92 | } 93 | .lxy-nav-bar__inner .lxy-nav-bar__center { 94 | font-size: 17px; 95 | line-height: 17px; 96 | text-align: center; 97 | position: relative; 98 | flex: 1; 99 | display: -webkit-box; 100 | display: -webkit-flex; 101 | display: flex; 102 | align-items: center; 103 | justify-content: center; 104 | padding-left: 10px; 105 | } 106 | .lxy-nav-bar__inner .lxy-nav-bar__center .lxy-nav-bar__center-title { 107 | margin-top: -2px; 108 | } 109 | .lxy-nav-bar__inner .lxy-nav-bar__loading { 110 | font-size: 0; 111 | } 112 | .lxy-nav-bar__inner .lxy-nav-bar__loading .lxy-loading { 113 | margin-left: 0; 114 | } 115 | .lxy-nav-bar__inner .lxy-nav-bar__right { 116 | margin-right: 10px; 117 | } 118 | .lxy-nav-bar__placeholder { 119 | height: var(--height); 120 | background: #f8f8f8; 121 | position: relative; 122 | z-index: 50; 123 | } 124 | 125 | .lxy-nav-bar-search { 126 | width: 100%; 127 | height: 100%; 128 | display: flex; 129 | justify-content: center; 130 | align-items: center; 131 | width: 100%; 132 | height: 32px; 133 | border-radius: 16px; 134 | position: relative; 135 | background: #f6f6f6; 136 | } 137 | 138 | .lxy-nav-bar-search__input { 139 | height: 100%; 140 | display: flex; 141 | align-items: center; 142 | color: #999; 143 | font-size: 15px; 144 | line-height: 15px; 145 | } 146 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback { 147 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E"); 148 | } 149 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback.white { 150 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z' fill='%23ffffff'/%3E%3C/svg%3E"); 151 | } 152 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome { 153 | background-image: url("data:image/svg+xml,%3Csvg t='1565752242401' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='4326' width='48' height='48'%3E%3Cpath d='M931.148 451.25L591.505 97.654c-21.106-21.953-49.313-34.034-79.551-34.034-30.235 0-58.448 12.081-79.554 34.034L92.76 451.25c-35.049 36.498-30.536 68.044-24.742 81.222 4.13 9.35 18.07 35.05 58.231 35.05h49.78v272.016c0 61.756 44.342 119.906 107.357 119.906h144.587v-287.87c0-30.866-4.675-48.062 26.848-48.062h114.268c31.52 0 26.845 17.196 26.845 48.061v287.872h144.588c63.013 0 107.358-58.15 107.358-119.906V567.523h49.782c40.16 0 54.1-25.7 58.229-35.05 5.793-13.18 10.306-44.726-24.743-81.224z m-33.486 60.28h-105.77v328.007c0 30.865-19.877 63.917-51.37 63.917h-88.6V671.572c0-61.761-19.79-104.05-82.832-104.05H454.821c-63.045 0-82.836 42.289-82.836 104.05v231.883h-88.599c-31.495 0-51.37-33.052-51.37-63.917V511.529H126.25c-0.984 0-1.888-3.852-2.708-3.907 1.94-3.388 5.276-11.975 10.825-17.743l339.671-353.35c10.142-10.578 24.467-17.057 38.353-16.924 13.888-0.133 27.342 6.346 37.483 16.923L889.54 489.88c5.549 5.768 8.885 14.355 10.825 17.743-0.818 0.055-1.72 3.907-2.704 3.907z' fill='%23000000' p-id='4327'%3E%3C/path%3E%3C/svg%3E"); 154 | background-size: 22px 22px; 155 | } 156 | .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome.white { 157 | background-image: url("data:image/svg+xml,%3Csvg t='1565752242401' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='4326' width='48' height='48'%3E%3Cpath d='M931.148 451.25L591.505 97.654c-21.106-21.953-49.313-34.034-79.551-34.034-30.235 0-58.448 12.081-79.554 34.034L92.76 451.25c-35.049 36.498-30.536 68.044-24.742 81.222 4.13 9.35 18.07 35.05 58.231 35.05h49.78v272.016c0 61.756 44.342 119.906 107.357 119.906h144.587v-287.87c0-30.866-4.675-48.062 26.848-48.062h114.268c31.52 0 26.845 17.196 26.845 48.061v287.872h144.588c63.013 0 107.358-58.15 107.358-119.906V567.523h49.782c40.16 0 54.1-25.7 58.229-35.05 5.793-13.18 10.306-44.726-24.743-81.224z m-33.486 60.28h-105.77v328.007c0 30.865-19.877 63.917-51.37 63.917h-88.6V671.572c0-61.761-19.79-104.05-82.832-104.05H454.821c-63.045 0-82.836 42.289-82.836 104.05v231.883h-88.599c-31.495 0-51.37-33.052-51.37-63.917V511.529H126.25c-0.984 0-1.888-3.852-2.708-3.907 1.94-3.388 5.276-11.975 10.825-17.743l339.671-353.35c10.142-10.578 24.467-17.057 38.353-16.924 13.888-0.133 27.342 6.346 37.483 16.923L889.54 489.88c5.549 5.768 8.885 14.355 10.825 17.743-0.818 0.055-1.72 3.907-2.704 3.907z' fill='%23ffffff' p-id='4327'%3E%3C/path%3E%3C/svg%3E"); 158 | background-size: 22px 22px; 159 | } 160 | .lxy-nav-bar-search__icon { 161 | width: 22px; 162 | height: 22px; 163 | display: flex; 164 | align-items: center; 165 | justify-content: center; 166 | background-image: url("data:image/svg+xml,%3Csvg t='1565691512239' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='1240' width='48' height='48'%3E%3Cpath d='M819.2 798.254545L674.909091 653.963636c46.545455-48.872727 74.472727-114.036364 74.472727-186.181818 0-151.272727-123.345455-274.618182-274.618182-274.618182-151.272727 0-274.618182 123.345455-274.618181 274.618182 0 151.272727 123.345455 274.618182 274.618181 274.618182 65.163636 0 128-23.272727 174.545455-62.836364l144.290909 144.290909c2.327273 2.327273 6.981818 4.654545 11.636364 4.654546s9.309091-2.327273 11.636363-4.654546c6.981818-6.981818 6.981818-18.618182 2.327273-25.6zM235.054545 467.781818c0-132.654545 107.054545-239.709091 239.709091-239.709091 132.654545 0 239.709091 107.054545 239.709091 239.709091 0 132.654545-107.054545 239.709091-239.709091 239.709091-132.654545 0-239.709091-107.054545-239.709091-239.709091z' fill='%23999999' p-id='1241'%3E%3C/path%3E%3C/svg%3E"); 167 | background-repeat: no-repeat; 168 | background-size: cover; 169 | } 170 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../tools/test/helper') 2 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const gulp = require('gulp') 4 | const clean = require('gulp-clean') 5 | const less = require('gulp-less') 6 | const rename = require('gulp-rename') 7 | const gulpif = require('gulp-if') 8 | const sourcemaps = require('gulp-sourcemaps') 9 | const webpack = require('webpack') 10 | const gulpInstall = require('gulp-install') 11 | 12 | const config = require('./config') 13 | const checkComponents = require('./checkcomponents') 14 | const _ = require('./utils') 15 | 16 | const wxssConfig = config.wxss || {} 17 | const srcPath = config.srcPath 18 | const distPath = config.distPath 19 | 20 | /** 21 | * get wxss stream 22 | */ 23 | function wxss(wxssFileList) { 24 | if (!wxssFileList.length) return false 25 | 26 | return gulp.src(wxssFileList, {cwd: srcPath, base: srcPath}) 27 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.init())) 28 | .pipe(gulpif(wxssConfig.less, less({paths: [srcPath]}))) 29 | .pipe(rename({extname: '.wxss'})) 30 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.write('./'))) 31 | .pipe(_.logger(wxssConfig.less ? 'generate' : undefined)) 32 | .pipe(gulp.dest(distPath)) 33 | } 34 | 35 | /** 36 | * get js stream 37 | */ 38 | function js(jsFileMap, scope) { 39 | const webpackConfig = config.webpack 40 | const webpackCallback = (err, stats) => { 41 | if (!err) { 42 | // eslint-disable-next-line no-console 43 | console.log(stats.toString({ 44 | assets: true, 45 | cached: false, 46 | colors: true, 47 | children: false, 48 | errors: true, 49 | warnings: true, 50 | version: true, 51 | modules: false, 52 | publicPath: true, 53 | })) 54 | } else { 55 | // eslint-disable-next-line no-console 56 | console.log(err) 57 | } 58 | } 59 | 60 | webpackConfig.entry = jsFileMap 61 | webpackConfig.output.path = distPath 62 | 63 | if (scope.webpackWatcher) { 64 | scope.webpackWatcher.close() 65 | scope.webpackWatcher = null 66 | } 67 | 68 | if (config.isWatch) { 69 | scope.webpackWatcher = webpack(webpackConfig).watch({ 70 | ignored: /node_modules/, 71 | }, webpackCallback) 72 | } else { 73 | webpack(webpackConfig).run(webpackCallback) 74 | } 75 | } 76 | 77 | /** 78 | * copy file 79 | */ 80 | function copy(copyFileList) { 81 | if (!copyFileList.length) return false 82 | 83 | return gulp.src(copyFileList, {cwd: srcPath, base: srcPath}) 84 | .pipe(_.logger()) 85 | .pipe(gulp.dest(distPath)) 86 | } 87 | 88 | /** 89 | * install packages 90 | */ 91 | function install() { 92 | return gulp.series(async () => { 93 | const demoDist = config.demoDist 94 | const demoPackageJsonPath = path.join(demoDist, 'package.json') 95 | const packageJson = _.readJson(path.resolve(__dirname, '../package.json')) 96 | const dependencies = packageJson.dependencies || {} 97 | 98 | await _.writeFile(demoPackageJsonPath, JSON.stringify({dependencies}, null, '\t')) // write dev demo's package.json 99 | }, () => { 100 | const demoDist = config.demoDist 101 | const demoPackageJsonPath = path.join(demoDist, 'package.json') 102 | 103 | return gulp.src(demoPackageJsonPath) 104 | .pipe(gulpInstall({production: true})) 105 | }) 106 | } 107 | 108 | class BuildTask { 109 | constructor(id, entry) { 110 | if (!entry) return 111 | 112 | this.id = id 113 | this.entries = Array.isArray(config.entry) ? config.entry : [config.entry] 114 | this.copyList = Array.isArray(config.copy) ? config.copy : [] 115 | this.componentListMap = {} 116 | this.cachedComponentListMap = {} 117 | 118 | this.init() 119 | } 120 | 121 | init() { 122 | const id = this.id 123 | 124 | /** 125 | * clean the dist folder 126 | */ 127 | gulp.task(`${id}-clean-dist`, () => gulp.src(distPath, {read: false, allowEmpty: true}).pipe(clean())) 128 | 129 | /** 130 | * copy demo to the dev folder 131 | */ 132 | let isDemoExists = false 133 | gulp.task(`${id}-demo`, gulp.series(async () => { 134 | const demoDist = config.demoDist 135 | 136 | isDemoExists = await _.checkFileExists(path.join(demoDist, 'project.config.json')) 137 | }, done => { 138 | if (!isDemoExists) { 139 | const demoSrc = config.demoSrc 140 | const demoDist = config.demoDist 141 | 142 | return gulp.src('**/*', {cwd: demoSrc, base: demoSrc}) 143 | .pipe(gulp.dest(demoDist)) 144 | } 145 | 146 | return done() 147 | })) 148 | 149 | /** 150 | * install packages for dev 151 | */ 152 | gulp.task(`${id}-install`, install()) 153 | 154 | /** 155 | * check custom components 156 | */ 157 | gulp.task(`${id}-component-check`, async () => { 158 | const entries = this.entries 159 | const mergeComponentListMap = {} 160 | for (let i = 0, len = entries.length; i < len; i++) { 161 | let entry = entries[i] 162 | entry = path.join(srcPath, `${entry}.json`) 163 | // eslint-disable-next-line no-await-in-loop 164 | const newComponentListMap = await checkComponents(entry) 165 | 166 | _.merge(mergeComponentListMap, newComponentListMap) 167 | } 168 | 169 | this.cachedComponentListMap = this.componentListMap 170 | this.componentListMap = mergeComponentListMap 171 | }) 172 | 173 | /** 174 | * write json to the dist folder 175 | */ 176 | gulp.task(`${id}-component-json`, done => { 177 | const jsonFileList = this.componentListMap.jsonFileList 178 | 179 | if (jsonFileList && jsonFileList.length) { 180 | return copy(this.componentListMap.jsonFileList) 181 | } 182 | 183 | return done() 184 | }) 185 | 186 | /** 187 | * copy wxml to the dist folder 188 | */ 189 | gulp.task(`${id}-component-wxml`, done => { 190 | const wxmlFileList = this.componentListMap.wxmlFileList 191 | 192 | if (wxmlFileList && 193 | wxmlFileList.length && 194 | !_.compareArray(this.cachedComponentListMap.wxmlFileList, wxmlFileList)) { 195 | return copy(wxmlFileList) 196 | } 197 | 198 | return done() 199 | }) 200 | 201 | /** 202 | * generate wxss to the dist folder 203 | */ 204 | gulp.task(`${id}-component-wxss`, done => { 205 | const wxssFileList = this.componentListMap.wxssFileList 206 | 207 | if (wxssFileList && 208 | wxssFileList.length && 209 | !_.compareArray(this.cachedComponentListMap.wxssFileList, wxssFileList)) { 210 | return wxss(wxssFileList, srcPath, distPath) 211 | } 212 | 213 | return done() 214 | }) 215 | 216 | /** 217 | * generate js to the dist folder 218 | */ 219 | gulp.task(`${id}-component-js`, done => { 220 | const jsFileList = this.componentListMap.jsFileList 221 | 222 | if (jsFileList && 223 | jsFileList.length && 224 | !_.compareArray(this.cachedComponentListMap.jsFileList, jsFileList)) { 225 | js(this.componentListMap.jsFileMap, this) 226 | } 227 | 228 | return done() 229 | }) 230 | 231 | /** 232 | * copy resources to dist folder 233 | */ 234 | gulp.task(`${id}-copy`, gulp.parallel(done => { 235 | const copyList = this.copyList 236 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.!(wxss)')) 237 | 238 | if (copyFileList.length) return copy(copyFileList) 239 | 240 | return done() 241 | }, done => { 242 | const copyList = this.copyList 243 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.wxss')) 244 | 245 | if (copyFileList.length) return wxss(copyFileList, srcPath, distPath) 246 | 247 | return done() 248 | })) 249 | 250 | /** 251 | * watch json 252 | */ 253 | gulp.task(`${id}-watch-json`, () => gulp.watch(this.componentListMap.jsonFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`)))) 254 | 255 | /** 256 | * watch wxml 257 | */ 258 | gulp.task(`${id}-watch-wxml`, () => { 259 | this.cachedComponentListMap.wxmlFileList = null 260 | return gulp.watch(this.componentListMap.wxmlFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxml`)) 261 | }) 262 | 263 | /** 264 | * watch wxss 265 | */ 266 | gulp.task(`${id}-watch-wxss`, () => { 267 | this.cachedComponentListMap.wxssFileList = null 268 | return gulp.watch('**/*.wxss', {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxss`)) 269 | }) 270 | 271 | /** 272 | * watch resources 273 | */ 274 | gulp.task(`${id}-watch-copy`, () => { 275 | const copyList = this.copyList 276 | const copyFileList = copyList.map(dir => path.join(dir, '**/*')) 277 | const watchCallback = filePath => copy([filePath]) 278 | 279 | return gulp.watch(copyFileList, {cwd: srcPath, base: srcPath}) 280 | .on('change', watchCallback) 281 | .on('add', watchCallback) 282 | .on('unlink', watchCallback) 283 | }) 284 | 285 | /** 286 | * watch demo 287 | */ 288 | gulp.task(`${id}-watch-demo`, () => { 289 | const demoSrc = config.demoSrc 290 | const demoDist = config.demoDist 291 | const watchCallback = filePath => gulp.src(filePath, {cwd: demoSrc, base: demoSrc}) 292 | .pipe(gulp.dest(demoDist)) 293 | 294 | return gulp.watch('**/*', {cwd: demoSrc, base: demoSrc}) 295 | .on('change', watchCallback) 296 | .on('add', watchCallback) 297 | .on('unlink', watchCallback) 298 | }) 299 | 300 | /** 301 | * watch installed packages 302 | */ 303 | gulp.task(`${id}-watch-install`, () => gulp.watch(path.resolve(__dirname, '../package.json'), install())) 304 | 305 | /** 306 | * build custom component 307 | */ 308 | gulp.task(`${id}-build`, gulp.series(`${id}-clean-dist`, `${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`, `${id}-copy`))) 309 | 310 | gulp.task(`${id}-watch`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`, gulp.parallel(`${id}-watch-wxml`, `${id}-watch-wxss`, `${id}-watch-json`, `${id}-watch-copy`, `${id}-watch-install`, `${id}-watch-demo`))) 311 | 312 | gulp.task(`${id}-dev`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`)) 313 | 314 | gulp.task(`${id}-default`, gulp.series(`${id}-build`)) 315 | } 316 | } 317 | 318 | module.exports = BuildTask 319 | -------------------------------------------------------------------------------- /tools/checkcomponents.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const _ = require('./utils') 4 | const config = require('./config') 5 | 6 | const srcPath = config.srcPath 7 | 8 | /** 9 | * get json path's info 10 | */ 11 | function getJsonPathInfo(jsonPath) { 12 | const dirPath = path.dirname(jsonPath) 13 | const fileName = path.basename(jsonPath, '.json') 14 | const relative = path.relative(srcPath, dirPath) 15 | const fileBase = path.join(relative, fileName) 16 | 17 | return { 18 | dirPath, fileName, relative, fileBase 19 | } 20 | } 21 | 22 | /** 23 | * check included components 24 | */ 25 | const checkProps = ['usingComponents', 'componentGenerics'] 26 | async function checkIncludedComponents(jsonPath, componentListMap) { 27 | const json = _.readJson(jsonPath) 28 | if (!json) throw new Error(`json is not valid: "${jsonPath}"`) 29 | 30 | const {dirPath, fileName, fileBase} = getJsonPathInfo(jsonPath) 31 | 32 | for (let i = 0, len = checkProps.length; i < len; i++) { 33 | const checkProp = checkProps[i] 34 | const checkPropValue = json[checkProp] || {} 35 | const keys = Object.keys(checkPropValue) 36 | 37 | for (let j = 0, jlen = keys.length; j < jlen; j++) { 38 | const key = keys[j] 39 | let value = typeof checkPropValue[key] === 'object' ? checkPropValue[key].default : checkPropValue[key] 40 | if (!value) continue 41 | 42 | value = _.transformPath(value, path.sep) 43 | 44 | // check relative path 45 | const componentPath = `${path.join(dirPath, value)}.json` 46 | // eslint-disable-next-line no-await-in-loop 47 | const isExists = await _.checkFileExists(componentPath) 48 | if (isExists) { 49 | // eslint-disable-next-line no-await-in-loop 50 | await checkIncludedComponents(componentPath, componentListMap) 51 | } 52 | } 53 | } 54 | 55 | // checked 56 | componentListMap.wxmlFileList.push(`${fileBase}.wxml`) 57 | componentListMap.wxssFileList.push(`${fileBase}.wxss`) 58 | componentListMap.jsonFileList.push(`${fileBase}.json`) 59 | componentListMap.jsFileList.push(`${fileBase}.js`) 60 | 61 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js` 62 | } 63 | 64 | module.exports = async function (entry) { 65 | const componentListMap = { 66 | wxmlFileList: [], 67 | wxssFileList: [], 68 | jsonFileList: [], 69 | jsFileList: [], 70 | 71 | jsFileMap: {}, // for webpack entry 72 | } 73 | 74 | const isExists = await _.checkFileExists(entry) 75 | if (!isExists) { 76 | const {dirPath, fileName, fileBase} = getJsonPathInfo(entry) 77 | 78 | componentListMap.jsFileList.push(`${fileBase}.js`) 79 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js` 80 | 81 | return componentListMap 82 | } 83 | 84 | await checkIncludedComponents(entry, componentListMap) 85 | 86 | return componentListMap 87 | } 88 | -------------------------------------------------------------------------------- /tools/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const webpack = require('webpack') 4 | const nodeExternals = require('webpack-node-externals') 5 | 6 | const isDev = process.argv.indexOf('--develop') >= 0 7 | const isWatch = process.argv.indexOf('--watch') >= 0 8 | const demoSrc = path.resolve(__dirname, './demo') 9 | const demoDist = path.resolve(__dirname, '../miniprogram_dev') 10 | const src = path.resolve(__dirname, '../src') 11 | const dev = path.join(demoDist, 'components') 12 | const dist = path.resolve(__dirname, '../miniprogram_dist') 13 | 14 | module.exports = { 15 | entry: ['index'], 16 | 17 | isDev, 18 | isWatch, 19 | srcPath: src, 20 | distPath: isDev ? dev : dist, 21 | 22 | demoSrc, 23 | demoDist, 24 | 25 | wxss: { 26 | less: false, // compile wxss with less 27 | sourcemap: false, // source map for less 28 | }, 29 | 30 | webpack: { 31 | mode: 'production', 32 | output: { 33 | filename: '[name].js', 34 | libraryTarget: 'commonjs2', 35 | }, 36 | target: 'node', 37 | externals: [nodeExternals()], // ignore node_modules 38 | module: { 39 | rules: [{ 40 | test: /\.js$/i, 41 | use: [ 42 | 'babel-loader', 43 | 'eslint-loader' 44 | ], 45 | exclude: /node_modules/ 46 | }], 47 | }, 48 | resolve: { 49 | modules: [src, 'node_modules'], 50 | extensions: ['.js', '.json'], 51 | }, 52 | plugins: [ 53 | new webpack.DefinePlugin({}), 54 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}), 55 | ], 56 | optimization: { 57 | minimize: false, 58 | }, 59 | // devtool: 'nosources-source-map', // source map for js 60 | performance: { 61 | hints: 'warning', 62 | assetFilter: assetFilename => assetFilename.endsWith('.js') 63 | } 64 | }, 65 | copy: ['./wxml', './wxss', './wxs', './images'], 66 | } 67 | -------------------------------------------------------------------------------- /tools/demo/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({ 3 | onLaunch() { 4 | } 5 | // globalData: {} 6 | }) 7 | -------------------------------------------------------------------------------- /tools/demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/demo1/index", 5 | "pages/demo2/index", 6 | "pages/demo3/index", 7 | "pages/demo4/index", 8 | "pages/demo5/index", 9 | "pages/demo6/index", 10 | "pages/demo7/index", 11 | "pages/demo8/index", 12 | "pages/demo9/index", 13 | "pages/demo10/index" 14 | ], 15 | "window": { 16 | "backgroundTextStyle": "light", 17 | "navigationBarBackgroundColor": "#fff", 18 | "navigationBarTitleText": "自定义导航栏", 19 | "navigationBarTextStyle": "black", 20 | "navigationStyle": "custom" 21 | }, 22 | "usingComponents": { 23 | "navBar": "/components/index" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/demo/app.wxss: -------------------------------------------------------------------------------- 1 | /* page { 2 | background-color: #e5e5e5; 3 | } */ 4 | .main { 5 | /* prettier-ignore */ 6 | height: 800PX; 7 | background: #eeeeee; 8 | padding-top: 100rpx; 9 | font-size: 30rpx; 10 | line-height: 60rpx; 11 | /* prettier-ignore */ 12 | padding: 30rpx; 13 | text-align: center; 14 | } 15 | .main .p.active { 16 | color: #087ad8; 17 | text-align: left; 18 | } 19 | .main .p.active:active { 20 | color: #0397fa; 21 | } 22 | 23 | /**app.wxss**/ 24 | .container { 25 | height: 100%; 26 | display: -webkit-box; 27 | display: -webkit-flex; 28 | display: -ms-flexbox; 29 | display: flex; 30 | -webkit-box-orient: vertical; 31 | -webkit-box-direction: normal; 32 | -webkit-flex-direction: column; 33 | -ms-flex-direction: column; 34 | flex-direction: column; 35 | -webkit-box-align: center; 36 | -webkit-align-items: center; 37 | -ms-flex-align: center; 38 | align-items: center; 39 | -webkit-box-pack: justify; 40 | -webkit-justify-content: space-between; 41 | -ms-flex-pack: justify; 42 | justify-content: space-between; 43 | padding: 200rpx 0; 44 | -webkit-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .main { 49 | /* prettier-ignore */ 50 | height: 400PX; 51 | height: 100vh; 52 | background: #eeeeee; 53 | padding-top: 100rpx; 54 | font-size: 30rpx; 55 | line-height: 60rpx; 56 | padding: 30rpx; 57 | text-align: center; 58 | } 59 | 60 | .lxy-nav-bar-search { 61 | display: -webkit-box; 62 | display: -webkit-flex; 63 | display: -ms-flexbox; 64 | display: flex; 65 | -webkit-box-pack: center; 66 | -webkit-justify-content: center; 67 | -ms-flex-pack: center; 68 | justify-content: center; 69 | -webkit-box-align: center; 70 | -webkit-align-items: center; 71 | -ms-flex-align: center; 72 | align-items: center; 73 | width: 100%; 74 | /* prettier-ignore */ 75 | height: 32PX; 76 | /* prettier-ignore */ 77 | font-size: 16PX; 78 | /* prettier-ignore */ 79 | border-radius: 16PX; 80 | /* prettier-ignore */ 81 | padding-left: 5PX; 82 | position: relative; 83 | background: #f6f6f6; 84 | } 85 | 86 | .srch-ipt { 87 | /* prettier-ignore */ 88 | height: 32PX; 89 | text-align: left; 90 | /* prettier-ignore */ 91 | font-size: 15PX; 92 | } 93 | 94 | .ipt-placeholder { 95 | /* prettier-ignore */ 96 | font-size: 15PX; 97 | color: #bebebe; 98 | text-align: left; 99 | } 100 | 101 | .lxy-nav-bar-search__input { 102 | flex: 1; 103 | } 104 | 105 | .lxy-nav-bar-search__icon { 106 | /* prettier-ignore */ 107 | width: 22PX; 108 | /* prettier-ignore */ 109 | height: 22PX; 110 | display: -webkit-box; 111 | display: -webkit-flex; 112 | display: -ms-flexbox; 113 | display: flex; 114 | -webkit-box-align: center; 115 | -webkit-align-items: center; 116 | -ms-flex-align: center; 117 | align-items: center; 118 | -webkit-box-pack: center; 119 | -webkit-justify-content: center; 120 | -ms-flex-pack: center; 121 | justify-content: center; 122 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADBklEQVRoQ+2YS3IaMRCGu7UQ7MINjE9gcgLjEyTcADZoZuXcIPYJglczYgOcINwg+ATBJwg5gZMlLNSpdiTXZMpV1mOo4KpRFQuXZ1r99d9qdQ/CG1/4xv2HFuB/K9gq0CqQGIE2hRIDmPx6IwosFovefr+/BICPANDnHyL2iWhjPVwLIe6n0+k22eOagSQAdvxwOFwDwCcA6FnbPwFgV9lnAADv+G8GIqLbPM8dWDJPNIDWmqO9sI7fG2OW3W53PZlMftW9ms/nA2PMGBHHFmYtpZy89GwoURSA1poj/gUAfgPAWCm19tnYptoSET8AwNYYM8rzvKqWj5l/ngkGKIpiLIRYENFDp9MZxkTR2WAIKeVVjA1HEQTAqUBE31OcdxtXINZKqVFw6O0LQQBaa5a7Z4wZpErP+5dlOUPEa2PMJM/zZQyEN0AlYrdKqZuYzerv2Cq2I6LHLMvOY2x6A5RluUXEnlKK63xjqxKYkW8xqG7uBVAURV8I8QMAVkopLoWNrVTbvgBPlQcAoqL0Gi2ry/dDTBp5AWitOec/I+L7Y7QDZVmu+W5QSnn5E5xCDiBmg9eiz/9Pse9FnLLBSQEYY66abMQcnNaam7vLGIV9FeDG7euxDrG9ICGmRHsB2Avn8ZhllIjusizjJjFoeQHYg8YyX0gpz1Oar7p3qRXOG6AoiqEQ4hsANN1K8AX5oJQaBoU+spl7OmxN3Qeu/qcUB28FGNi20wxBqankUic2951aQQD2LDxVJCLaCSFGMTeza6N5rsiyjGfm6BUMUIHg/p0AYCalvPM52HyOEJFbkud8T5kF2JcogEo68Sx8xmogIvczq7oitgTzDMzK8Y/XrZRytt/vN4h4kQIRDeA0t/08N3tn1TywUPXZYWWMuXHTnB3ykyCSAZzT9oBzanBOPztORDwIbaSUm5fSLBWiMYDoU8gfl/5+2YtS4iQAGD4W4mQA6hC+jeNJAVQg+Ovd0mfIPzmA0LPUAoRGrOnnWwWajmiovVaB0Ig1/XyrQNMRDbX3B3I3pUA0WyZuAAAAAElFTkSuQmCC); 123 | background-repeat: no-repeat; 124 | background-size: cover; 125 | } 126 | 127 | .location { 128 | /* prettier-ignore */ 129 | font-size: 14PX; 130 | /* prettier-ignore */ 131 | width: 52PX; 132 | height: 100%; 133 | display: -webkit-box; 134 | display: -webkit-flex; 135 | display: -ms-flexbox; 136 | display: flex; 137 | -webkit-box-align: center; 138 | -webkit-align-items: center; 139 | -ms-flex-align: center; 140 | align-items: center; 141 | color: #fff; 142 | position: relative; 143 | /* prettier-ignore */ 144 | padding-left: 10PX; 145 | } 146 | 147 | .location .icon { 148 | width: 0; 149 | height: 0; 150 | /* prettier-ignore */ 151 | border-left: 5PX solid transparent; 152 | /* prettier-ignore */ 153 | border-right: 5PX solid transparent; 154 | /* prettier-ignore */ 155 | border-top: 5PX solid #fff; 156 | /* prettier-ignore */ 157 | margin-left: 2.5PX; 158 | } 159 | 160 | .location .btn { 161 | position: absolute; 162 | left: 0; 163 | top: 0; 164 | opacity: 0; 165 | width: 100%; 166 | height: 100%; 167 | } 168 | -------------------------------------------------------------------------------- /tools/demo/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /tools/demo/pages/demo1/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | // 获取应用实例 3 | // const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick() { 8 | wx.showModal({ 9 | title: '你点击了返回', 10 | content: '是否确认放回', 11 | success: e => { 12 | if (e.confirm) { 13 | const pages = getCurrentPages() 14 | if (pages.length >= 2) { 15 | wx.navigateBack({ 16 | delta: 1 17 | }) 18 | } else { 19 | wx.navigateTo({ 20 | url: '/pages/index/index' 21 | }) 22 | } 23 | } 24 | } 25 | }) 26 | }, 27 | handlerGohomeClick() { 28 | wx.navigateTo({ 29 | url: '/pages/index/index' 30 | }) 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /tools/demo/pages/demo1/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/demo1/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 搜索页面 12 | 13 | -------------------------------------------------------------------------------- /tools/demo/pages/demo1/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo1/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo10/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: { background: 'rgba(255,255,255,1)' }, 7 | handlerGobackClick() { 8 | wx.showModal({ 9 | title: '你点击了返回', 10 | content: '是否确认放回', 11 | success: e => { 12 | if (e.confirm) { 13 | const pages = getCurrentPages(); 14 | if (pages.length >= 2) { 15 | wx.navigateBack({ 16 | delta: 1 17 | }); 18 | } else { 19 | wx.navigateTo({ 20 | url: '/pages/index/index' 21 | }); 22 | } 23 | } 24 | } 25 | }); 26 | }, 27 | onPageScroll(e) { 28 | let opciaty = e.scrollTop / 130; 29 | if (opciaty >= 1) { 30 | opciaty = 1; 31 | } else if (opciaty <= 0) { 32 | opciaty = 0; 33 | } 34 | console.log('opciaty', opciaty); 35 | this.setData({ background: `rgba(255,0,0,${opciaty})` }); 36 | }, 37 | handlerGohomeClick() { 38 | wx.navigateTo({ 39 | url: '/pages/index/index' 40 | }); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /tools/demo/pages/demo10/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/demo10/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 搜索页面 12 | 13 | -------------------------------------------------------------------------------- /tools/demo/pages/demo10/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo10/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo2/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick(delta) { 8 | const pages = getCurrentPages(); 9 | if (pages.length >= 2) { 10 | wx.navigateBack({ 11 | delta: delta 12 | }); 13 | } else { 14 | wx.navigateTo({ 15 | url: '/pages/index/index' 16 | }); 17 | } 18 | }, 19 | handlerGohomeClick() { 20 | wx.navigateTo({ 21 | url: '/pages/index/index' 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /tools/demo/pages/demo2/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/demo2/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 有返回和home的页面 5 | 6 | -------------------------------------------------------------------------------- /tools/demo/pages/demo2/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo2/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo3/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick() { 8 | wx.showModal({ 9 | title: '你点击了返回', 10 | content: '是否确认放回', 11 | success: e => { 12 | if (e.confirm) { 13 | const pages = getCurrentPages(); 14 | if (pages.length >= 2) { 15 | wx.navigateBack({ 16 | delta: 1 17 | }); 18 | } else { 19 | wx.navigateTo({ 20 | url: '/pages/index/index' 21 | }); 22 | } 23 | } 24 | } 25 | }); 26 | }, 27 | handlerGohomeClick() { 28 | wx.navigateTo({ 29 | url: '/pages/index/index' 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /tools/demo/pages/demo3/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/demo3/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 详情页 5 | 6 | -------------------------------------------------------------------------------- /tools/demo/pages/demo3/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo3/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo4/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick(delta) { 8 | const pages = getCurrentPages(); 9 | if (pages.length >= 2) { 10 | wx.navigateBack({ 11 | delta: delta 12 | }); 13 | } else { 14 | wx.navigateTo({ 15 | url: '/pages/index/index' 16 | }); 17 | } 18 | }, 19 | handlerGohomeClick() { 20 | wx.navigateTo({ 21 | url: '/pages/index/index' 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /tools/demo/pages/demo4/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTextStyle": "white", 3 | "navigationStyle": "custom", 4 | "backgroundColor": "#f8f8f8", 5 | "backgroundColorTop": "#00000", 6 | "backgroundColorBottom": "#f8f8f8", 7 | "usingComponents": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/demo/pages/demo4/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 深色背景详情页 5 | 6 | -------------------------------------------------------------------------------- /tools/demo/pages/demo4/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo4/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo5/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick() { 8 | wx.showModal({ 9 | title: '你点击了返回', 10 | content: '是否确认放回', 11 | success: e => { 12 | if (e.confirm) { 13 | const pages = getCurrentPages(); 14 | if (pages.length >= 2) { 15 | wx.navigateBack({ 16 | delta: 1 17 | }); 18 | } else { 19 | wx.navigateTo({ 20 | url: '/pages/index/index' 21 | }); 22 | } 23 | } 24 | } 25 | }); 26 | }, 27 | handlerGohomeClick() { 28 | wx.navigateTo({ 29 | url: '/pages/index/index' 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /tools/demo/pages/demo5/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTextStyle": "white", 3 | "navigationStyle": "custom", 4 | "backgroundColor": "#f8f8f8", 5 | "backgroundColorTop": "#00000", 6 | "backgroundColorBottom": "#f8f8f8", 7 | "usingComponents": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/demo/pages/demo5/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 深色背景详情页包括返回和home按钮 5 | 6 | -------------------------------------------------------------------------------- /tools/demo/pages/demo5/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo5/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo6/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick(delta) { 8 | const pages = getCurrentPages(); 9 | if (pages.length >= 2) { 10 | wx.navigateBack({ 11 | delta: delta 12 | }); 13 | } else { 14 | wx.navigateTo({ 15 | url: '/pages/index/index' 16 | }); 17 | } 18 | }, 19 | handlerGohomeClick() { 20 | wx.navigateTo({ 21 | url: '/pages/index/index' 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /tools/demo/pages/demo6/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTextStyle": "white", 3 | "navigationStyle": "custom", 4 | "backgroundColor": "#f8f8f8", 5 | "backgroundColorTop": "#00000", 6 | "backgroundColorBottom": "#f8f8f8", 7 | "usingComponents": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/demo/pages/demo6/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 上海 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 点击跳转自定义左侧栏目带自定义搜索框 16 | 17 | -------------------------------------------------------------------------------- /tools/demo/pages/demo6/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo6/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo7/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | search() { 8 | wx.navigateTo({ 9 | url: '/pages/demo/demo1' 10 | }); 11 | }, 12 | handlerGobackClick() { 13 | wx.showModal({ 14 | title: '你点击了返回', 15 | content: '是否确认放回', 16 | success: e => { 17 | if (e.confirm) { 18 | const pages = getCurrentPages(); 19 | if (pages.length >= 2) { 20 | wx.navigateBack({ 21 | delta: 1 22 | }); 23 | } else { 24 | wx.navigateTo({ 25 | url: '/pages/index/index' 26 | }); 27 | } 28 | } 29 | } 30 | }); 31 | }, 32 | handlerGohomeClick() { 33 | wx.navigateTo({ 34 | url: '/pages/index/index' 35 | }); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /tools/demo/pages/demo7/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTextStyle": "white", 3 | "navigationStyle": "custom", 4 | "backgroundColor": "#f8f8f8", 5 | "backgroundColorTop": "#00000", 6 | "backgroundColorBottom": "#f8f8f8", 7 | "usingComponents": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/demo/pages/demo7/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 上海 5 | 6 | 7 | 8 | 9 | 深色背景详情页 10 | 11 | -------------------------------------------------------------------------------- /tools/demo/pages/demo7/index.wxss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingxiaoyi/miniprograms-navigation-bar/271725f003c2e7296df4125cd800610c4afcbb4b/tools/demo/pages/demo7/index.wxss -------------------------------------------------------------------------------- /tools/demo/pages/demo8/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick(delta) { 8 | const pages = getCurrentPages(); 9 | if (pages.length >= 2) { 10 | wx.navigateBack({ 11 | delta: delta 12 | }); 13 | } else { 14 | wx.navigateTo({ 15 | url: '/pages/index/index' 16 | }); 17 | } 18 | }, 19 | handlerGohomeClick() { 20 | wx.navigateTo({ 21 | url: '/pages/index/index' 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /tools/demo/pages/demo8/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/demo8/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 自定义样式和修改默认图标 5 | 6 | -------------------------------------------------------------------------------- /tools/demo/pages/demo8/index.wxss: -------------------------------------------------------------------------------- 1 | .lxy-navbar-extclass .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_goback { 2 | background-image: url("data:image/svg+xml,%3Csvg t='1565943723828' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3607' width='48' height='48'%3E%3Cpath d='M861.7 447.5c-14.9-35-36.1-66.4-63.1-93.4-38.2-38.3-85.4-64.9-139-78.3-23.9-6-48.6-8.1-73.3-8.1H336.8v-83.3c0-12.1-14.1-18.8-23.5-11.2l-162 131.3c-13.7 11.1-13.7 32 0 43.1L313.4 479c9.4 7.6 23.5 0.9 23.5-11.2v-83.3h245.3c92.6 0 175.8 67.3 185.1 159.4 10.7 105.8-73 195.8-176.7 195.8h-337c-32.3 0-58.4 26.2-58.4 58.4 0 32.3 26.2 58.4 58.4 58.4h332.8c24.6 0 49.4-2.1 73.3-8.1 53.6-13.4 100.7-40.1 139-78.3 27-27 48.2-58.4 63.1-93.4 15.5-36.4 23.3-75 23.3-114.7-0.1-39.5-8-78.1-23.4-114.5z m0 0' fill='%23000000' p-id='3608'%3E%3C/path%3E%3C/svg%3E"); 3 | background-size: 22px 22px; 4 | } 5 | 6 | .lxy-navbar-extclass .lxy-nav-bar__inner .lxy-nav-bar__left .lxy-nav-bar__btn_gohome { 7 | background-image: url("data:image/svg+xml,%3Csvg t='1565943334961' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3467' width='48' height='48'%3E%3Cpath d='M426.666667 597.333333h170.666666v298.666667h-170.666666z' fill='%23000000' p-id='3468'%3E%3C/path%3E%3Cpath d='M871.253333 434.346667L542.293333 98.133333a42.666667 42.666667 0 0 0-60.586666 0l-328.96 336.64A85.333333 85.333333 0 0 0 128 495.786667V853.333333a85.333333 85.333333 0 0 0 80.64 85.333334H341.333333v-384a42.666667 42.666667 0 0 1 42.666667-42.666667h256a42.666667 42.666667 0 0 1 42.666667 42.666667v384h132.693333A85.333333 85.333333 0 0 0 896 853.333333v-357.546666a88.32 88.32 0 0 0-24.746667-61.44z' fill='%23000000' p-id='3469'%3E%3C/path%3E%3C/svg%3E"); 8 | background-size: 22px 22px; 9 | } 10 | -------------------------------------------------------------------------------- /tools/demo/pages/demo9/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | //const app = getApp(); 4 | 5 | Page({ 6 | data: {}, 7 | handlerGobackClick(delta) { 8 | const pages = getCurrentPages(); 9 | if (pages.length >= 2) { 10 | wx.navigateBack({ 11 | delta: delta 12 | }); 13 | } else { 14 | wx.navigateTo({ 15 | url: '/pages/index/index' 16 | }); 17 | } 18 | }, 19 | handlerGohomeClick() { 20 | wx.navigateTo({ 21 | url: '/pages/index/index' 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /tools/demo/pages/demo9/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTextStyle": "white", 3 | "navigationStyle": "custom", 4 | "backgroundColor": "#f8f8f8", 5 | "backgroundColorTop": "#00000", 6 | "backgroundColorBottom": "#f8f8f8", 7 | "usingComponents": {} 8 | } 9 | -------------------------------------------------------------------------------- /tools/demo/pages/demo9/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 此处为图片背景 7 | 8 | -------------------------------------------------------------------------------- /tools/demo/pages/demo9/index.wxss: -------------------------------------------------------------------------------- 1 | .main22 { 2 | /* prettier-ignore */ 3 | height: 400PX; 4 | height: 100vh; 5 | background: #eeeeee; 6 | font-size: 30rpx; 7 | line-height: 60rpx; 8 | text-align: center; 9 | } 10 | .nav { 11 | height: 0; 12 | } 13 | 14 | .img { 15 | width: 100%; 16 | height: 500px; 17 | line-height: 500px; 18 | color: #fff; 19 | background: linear-gradient(280deg, #fff 0%, red 50%, blue 80%); 20 | } 21 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | // 获取应用实例 3 | // const app = getApp(); 4 | 5 | Page({ 6 | data: { 7 | loading: false, 8 | color: '#000', 9 | background: '#fff', 10 | show: true, 11 | animated: false 12 | }, 13 | 14 | onLoad() {}, 15 | search() { 16 | wx.navigateTo({ 17 | url: '/pages/demo1/index' 18 | }) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } 4 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 例子1: 点击跳转搜索页 6 | 7 | 8 | 例子2: 点击跳转左侧有返回 home的详情页面 9 | 10 | 11 | 例子3: 点击跳转详情页 12 | 13 | 14 | 例子4: 点击跳转详情页,深背景色,白色主题 15 | 16 | 17 | 例子5: 点击跳转,深背景色,白色主题有home 18 | 19 | 20 | 例子6: 点击跳转自定义左侧栏目带自定义搜索框 21 | 22 | 23 | 例子7: 点击跳转自定义左侧栏目带标题 24 | 25 | 26 | 例子8: 修改自定义样式和图标用法 27 | 28 | 29 | 例子9: 导航头部透明,不占据位置显示 30 | 31 | 32 | 例子10: 渐变色动态修改导航颜色 33 | 34 | 35 | -------------------------------------------------------------------------------- /tools/demo/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .lxy-nav-bar-search__input { 2 | display: block; 3 | flex: none; 4 | } 5 | -------------------------------------------------------------------------------- /tools/demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true, 12 | "coverView": true, 13 | "nodeModules": true, 14 | "autoAudits": false, 15 | "checkInvalidKey": true, 16 | "checkSiteMap": true, 17 | "uploadWithSourceMap": true, 18 | "babelSetting": { 19 | "ignore": [], 20 | "disablePlugins": [], 21 | "outputPath": "" 22 | } 23 | }, 24 | "compileType": "miniprogram", 25 | "libVersion": "2.7.7", 26 | "appid": "wx76fe9d97c50feaa5", 27 | "projectname": "navigationBar", 28 | "debugOptions": { 29 | "hidedInDevtools": [] 30 | }, 31 | "isGameTourist": false, 32 | "simulatorType": "wechat", 33 | "simulatorPluginLibVersion": {}, 34 | "condition": { 35 | "search": { 36 | "current": -1, 37 | "list": [] 38 | }, 39 | "conversation": { 40 | "current": -1, 41 | "list": [] 42 | }, 43 | "plugin": { 44 | "current": -1, 45 | "list": [] 46 | }, 47 | "game": { 48 | "currentL": -1, 49 | "list": [] 50 | }, 51 | "miniprogram": { 52 | "current": 1, 53 | "list": [ 54 | { 55 | "id": -1, 56 | "name": "pages/demo8/index", 57 | "pathName": "pages/demo8/index", 58 | "query": "", 59 | "scene": null 60 | }, 61 | { 62 | "id": -1, 63 | "name": "pages/demo1/index", 64 | "pathName": "pages/demo1/index", 65 | "scene": null 66 | } 67 | ] 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tools/test/helper.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const jComponent = require('j-component') 4 | 5 | const config = require('../config') 6 | const _ = require('../utils') 7 | 8 | const srcPath = config.srcPath 9 | const componentMap = {} 10 | let nowLoad = null 11 | 12 | /** 13 | * register custom component 14 | */ 15 | global.Component = options => { 16 | const component = nowLoad 17 | const definition = Object.assign({ 18 | template: component.wxml, 19 | usingComponents: component.json.usingComponents, 20 | tagName: component.tagName, 21 | }, options) 22 | 23 | component.id = jComponent.register(definition) 24 | } 25 | 26 | /** 27 | * register behavior 28 | */ 29 | global.Behavior = options => jComponent.behavior(options) 30 | 31 | /** 32 | * register global components 33 | */ 34 | // eslint-disable-next-line semi-style 35 | ;[ 36 | 'view', 'scroll-view', 'swiper', 'movable-view', 'cover-view', 'cover-view', 37 | 'icon', 'text', 'rich-text', 'progress', 38 | 'button', 'checkbox', 'form', 'input', 'label', 'picker', 'picker', 'picker-view', 'radio', 'slider', 'switch', 'textarea', 39 | 'navigator', 'function-page-navigator', 40 | 'audio', 'image', 'video', 'camera', 'live-player', 'live-pusher', 41 | 'map', 42 | 'canvas', 43 | 'open-data', 'web-view', 'ad' 44 | ].forEach(name => { 45 | jComponent.register({ 46 | id: name, 47 | tagName: `wx-${name}`, 48 | template: '' 49 | }) 50 | }) 51 | 52 | /** 53 | * Touch polyfill 54 | */ 55 | class Touch { 56 | constructor(options = {}) { 57 | this.clientX = 0 58 | this.clientY = 0 59 | this.identifier = 0 60 | this.pageX = 0 61 | this.pageY = 0 62 | this.screenX = 0 63 | this.screenY = 0 64 | this.target = null 65 | 66 | Object.keys(options).forEach(key => { 67 | this[key] = options[key] 68 | }) 69 | } 70 | } 71 | global.Touch = window.Touch = Touch 72 | 73 | /** 74 | * load component 75 | */ 76 | async function load(componentPath, tagName) { 77 | if (typeof componentPath === 'object') { 78 | const definition = componentPath 79 | 80 | return jComponent.register(definition) 81 | } 82 | 83 | const wholePath = path.join(srcPath, componentPath) 84 | 85 | const oldLoad = nowLoad 86 | const component = nowLoad = {} 87 | 88 | component.tagName = tagName 89 | component.wxml = await _.readFile(`${wholePath}.wxml`) 90 | component.wxss = await _.readFile(`${wholePath}.wxss`) 91 | component.json = _.readJson(`${wholePath}.json`) 92 | 93 | if (!component.json) { 94 | throw new Error(`invalid component: ${wholePath}`) 95 | } 96 | 97 | // preload using components 98 | const usingComponents = component.json.usingComponents || {} 99 | const usingComponentKeys = Object.keys(usingComponents) 100 | for (let i = 0, len = usingComponentKeys.length; i < len; i++) { 101 | const key = usingComponentKeys[i] 102 | const usingPath = path.join(path.dirname(componentPath), usingComponents[key]) 103 | // eslint-disable-next-line no-await-in-loop 104 | const id = await load(usingPath) 105 | 106 | usingComponents[key] = id 107 | } 108 | 109 | // require js 110 | // eslint-disable-next-line import/no-dynamic-require 111 | require(wholePath) 112 | 113 | nowLoad = oldLoad 114 | componentMap[wholePath] = component 115 | 116 | return component.id 117 | } 118 | 119 | /** 120 | * render component 121 | */ 122 | function render(componentId, properties) { 123 | if (!componentId) throw new Error('you need to pass the componentId') 124 | 125 | return jComponent.create(componentId, properties) 126 | } 127 | 128 | /** 129 | * test a dom is similar to the html 130 | */ 131 | function match(dom, html) { 132 | if (!(dom instanceof window.Element) || !html || typeof html !== 'string') return false 133 | 134 | // remove some 135 | html = html.trim() 136 | .replace(/(>)[\n\r\s\t]+(<)/g, '$1$2') 137 | 138 | const a = dom.cloneNode() 139 | const b = dom.cloneNode() 140 | 141 | a.innerHTML = dom.innerHTML 142 | b.innerHTML = html 143 | 144 | return a.isEqualNode(b) 145 | } 146 | 147 | /** 148 | * wait for some time 149 | */ 150 | function sleep(time = 0) { 151 | return new Promise(resolve => { 152 | setTimeout(() => { 153 | resolve() 154 | }, time) 155 | }) 156 | } 157 | 158 | module.exports = { 159 | load, 160 | render, 161 | match, 162 | sleep, 163 | } 164 | -------------------------------------------------------------------------------- /tools/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | // eslint-disable-next-line no-unused-vars 5 | const colors = require('colors') 6 | const through = require('through2') 7 | 8 | /** 9 | * async function wrapper 10 | */ 11 | function wrap(func, scope) { 12 | return function (...args) { 13 | if (args.length) { 14 | const temp = args.pop() 15 | if (typeof temp !== 'function') { 16 | args.push(temp) 17 | } 18 | } 19 | 20 | return new Promise(function (resolve, reject) { 21 | args.push(function (err, data) { 22 | if (err) reject(err) 23 | else resolve(data) 24 | }) 25 | 26 | func.apply((scope || null), args) 27 | }) 28 | } 29 | } 30 | 31 | const accessSync = wrap(fs.access) 32 | const statSync = wrap(fs.stat) 33 | const renameSync = wrap(fs.rename) 34 | const mkdirSync = wrap(fs.mkdir) 35 | const readFileSync = wrap(fs.readFile) 36 | const writeFileSync = wrap(fs.writeFile) 37 | 38 | /** 39 | * transform path segment separator 40 | */ 41 | function transformPath(filePath, sep = '/') { 42 | return filePath.replace(/[\\/]/g, sep) 43 | } 44 | 45 | /** 46 | * check file exists 47 | */ 48 | async function checkFileExists(filePath) { 49 | try { 50 | await accessSync(filePath) 51 | return true 52 | } catch (err) { 53 | return false 54 | } 55 | } 56 | 57 | /** 58 | * create folder 59 | */ 60 | async function recursiveMkdir(dirPath) { 61 | const prevDirPath = path.dirname(dirPath) 62 | try { 63 | await accessSync(prevDirPath) 64 | } catch (err) { 65 | // prevDirPath is not exist 66 | await recursiveMkdir(prevDirPath) 67 | } 68 | 69 | try { 70 | await accessSync(dirPath) 71 | 72 | const stat = await statSync(dirPath) 73 | if (stat && !stat.isDirectory()) { 74 | // dirPath already exists but is not a directory 75 | await renameSync(dirPath, `${dirPath}.bak`) // rename to a file with the suffix ending in '.bak' 76 | await mkdirSync(dirPath) 77 | } 78 | } catch (err) { 79 | // dirPath is not exist 80 | await mkdirSync(dirPath) 81 | } 82 | } 83 | 84 | /** 85 | * read json 86 | */ 87 | function readJson(filePath) { 88 | try { 89 | // eslint-disable-next-line import/no-dynamic-require 90 | const content = require(filePath) 91 | delete require.cache[require.resolve(filePath)] 92 | return content 93 | } catch (err) { 94 | return null 95 | } 96 | } 97 | 98 | /** 99 | * read file 100 | */ 101 | async function readFile(filePath) { 102 | try { 103 | return await readFileSync(filePath, 'utf8') 104 | } catch (err) { 105 | // eslint-disable-next-line no-console 106 | return console.error(err) 107 | } 108 | } 109 | 110 | /** 111 | * write file 112 | */ 113 | async function writeFile(filePath, data) { 114 | try { 115 | await recursiveMkdir(path.dirname(filePath)) 116 | return await writeFileSync(filePath, data, 'utf8') 117 | } catch (err) { 118 | // eslint-disable-next-line no-console 119 | return console.error(err) 120 | } 121 | } 122 | 123 | /** 124 | * time format 125 | */ 126 | function format(time, reg) { 127 | const date = typeof time === 'string' ? new Date(time) : time 128 | const map = {} 129 | map.yyyy = date.getFullYear() 130 | map.yy = ('' + map.yyyy).substr(2) 131 | map.M = date.getMonth() + 1 132 | map.MM = (map.M < 10 ? '0' : '') + map.M 133 | map.d = date.getDate() 134 | map.dd = (map.d < 10 ? '0' : '') + map.d 135 | map.H = date.getHours() 136 | map.HH = (map.H < 10 ? '0' : '') + map.H 137 | map.m = date.getMinutes() 138 | map.mm = (map.m < 10 ? '0' : '') + map.m 139 | map.s = date.getSeconds() 140 | map.ss = (map.s < 10 ? '0' : '') + map.s 141 | 142 | return reg.replace(/\byyyy|yy|MM|M|dd|d|HH|H|mm|m|ss|s\b/g, $1 => map[$1]) 143 | } 144 | 145 | /** 146 | * logger plugin 147 | */ 148 | function logger(action = 'copy') { 149 | return through.obj(function (file, enc, cb) { 150 | const type = path.extname(file.path).slice(1).toLowerCase() 151 | 152 | // eslint-disable-next-line no-console 153 | console.log(`[${format(new Date(), 'yyyy-MM-dd HH:mm:ss').grey}] [${action.green} ${type.green}] ${'=>'.cyan} ${file.path}`) 154 | 155 | this.push(file) 156 | cb() 157 | }) 158 | } 159 | 160 | /** 161 | * compare arrays 162 | */ 163 | function compareArray(arr1, arr2) { 164 | if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false 165 | if (arr1.length !== arr2.length) return false 166 | 167 | for (let i = 0, len = arr1.length; i < len; i++) { 168 | if (arr1[i] !== arr2[i]) return false 169 | } 170 | 171 | return true 172 | } 173 | 174 | /** 175 | * merge two object 176 | */ 177 | function merge(obj1, obj2) { 178 | Object.keys(obj2).forEach(key => { 179 | if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { 180 | obj1[key] = obj1[key].concat(obj2[key]) 181 | } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') { 182 | obj1[key] = Object.assign(obj1[key], obj2[key]) 183 | } else { 184 | obj1[key] = obj2[key] 185 | } 186 | }) 187 | 188 | return obj1 189 | } 190 | 191 | /** 192 | * get random id 193 | */ 194 | let seed = +new Date() 195 | function getId() { 196 | return ++seed 197 | } 198 | 199 | module.exports = { 200 | wrap, 201 | transformPath, 202 | 203 | checkFileExists, 204 | readJson, 205 | readFile, 206 | writeFile, 207 | 208 | logger, 209 | format, 210 | compareArray, 211 | merge, 212 | getId, 213 | } 214 | --------------------------------------------------------------------------------