├── .gitignore ├── .hbuilderx └── launch.json ├── App.vue ├── README.md ├── api ├── index.js ├── login.js └── mock.js ├── common ├── common.scss ├── font │ ├── demo.css │ ├── demo_index.html │ ├── iconfont.css │ ├── iconfont.eot │ ├── iconfont.js │ ├── iconfont.json │ ├── iconfont.svg │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.woff2 ├── home.scss ├── index.scss ├── login.scss └── password.scss ├── components ├── neil-modal │ ├── neil-modal.vue │ └── readme.md ├── uni-countdown │ └── uni-countdown.vue └── uni-popup │ └── uni-popup.vue ├── lang ├── en.js ├── index.js ├── vue-i18n.js └── zh.js ├── main.js ├── manifest.json ├── package-lock.json ├── package.json ├── pages.json ├── pages └── login │ └── login.vue ├── static ├── 1.gif ├── images │ ├── bg_circle.png │ ├── bg_login.png │ ├── big_btn.png │ ├── bottom_btn.png │ ├── bottom_btn_tap.png │ ├── circel_btn.png │ ├── circel_btn_tap.png │ ├── left-tap.png │ ├── left.png │ ├── left_btn.png │ ├── left_btn_tap.png │ ├── logo │ │ ├── LOGO.png │ │ └── LOGO1.png │ ├── right-tap.png │ ├── right.png │ ├── right_btn.png │ ├── right_btn_tap.png │ ├── tap.png │ ├── top_btn.png │ └── top_btn_tap.png └── neil-modal │ └── logo.png ├── uni.scss └── utils ├── request ├── index.js ├── readme.md └── request.js ├── service.js ├── socket └── BLEConn.js └── storageTypes.js /.gitignore: -------------------------------------------------------------------------------- 1 | unpackage 2 | .idea 3 | node_modules -------------------------------------------------------------------------------- /.hbuilderx/launch.json: -------------------------------------------------------------------------------- 1 | { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ 2 | // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 3 | "version": "0.0", 4 | "configurations": [{ 5 | "default" : 6 | { 7 | "launchtype" : "local" 8 | }, 9 | "mp-weixin" : 10 | { 11 | "launchtype" : "local" 12 | }, 13 | "type" : "uniCloud" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 9 | # 启动 10 | 11 | 把项目下载后放到Hbuilder中 用微信小程序预览即可打开 12 | 13 | # 预览 14 | 15 | 16 | ![image](https://github.com/menglin1997/BLEConn/blob/master/static/1.gif) 17 | 18 | 19 | # 项目介绍 20 | 21 | > 本项目是由uniapp开发的微信小程序连接低功耗蓝牙 22 | > 23 | > 主要功能有搜索低功耗蓝牙,连接低功耗蓝牙,给蓝牙发送命令,接收蓝牙回复的命令 24 | 25 | # 功能介绍以及使用说明 26 | 27 | > 用到的文件有`pages/login/login`, `utils/socket/BLEConn.js`两个文件 28 | > 29 | > `utils/socket/BLEConn.js`对蓝牙操作的封装(包含蓝牙列表搜索,蓝牙连接,蓝牙断开,蓝牙分包发送命令,蓝牙分包接收命令) 30 | > 31 | > `pages/login/login`对蓝牙的连接操作 32 | 33 | ## BLEConn.js方法说明 34 | > getBlooth 搜索蓝牙列表 35 | > createBLE 连接蓝牙 36 | > getCharacteristics 获取某个服务的特征值这里做了判断 有notify功能才会返回正确 可根据项目需要修改返回状态 37 | > writeBLE 写入命令-即给蓝牙发送命令(项目中是分包操作),如果不需要分包可参考hexToArrayBuffer方法转换命令 38 | > watchNotify 如果你项目里面有notify功能 这里会主动收到消息 39 | 40 | ## `pages/login/login`业务逻辑 41 | 42 | 1. 点击按钮调用BLEConn封装的方法进行搜索蓝牙 43 | 2. 搜索到蓝牙之后点击需要连接的蓝牙进行连接 44 | 3. 连接时调用createBLE方法 ,该方法会传入两个参数,一个是需要连接的蓝牙对象,另一个是发送命令时候的服务的uuid(此uuid可以问给蓝牙协议的人) 45 | 4. 连接不上会进行三次重连操作 ,三次都没有连接上提示连接失败 46 | 47 | # `utils/socket/BLEConn.js`业务逻辑 48 | 49 | 1. 初始化蓝牙 50 | 2. 搜索蓝牙列表,将搜索出的name值为空或者’未知设备‘并且包含有localName的蓝牙设备的name重新命名为localName的值,将搜索出来的deviceId重复的只保存一个 51 | 52 | # 分包接收访问下边的链接 53 | 54 | https://juejin.im/post/5e1b0bf25188252c4f2ba2bd 55 | 56 | # 分包发送访问下边的链接 57 | 58 | https://juejin.im/post/5e1b0c85e51d45588849581f 59 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/api/index.js -------------------------------------------------------------------------------- /api/login.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/api/login.js -------------------------------------------------------------------------------- /api/mock.js: -------------------------------------------------------------------------------- 1 | export default data = { 2 | 3 | } -------------------------------------------------------------------------------- /common/common.scss: -------------------------------------------------------------------------------- 1 | page { 2 | font-size: 30rpx; 3 | font-family: '思源黑体', 'Segoe UI', 'Lucida Grande', Helvetica, Arial, 'Microsoft YaHei'; 4 | color: #3a3a3a; 5 | width: 100vw; 6 | height: 100vh; 7 | background: #1b294b; 8 | } 9 | 10 | page, 11 | view, 12 | text, 13 | image, 14 | scroll-view, 15 | swiper, 16 | swiper-item { 17 | box-sizing: border-box; 18 | } 19 | 20 | .am-clear-native-style { 21 | /* 清除原生默认样式 */ 22 | background: transparent; 23 | border: none; 24 | margin: 0; 25 | padding: 0; 26 | &:after { 27 | border: none; 28 | } 29 | } 30 | // 超出出现省略号 param {Number} $value - 行数,默认2 31 | @mixin amOverLineClamp($value: 2) { 32 | text-overflow: -o-ellipsis-lastline; 33 | overflow: hidden; 34 | text-overflow: ellipsis; 35 | display: -webkit-box; 36 | -webkit-line-clamp: $value; 37 | line-clamp: $value; 38 | -webkit-box-orient: vertical; 39 | } 40 | // 超出两行出现省略号 41 | .am-line-clamp-two { 42 | @include amOverLineClamp(); 43 | } 44 | 45 | .am-text-eill { 46 | /*超出省略号*/ 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | } 51 | 52 | .am-text-break { 53 | /*换行*/ 54 | white-space: normal; 55 | word-break: break-all; 56 | } 57 | 58 | .am-align-center { 59 | /*文本居中*/ 60 | text-align: center; 61 | } 62 | 63 | .am-block { 64 | display: block; 65 | } 66 | 67 | .am-inline { 68 | display: inline; 69 | } 70 | 71 | .am-inline-block { 72 | display: inline-block; 73 | } -------------------------------------------------------------------------------- /common/font/demo.css: -------------------------------------------------------------------------------- 1 | /* Logo 字体 */ 2 | @font-face { 3 | font-family: "iconfont logo"; 4 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); 5 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), 6 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), 7 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), 8 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); 9 | } 10 | 11 | .logo { 12 | font-family: "iconfont logo"; 13 | font-size: 160px; 14 | font-style: normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | /* tabs */ 20 | .nav-tabs { 21 | position: relative; 22 | } 23 | 24 | .nav-tabs .nav-more { 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 42px; 29 | line-height: 42px; 30 | color: #666; 31 | } 32 | 33 | #tabs { 34 | border-bottom: 1px solid #eee; 35 | } 36 | 37 | #tabs li { 38 | cursor: pointer; 39 | width: 100px; 40 | height: 40px; 41 | line-height: 40px; 42 | text-align: center; 43 | font-size: 16px; 44 | border-bottom: 2px solid transparent; 45 | position: relative; 46 | z-index: 1; 47 | margin-bottom: -1px; 48 | color: #666; 49 | } 50 | 51 | 52 | #tabs .active { 53 | border-bottom-color: #f00; 54 | color: #222; 55 | } 56 | 57 | .tab-container .content { 58 | display: none; 59 | } 60 | 61 | /* 页面布局 */ 62 | .main { 63 | padding: 30px 100px; 64 | width: 960px; 65 | margin: 0 auto; 66 | } 67 | 68 | .main .logo { 69 | color: #333; 70 | text-align: left; 71 | margin-bottom: 30px; 72 | line-height: 1; 73 | height: 110px; 74 | margin-top: -50px; 75 | overflow: hidden; 76 | *zoom: 1; 77 | } 78 | 79 | .main .logo a { 80 | font-size: 160px; 81 | color: #333; 82 | } 83 | 84 | .helps { 85 | margin-top: 40px; 86 | } 87 | 88 | .helps pre { 89 | padding: 20px; 90 | margin: 10px 0; 91 | border: solid 1px #e7e1cd; 92 | background-color: #fffdef; 93 | overflow: auto; 94 | } 95 | 96 | .icon_lists { 97 | width: 100% !important; 98 | overflow: hidden; 99 | *zoom: 1; 100 | } 101 | 102 | .icon_lists li { 103 | width: 100px; 104 | margin-bottom: 10px; 105 | margin-right: 20px; 106 | text-align: center; 107 | list-style: none !important; 108 | cursor: default; 109 | } 110 | 111 | .icon_lists li .code-name { 112 | line-height: 1.2; 113 | } 114 | 115 | .icon_lists .icon { 116 | display: block; 117 | height: 100px; 118 | line-height: 100px; 119 | font-size: 42px; 120 | margin: 10px auto; 121 | color: #333; 122 | -webkit-transition: font-size 0.25s linear, width 0.25s linear; 123 | -moz-transition: font-size 0.25s linear, width 0.25s linear; 124 | transition: font-size 0.25s linear, width 0.25s linear; 125 | } 126 | 127 | .icon_lists .icon:hover { 128 | font-size: 100px; 129 | } 130 | 131 | .icon_lists .svg-icon { 132 | /* 通过设置 font-size 来改变图标大小 */ 133 | width: 1em; 134 | /* 图标和文字相邻时,垂直对齐 */ 135 | vertical-align: -0.15em; 136 | /* 通过设置 color 来改变 SVG 的颜色/fill */ 137 | fill: currentColor; 138 | /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 139 | normalize.css 中也包含这行 */ 140 | overflow: hidden; 141 | } 142 | 143 | .icon_lists li .name, 144 | .icon_lists li .code-name { 145 | color: #666; 146 | } 147 | 148 | /* markdown 样式 */ 149 | .markdown { 150 | color: #666; 151 | font-size: 14px; 152 | line-height: 1.8; 153 | } 154 | 155 | .highlight { 156 | line-height: 1.5; 157 | } 158 | 159 | .markdown img { 160 | vertical-align: middle; 161 | max-width: 100%; 162 | } 163 | 164 | .markdown h1 { 165 | color: #404040; 166 | font-weight: 500; 167 | line-height: 40px; 168 | margin-bottom: 24px; 169 | } 170 | 171 | .markdown h2, 172 | .markdown h3, 173 | .markdown h4, 174 | .markdown h5, 175 | .markdown h6 { 176 | color: #404040; 177 | margin: 1.6em 0 0.6em 0; 178 | font-weight: 500; 179 | clear: both; 180 | } 181 | 182 | .markdown h1 { 183 | font-size: 28px; 184 | } 185 | 186 | .markdown h2 { 187 | font-size: 22px; 188 | } 189 | 190 | .markdown h3 { 191 | font-size: 16px; 192 | } 193 | 194 | .markdown h4 { 195 | font-size: 14px; 196 | } 197 | 198 | .markdown h5 { 199 | font-size: 12px; 200 | } 201 | 202 | .markdown h6 { 203 | font-size: 12px; 204 | } 205 | 206 | .markdown hr { 207 | height: 1px; 208 | border: 0; 209 | background: #e9e9e9; 210 | margin: 16px 0; 211 | clear: both; 212 | } 213 | 214 | .markdown p { 215 | margin: 1em 0; 216 | } 217 | 218 | .markdown>p, 219 | .markdown>blockquote, 220 | .markdown>.highlight, 221 | .markdown>ol, 222 | .markdown>ul { 223 | width: 80%; 224 | } 225 | 226 | .markdown ul>li { 227 | list-style: circle; 228 | } 229 | 230 | .markdown>ul li, 231 | .markdown blockquote ul>li { 232 | margin-left: 20px; 233 | padding-left: 4px; 234 | } 235 | 236 | .markdown>ul li p, 237 | .markdown>ol li p { 238 | margin: 0.6em 0; 239 | } 240 | 241 | .markdown ol>li { 242 | list-style: decimal; 243 | } 244 | 245 | .markdown>ol li, 246 | .markdown blockquote ol>li { 247 | margin-left: 20px; 248 | padding-left: 4px; 249 | } 250 | 251 | .markdown code { 252 | margin: 0 3px; 253 | padding: 0 5px; 254 | background: #eee; 255 | border-radius: 3px; 256 | } 257 | 258 | .markdown strong, 259 | .markdown b { 260 | font-weight: 600; 261 | } 262 | 263 | .markdown>table { 264 | border-collapse: collapse; 265 | border-spacing: 0px; 266 | empty-cells: show; 267 | border: 1px solid #e9e9e9; 268 | width: 95%; 269 | margin-bottom: 24px; 270 | } 271 | 272 | .markdown>table th { 273 | white-space: nowrap; 274 | color: #333; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown>table th, 279 | .markdown>table td { 280 | border: 1px solid #e9e9e9; 281 | padding: 8px 16px; 282 | text-align: left; 283 | } 284 | 285 | .markdown>table th { 286 | background: #F7F7F7; 287 | } 288 | 289 | .markdown blockquote { 290 | font-size: 90%; 291 | color: #999; 292 | border-left: 4px solid #e9e9e9; 293 | padding-left: 0.8em; 294 | margin: 1em 0; 295 | } 296 | 297 | .markdown blockquote p { 298 | margin: 0; 299 | } 300 | 301 | .markdown .anchor { 302 | opacity: 0; 303 | transition: opacity 0.3s ease; 304 | margin-left: 8px; 305 | } 306 | 307 | .markdown .waiting { 308 | color: #ccc; 309 | } 310 | 311 | .markdown h1:hover .anchor, 312 | .markdown h2:hover .anchor, 313 | .markdown h3:hover .anchor, 314 | .markdown h4:hover .anchor, 315 | .markdown h5:hover .anchor, 316 | .markdown h6:hover .anchor { 317 | opacity: 1; 318 | display: inline-block; 319 | } 320 | 321 | .markdown>br, 322 | .markdown>p>br { 323 | clear: both; 324 | } 325 | 326 | 327 | .hljs { 328 | display: block; 329 | background: white; 330 | padding: 0.5em; 331 | color: #333333; 332 | overflow-x: auto; 333 | } 334 | 335 | .hljs-comment, 336 | .hljs-meta { 337 | color: #969896; 338 | } 339 | 340 | .hljs-string, 341 | .hljs-variable, 342 | .hljs-template-variable, 343 | .hljs-strong, 344 | .hljs-emphasis, 345 | .hljs-quote { 346 | color: #df5000; 347 | } 348 | 349 | .hljs-keyword, 350 | .hljs-selector-tag, 351 | .hljs-type { 352 | color: #a71d5d; 353 | } 354 | 355 | .hljs-literal, 356 | .hljs-symbol, 357 | .hljs-bullet, 358 | .hljs-attribute { 359 | color: #0086b3; 360 | } 361 | 362 | .hljs-section, 363 | .hljs-name { 364 | color: #63a35c; 365 | } 366 | 367 | .hljs-tag { 368 | color: #333333; 369 | } 370 | 371 | .hljs-title, 372 | .hljs-attr, 373 | .hljs-selector-id, 374 | .hljs-selector-class, 375 | .hljs-selector-attr, 376 | .hljs-selector-pseudo { 377 | color: #795da3; 378 | } 379 | 380 | .hljs-addition { 381 | color: #55a532; 382 | background-color: #eaffea; 383 | } 384 | 385 | .hljs-deletion { 386 | color: #bd2c00; 387 | background-color: #ffecec; 388 | } 389 | 390 | .hljs-link { 391 | text-decoration: underline; 392 | } 393 | 394 | /* 代码高亮 */ 395 | /* PrismJS 1.15.0 396 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ 397 | /** 398 | * prism.js default theme for JavaScript, CSS and HTML 399 | * Based on dabblet (http://dabblet.com) 400 | * @author Lea Verou 401 | */ 402 | code[class*="language-"], 403 | pre[class*="language-"] { 404 | color: black; 405 | background: none; 406 | text-shadow: 0 1px white; 407 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 408 | text-align: left; 409 | white-space: pre; 410 | word-spacing: normal; 411 | word-break: normal; 412 | word-wrap: normal; 413 | line-height: 1.5; 414 | 415 | -moz-tab-size: 4; 416 | -o-tab-size: 4; 417 | tab-size: 4; 418 | 419 | -webkit-hyphens: none; 420 | -moz-hyphens: none; 421 | -ms-hyphens: none; 422 | hyphens: none; 423 | } 424 | 425 | pre[class*="language-"]::-moz-selection, 426 | pre[class*="language-"] ::-moz-selection, 427 | code[class*="language-"]::-moz-selection, 428 | code[class*="language-"] ::-moz-selection { 429 | text-shadow: none; 430 | background: #b3d4fc; 431 | } 432 | 433 | pre[class*="language-"]::selection, 434 | pre[class*="language-"] ::selection, 435 | code[class*="language-"]::selection, 436 | code[class*="language-"] ::selection { 437 | text-shadow: none; 438 | background: #b3d4fc; 439 | } 440 | 441 | @media print { 442 | 443 | code[class*="language-"], 444 | pre[class*="language-"] { 445 | text-shadow: none; 446 | } 447 | } 448 | 449 | /* Code blocks */ 450 | pre[class*="language-"] { 451 | padding: 1em; 452 | margin: .5em 0; 453 | overflow: auto; 454 | } 455 | 456 | :not(pre)>code[class*="language-"], 457 | pre[class*="language-"] { 458 | background: #f5f2f0; 459 | } 460 | 461 | /* Inline code */ 462 | :not(pre)>code[class*="language-"] { 463 | padding: .1em; 464 | border-radius: .3em; 465 | white-space: normal; 466 | } 467 | 468 | .token.comment, 469 | .token.prolog, 470 | .token.doctype, 471 | .token.cdata { 472 | color: slategray; 473 | } 474 | 475 | .token.punctuation { 476 | color: #999; 477 | } 478 | 479 | .namespace { 480 | opacity: .7; 481 | } 482 | 483 | .token.property, 484 | .token.tag, 485 | .token.boolean, 486 | .token.number, 487 | .token.constant, 488 | .token.symbol, 489 | .token.deleted { 490 | color: #905; 491 | } 492 | 493 | .token.selector, 494 | .token.attr-name, 495 | .token.string, 496 | .token.char, 497 | .token.builtin, 498 | .token.inserted { 499 | color: #690; 500 | } 501 | 502 | .token.operator, 503 | .token.entity, 504 | .token.url, 505 | .language-css .token.string, 506 | .style .token.string { 507 | color: #9a6e3a; 508 | background: hsla(0, 0%, 100%, .5); 509 | } 510 | 511 | .token.atrule, 512 | .token.attr-value, 513 | .token.keyword { 514 | color: #07a; 515 | } 516 | 517 | .token.function, 518 | .token.class-name { 519 | color: #DD4A68; 520 | } 521 | 522 | .token.regex, 523 | .token.important, 524 | .token.variable { 525 | color: #e90; 526 | } 527 | 528 | .token.important, 529 | .token.bold { 530 | font-weight: bold; 531 | } 532 | 533 | .token.italic { 534 | font-style: italic; 535 | } 536 | 537 | .token.entity { 538 | cursor: help; 539 | } 540 | -------------------------------------------------------------------------------- /common/font/demo_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IconFont Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 27 |
28 |
29 |
    30 | 31 |
  • 32 | 33 |
    箭头
    34 |
    
    35 |
  • 36 | 37 |
  • 38 | 39 |
    输出
    40 |
    
    41 |
  • 42 | 43 |
  • 44 | 45 |
    箭头
    46 |
    
    47 |
  • 48 | 49 |
50 |
51 |

Unicode 引用

52 |
53 | 54 |

Unicode 是字体在网页端最原始的应用方式,特点是:

55 |
    56 |
  • 兼容性最好,支持 IE6+,及所有现代浏览器。
  • 57 |
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • 58 |
  • 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。
  • 59 |
60 |
61 |

注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式

62 |
63 |

Unicode 使用步骤如下:

64 |

第一步:拷贝项目下面生成的 @font-face

65 |
@font-face {
 67 |   font-family: 'iconfont';
 68 |   src: url('iconfont.eot');
 69 |   src: url('iconfont.eot?#iefix') format('embedded-opentype'),
 70 |       url('iconfont.woff2') format('woff2'),
 71 |       url('iconfont.woff') format('woff'),
 72 |       url('iconfont.ttf') format('truetype'),
 73 |       url('iconfont.svg#iconfont') format('svg');
 74 | }
 75 | 
76 |

第二步:定义使用 iconfont 的样式

77 |
.iconfont {
 79 |   font-family: "iconfont" !important;
 80 |   font-size: 16px;
 81 |   font-style: normal;
 82 |   -webkit-font-smoothing: antialiased;
 83 |   -moz-osx-font-smoothing: grayscale;
 84 | }
 85 | 
86 |

第三步:挑选相应图标并获取字体编码,应用于页面

87 |
 88 | <span class="iconfont">&#x33;</span>
 90 | 
91 |
92 |

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

93 |
94 |
95 |
96 |
97 |
    98 | 99 |
  • 100 | 101 |
    102 | 箭头 103 |
    104 |
    .icon-ai36 105 |
    106 |
  • 107 | 108 |
  • 109 | 110 |
    111 | 输出 112 |
    113 |
    .icon-shuchu 114 |
    115 |
  • 116 | 117 |
  • 118 | 119 |
    120 | 箭头 121 |
    122 |
    .icon-ai36-copy 123 |
    124 |
  • 125 | 126 |
127 |
128 |

font-class 引用

129 |
130 | 131 |

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

132 |

与 Unicode 使用方式相比,具有如下特点:

133 |
    134 |
  • 兼容性良好,支持 IE8+,及所有现代浏览器。
  • 135 |
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • 136 |
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • 137 |
  • 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
  • 138 |
139 |

使用步骤如下:

140 |

第一步:引入项目下面生成的 fontclass 代码:

141 |
<link rel="stylesheet" href="./iconfont.css">
142 | 
143 |

第二步:挑选相应图标并获取类名,应用于页面:

144 |
<span class="iconfont icon-xxx"></span>
145 | 
146 |
147 |

" 148 | iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

149 |
150 |
151 |
152 |
153 |
    154 | 155 |
  • 156 | 159 |
    箭头
    160 |
    #icon-ai36
    161 |
  • 162 | 163 |
  • 164 | 167 |
    输出
    168 |
    #icon-shuchu
    169 |
  • 170 | 171 |
  • 172 | 175 |
    箭头
    176 |
    #icon-ai36-copy
    177 |
  • 178 | 179 |
180 |
181 |

Symbol 引用

182 |
183 | 184 |

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 185 | 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

186 |
    187 |
  • 支持多色图标了,不再受单色限制。
  • 188 |
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • 189 |
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • 190 |
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • 191 |
192 |

使用步骤如下:

193 |

第一步:引入项目下面生成的 symbol 代码:

194 |
<script src="./iconfont.js"></script>
195 | 
196 |

第二步:加入通用 CSS 代码(引入一次就行):

197 |
<style>
198 | .icon {
199 |   width: 1em;
200 |   height: 1em;
201 |   vertical-align: -0.15em;
202 |   fill: currentColor;
203 |   overflow: hidden;
204 | }
205 | </style>
206 | 
207 |

第三步:挑选相应图标并获取类名,应用于页面:

208 |
<svg class="icon" aria-hidden="true">
209 |   <use xlink:href="#icon-xxx"></use>
210 | </svg>
211 | 
212 |
213 |
214 | 215 |
216 |
217 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /common/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1573099033531'); /* IE9 */ 3 | src: url('iconfont.eot?t=1573099033531#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAANEAAsAAAAAB3QAAAL4AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqCNIIaATYCJAMQCwoABCAFhG0HQxuHBhHVmyfIfh7YjqkTjqGcizyUo6tRDB0T/OfgefTv69wkD1+YyQpAJRSN0cHdljtva84BGxVhhqBbP9KlPBXyl08HAZAEQ/gHx4wXKZRns7lkUGXSACc40Kj8KOF/kH9B/kF3EZMiL22HAGGKUAlp1aZTL3wMoJ8AMmn82OH4FTsGE4R8BLdjqkG24eCr9eohHbbyn5dfbMQHhaMBQ7uNaT2KZl/tN6uXBcsYRJUs304LBLNAA5UAAzKuUz8CLf2V0ISzvzHUAXx8FHy1X/d/s0FgK+HCbPgvDxSCBnEByVEAajkBDV8tpkPB1/10CHyzSWiAF9LbB1xgEDDNwRQSK2jxvOJF3a9Y2IcoMSbFJCq9ei88fR4OPa2VvP68iHf0Rd3UtWeFWXZuwwWRpJuS5RcHLt+Ta2f12bPh3LKc7lFKOh+Jddv1VBtt3712796FvSIqoiODdViFe/dt0qLomzcn5k+af6LAsQUTF5x489pcxO1p5+S2rRXef6zWsf1hHmNvV71ga4Ej7YUONU2kprtm67q/5f+UyZtd1N6udNsWnZ1X5n/ZvJaiNW7NCLmZUesSjxVgeUybf8ed9Bd1sJToZRDs04v16B/yOxTuP6ri0ESjf37EAPDux438nD+d+1a5Gff1lxkgCEjIBwpjUgHmFEZDs8VZCggTJpIDzGk+byHLmZwMgk+OBoVHYdD4lEIYTCVwiFALXHyaQpiKtJ0dIcMMNGJCQAVWWyAkuQqKOA9Ak+QRwmA+gUMuf8AlKRrC9JHMkhHKCGxnMaaCBWR/sDk1CqlkJlLvqNsqZZPPSW/kYF2wXqzqoRgb5C1GhI/eiChQTDVE1m5YVQSeyWEuCyPiL8ul6nqlRU51Z9aJMRUsIPuDzalRtLXMGt9/R91WKQ/UzBjfyMG2D+vFagIiljSTal7llvDRGxEFiqmGyOqEVUXguzs5zGVhFnj9ZQmbqamSxfGy+gGygwbAUrpKtBhxkNrtYRRMmyPxVMfOc/L/Tgc=') format('woff2'), 5 | url('iconfont.woff?t=1573099033531') format('woff'), 6 | url('iconfont.ttf?t=1573099033531') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1573099033531#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-ai36:before { 19 | content: "\e699"; 20 | } 21 | 22 | .icon-shuchu:before { 23 | content: "\e617"; 24 | } 25 | 26 | .icon-ai36-copy:before { 27 | content: "\e717"; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /common/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/common/font/iconfont.eot -------------------------------------------------------------------------------- /common/font/iconfont.js: -------------------------------------------------------------------------------- 1 | !function(d){var e,n='',t=(e=document.getElementsByTagName("script"))[e.length-1].getAttribute("data-injectcss");if(t&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(e){console&&console.log(e)}}!function(e){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(e,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),e()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(c=e,o=d.document,i=!1,(l=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(l,50)}n()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,n())});function n(){i||(i=!0,c())}var c,o,i,l}(function(){var e,t;(e=document.createElement("div")).innerHTML=n,n=null,(t=e.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",function(e,t){t.firstChild?function(e,t){t.parentNode.insertBefore(e,t)}(e,t.firstChild):t.appendChild(e)}(t,document.body))})}(window); -------------------------------------------------------------------------------- /common/font/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "", 3 | "name": "", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "44945910", 10 | "name": "箭头", 11 | "font_class": "ai36", 12 | "unicode": "e699", 13 | "unicode_decimal": 59033 14 | }, 15 | { 16 | "icon_id": "44945927", 17 | "name": "输出", 18 | "font_class": "shuchu", 19 | "unicode": "e617", 20 | "unicode_decimal": 58903 21 | }, 22 | { 23 | "icon_id": "45432605", 24 | "name": "箭头", 25 | "font_class": "ai36-copy", 26 | "unicode": "e717", 27 | "unicode_decimal": 59159 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /common/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /common/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/common/font/iconfont.ttf -------------------------------------------------------------------------------- /common/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/common/font/iconfont.woff -------------------------------------------------------------------------------- /common/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/common/font/iconfont.woff2 -------------------------------------------------------------------------------- /common/home.scss: -------------------------------------------------------------------------------- 1 | // 屏幕 2 | .screen { 3 | touch-action: none; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: space-between; 7 | // height:379rpx; 8 | height:350rpx; 9 | background:rgba(0,97,192,1); 10 | box-shadow:0rpx 8rpx 8rpx 0rpx rgba(1,90,176,0.66); 11 | border-radius:8rpx; 12 | margin: auto; 13 | color: #fff; 14 | font-size: 40rpx; 15 | font-weight: bold; 16 | padding: 22rpx; 17 | overflow: hidden; 18 | .line { 19 | display: flex; 20 | justify-content: space-between; 21 | } 22 | .center { 23 | display: flex; 24 | justify-content: center; 25 | } 26 | } 27 | // 居中的布局 28 | .screen_c { 29 | justify-content: center; 30 | } 31 | .margin_c { 32 | margin: 56rpx 0; 33 | } 34 | .en_l { 35 | margin-left: 30rpx; 36 | } 37 | .center { 38 | line-height: 45rpx; 39 | } 40 | .num { 41 | background: #000 !important; 42 | } 43 | .bg { 44 | background: #000 !important; 45 | // background: #0086B3; 46 | } -------------------------------------------------------------------------------- /common/index.scss: -------------------------------------------------------------------------------- 1 | 2 | .box { 3 | touch-action: none; 4 | } 5 | .content { 6 | touch-action: none; 7 | padding: 24rpx; 8 | height: 100%; 9 | padding-bottom: 0; 10 | padding-top: 15rpx; 11 | // padding-bottom: 5vh; 12 | 13 | 14 | 15 | /* // 按钮公共样式 */ 16 | .btns { 17 | padding-top: 26rpx; 18 | .btn { 19 | padding: 35rpx 0; 20 | width:160rpx; 21 | height:160rpx; 22 | background:linear-gradient(0deg,rgba(0,85,173,1) 0%,rgba(0,137,255,1) 100%); 23 | border-radius:50%; 24 | box-shadow:3rpx 5rpx 20rpx 0rpx rgba(0,93,187,0.48), 1rpx 1rpx 32rpx 0rpx rgba(0,93,182,0.64); 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | color: #fff; 29 | justify-content: space-around; 30 | border:7rpx solid linear-gradient(0deg,rgba(0,85,173,1) 0%,rgba(0,137,255,1) 100%); 31 | .s { 32 | font-size: 36rpx; 33 | text-shadow:0rpx 1rpx 1rpx rgba(60,70,91,0.75); 34 | } 35 | .x { 36 | font-size: 25rpx; 37 | text-shadow:0rpx 1rpx 1rpx rgba(60,70,91,0.75); 38 | } 39 | } 40 | /* // 按钮布局 */ 41 | .box { 42 | display: flex; 43 | justify-content: space-between; 44 | align-items: center; 45 | font-weight: bold; 46 | .none { 47 | background: none; 48 | box-shadow:none; 49 | border: none; 50 | } 51 | } 52 | /* // 中间大按钮 */ 53 | .center { 54 | justify-content: space-around; 55 | .left { 56 | width: 400rpx; 57 | height: 400rpx; 58 | background: none; 59 | box-shadow: none; 60 | background: url(../../static/images/big_btn.png) no-repeat; 61 | background-size: 100% 100%; 62 | position: relative; 63 | .center_btn { 64 | position: absolute; 65 | width:70rpx; 66 | height:70rpx; 67 | z-index: 1; 68 | } 69 | .top_btn { 70 | // top: 40rpx; 71 | top: 10rpx; 72 | padding-top: 10rpx; 73 | left: 50%; 74 | // width:51rpx; 75 | // height:31rpx; 76 | transform: translateX(-50%); 77 | background: url(../../static/images/top_btn.png) no-repeat center; 78 | background-size: 50rpx 30rpx; 79 | } 80 | .right_btn { 81 | top: 50%; 82 | // right: 40rpx; 83 | right: 10rpx; 84 | padding-right: 30rpx; 85 | // width: 31rpx; 86 | // height: 51rpx; 87 | transform: translateY(-50%); 88 | background: url(../../static/images/right_btn.png) no-repeat center; 89 | background-size: 30rpx 50rpx; 90 | } 91 | .left_btn { 92 | top: 50%; 93 | // left: 40rpx; 94 | padding-left: 30rpx; 95 | left: 10rpx; 96 | // width: 31rpx; 97 | // height: 51rpx; 98 | transform: translateY(-50%); 99 | background: url(../../static/images/left_btn.png) no-repeat center; 100 | background-size: 30rpx 50rpx; 101 | } 102 | .bottom_btn { 103 | // bottom: 40rpx; 104 | bottom: 10rpx; 105 | left: 50%; 106 | padding-bottom: 30rpx; 107 | // width:51rpx; 108 | // height:31rpx; 109 | transform: translateX(-50%); 110 | background: url(../../static/images/bottom_btn.png) no-repeat center; 111 | background-size: 50rpx 30rpx; 112 | } 113 | .circel_btn { 114 | position: absolute; 115 | top: 50%; 116 | left: 50%; 117 | width: 180rpx; 118 | height: 180rpx; 119 | transform: translate(-50%,-50%); 120 | background: url(../../static/images/circel_btn.png) no-repeat; 121 | background-size: 100% 100%; 122 | } 123 | 124 | /* // 点击特效 125 | // 中间大按钮特效 126 | // 上 */ 127 | .top_btn_hover { 128 | background: url(../../static/images/top_btn_tap.png) no-repeat center; 129 | background-size: 50rpx 30rpx; 130 | } 131 | /* // 下 */ 132 | .bottom_btn_hover { 133 | background: url(../../static/images/bottom_btn_tap.png) no-repeat center; 134 | background-size: 50rpx 30rpx; 135 | } 136 | /* // 左 */ 137 | .left_btn_hover { 138 | background: url(../../static/images/left_btn_tap.png) no-repeat center; 139 | background-size: 30rpx 50rpx; 140 | } 141 | /* // 右 */ 142 | .right_btn_hover { 143 | background: url(../../static/images/right_btn_tap.png) no-repeat center; 144 | background-size: 30rpx 50rpx; 145 | } 146 | /* // 中 */ 147 | .circel_btn_hover { 148 | background: url(../../static/images/circel_btn_tap.png) no-repeat; 149 | background-size: 180rpx 180rpx; 150 | } 151 | } 152 | } 153 | /* // 下方按钮 */ 154 | .bottom { 155 | .red { 156 | justify-content: space-between; 157 | flex-direction: row; 158 | .redChild { 159 | width:80rpx; 160 | height:160rpx; 161 | background:linear-gradient(0deg,rgba(199,34,99,1) 0%,rgba(250,44,160,1) 100%); 162 | // box-shadow:6rpx 10rpx 32rpx 0rpx rgba(12,19,36,0.48); 163 | border-radius: 80rpx 0 0 80rpx; 164 | display: flex; 165 | flex-direction: column; 166 | // justify-content: space-around; 167 | padding-top: 45rpx; 168 | align-items: center; 169 | } 170 | 171 | .auto { 172 | border-radius: 0rpx 80rpx 80rpx 0rpx; 173 | // margin-left: 1rpx; 174 | // background: url(../../static/images/right.png) no-repeat 100% 100% !important; 175 | } 176 | .Manu { 177 | padding-right: -1rpx; 178 | // background: url(../../static/images/left.png) no-repeat 100% 100% !important; 179 | } 180 | } 181 | } 182 | } 183 | /* // 手指点击 */ 184 | .btn-hover { 185 | width:160rpx; 186 | height:160rpx; 187 | background:linear-gradient(0deg,rgba(0,78,158,1) 0%,rgba(31,145,243,1) 100%); 188 | background-size: 100% 100%; 189 | border:7rpx solid rgba(14, 109, 198, 1); 190 | box-shadow:3rpx 5rpx 20rpx 0rpx rgba(0,93,187,0.48), 1rpx 1rpx 32rpx 0rpx rgba(0,93,182,0.64); 191 | border-radius:50%; 192 | } 193 | /* // 手动自动点击特效 */ 194 | .btn-hover-right { 195 | box-shadow:6px 10px 32px 0px rgba(12,19,36,0.3) inset; 196 | } 197 | .btn-hover-left { 198 | background: #0C1324; 199 | box-shadow: 6px 10px 32px 0px rgba(12,19,36,0.3) inset; 200 | } 201 | .btn-hover-left1 { 202 | background: darkred !important; 203 | } 204 | 205 | } 206 | .num { 207 | background: #000 !important; 208 | } 209 | .bg { 210 | background: #000 !important; 211 | // background: #0086B3; 212 | } -------------------------------------------------------------------------------- /components/neil-modal/neil-modal.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 117 | 118 | 268 | -------------------------------------------------------------------------------- /components/neil-modal/readme.md: -------------------------------------------------------------------------------- 1 | ### Modal 模态框 2 | 3 | 自定义 Modal 模态框组件 4 | 5 | **使用方式:** 6 | 7 | 在 script 中引用组件 8 | 9 | ```javascript 10 | import neilModal from '@/components/neil-modal/neil-modal.vue'; 11 | export default { 12 | components: {neilModal} 13 | } 14 | ``` 15 | 16 | 基础使用方式 17 | 18 | ```html 19 | 26 | 27 | ``` 28 | 29 | 单个确认按钮 30 | 31 | ```html 32 | 37 | 38 | ``` 39 | 40 | **属性说明:** 41 | 42 | |属性名 |类型 |默认值 |说明 | 43 | |--- |---- |--- |--- | 44 | |title|String||标题 | 45 | |content|String||内容| 46 | |align|String|left|内容对齐方式,值为:left(左对齐)、center(居中对齐)、right(右对齐)| 47 | |show |Boolean |false |Modal的显示状态 | 48 | |show-cancel|Boolean|true |是否显示取消按钮| 49 | |auto-close|Boolean|true |点击遮罩是否自动关闭模态框| 50 | |confirm-color|String|#007aff|确认按钮的颜色 | 51 | |confirm-text|String|确定|确定按钮的文字 | 52 | |cancel-color|String|#333333|取消按钮的颜色 | 53 | |cancel-text|String|取消|取消按钮的文字 | 54 | 55 | **事件说明:** 56 | 57 | |事件名|说明 | 58 | |close|组件关闭时触发事件| 59 | |confirm|点击确认按钮时触发事件| 60 | |cancel|点击取消按钮时触发事件| 61 | 62 | **slot** 63 | 64 | 在 ``neil-modal`` 节点下,可以通过插入节点实现自定义 content 的需求(只有 content 属性为空的时候才会加载 slot) 65 | 66 | 使用示例: 67 | 68 | ```html 69 | 70 | 71 | 1. 修复标题颜色不对的问题 72 | 2. 增加支付宝支付功能 73 | 3. 增加更多示例 74 | 75 | 76 | ``` 77 | 78 | **其他** 79 | 80 | * Modal 组件 z-index 为 1000; 81 | * Modal 组件非原生组件,使用时会被原生组件所覆盖; 82 | * 通过本页面下载按钮下载的zip为一个完整 ``uni-app`` 工程,拖入 HBuilderX即可运行体验效果; 83 | * 若想集成本组件到现有工程,可以将 components 目录下的 neil-modal 目录拷贝到自己工程的 components 目录; 84 | * 使用过程出现问题或有新的需求可在评论区留言。 85 | -------------------------------------------------------------------------------- /components/uni-countdown/uni-countdown.vue: -------------------------------------------------------------------------------- 1 | 23 | 164 | 208 | -------------------------------------------------------------------------------- /components/uni-popup/uni-popup.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 97 | 215 | -------------------------------------------------------------------------------- /lang/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | lang: 'en', 3 | loading: 'loading...', 4 | title: 'AdverPlayer', 5 | login: { 6 | button: 'Scan the code', 7 | more: 'more' 8 | 9 | }, 10 | password: { 11 | placeholder: 'Enter device password', 12 | change: 'Change password', 13 | oldPswd: 'Enter old password', 14 | newPswd: 'Enter new password', 15 | submit: 'Submit', 16 | confirm: 'Confirm', 17 | cancel: 'Cancel' 18 | }, 19 | turnPage: { 20 | 21 | title: 'Turn Page', 22 | first: 'Last Page', 23 | last: 'First Page', 24 | down: 'Down One', 25 | up: 'Up One', 26 | // down: 'Down One', 27 | // up: 'Up One' 28 | }, 29 | calibration: { 30 | title: 'Calibration', 31 | click: 'Click', 32 | Inching: 'Inching', 33 | pres: 'Pres', 34 | moving: 'Moving' 35 | }, 36 | other: { 37 | closing: 'closing', 38 | sureConnBLE: "Make sure you're connected to bluetooth", 39 | corretTime: 'Automatic time correction?', 40 | y: 'Yes', 41 | n: 'Not', 42 | login: 'logining', 43 | AutoCan: 'Auto mode can be entered', 44 | offlineOrOther: 'Offline Or Other', 45 | settingFail: 'Fail', 46 | BLEList: 'Query bluetooth list', 47 | BLEConn: 'list (tap connect)', 48 | BLENone: 'None', 49 | corrected: 'Is connected', 50 | outLine: 'Out Line', 51 | setting1: 'setting', 52 | search: 'In the search', 53 | checking: 'checking', 54 | reconnection: 'reconnection', 55 | fail: 'fail', 56 | notStorage: 'Not in storage', 57 | conning: 'wait a moment', 58 | sucWait: 'Sucess waitting', 59 | searchNone: 'No device found/in use', 60 | searchFail: 'Query failed.Make sure bluetooth is enabled/located', 61 | stopMode: 'Stop Mode', 62 | back: 'Manu/Auto To ESC', 63 | save: 'ENTER To Save', 64 | toMenu: 'Esc To Back', 65 | move: 'To Move', 66 | enter: 'ENTER To Save', 67 | setItems: 'ENTER To Menu', 68 | sysTitle: 'System Time', 69 | modeTitle: 'Run Mode', 70 | host: 'Host', 71 | single: 'Single', 72 | test: 'Test', 73 | client: 'Client', 74 | delayTitle: 'Sync. Time', 75 | copyTitle: 'Data Copy', 76 | copy: 'Copying...', 77 | setIdTitle: 'Board ID' 78 | }, 79 | Manu: { 80 | title: 'Manu Mode', 81 | title2: 'Auto Mode' 82 | }, 83 | pageNum: { 84 | title: 'Page Number', 85 | set: 'Set To:', 86 | change: 'To Change' 87 | }, 88 | 89 | showTime: { 90 | all: 'All Show Time', 91 | single: 'One Show Time', 92 | No: 'NO.', 93 | set: 'Set To:' 94 | }, 95 | timer: { 96 | title1: 'ON Time A', 97 | title2: 'ON Time B', 98 | title3: 'OFF Time A', 99 | title4: 'OFF Time B', 100 | title5: 'ON Light Time', 101 | title6: 'OFF Light Time' 102 | 103 | }, 104 | speedSet: { 105 | title: 'Speed', 106 | set: 'Set To:', 107 | change: 'To Change' 108 | }, 109 | stopPic: { 110 | title: 'Stay Picture' 111 | }, 112 | parameters: { 113 | OV: 'OV Parameters', 114 | OC: 'OC Parameters', 115 | OT: 'OT Parameters', 116 | UV: 'UV Parameters', 117 | UVT: 'UVT Parameters' 118 | }, 119 | index: { 120 | GPRS: 'GPRS', 121 | BLE: 'BLE', 122 | Speed: 'Speed', 123 | modelContent: 'Sure back to the scan page?', 124 | manu: 'Manu', 125 | client: 'Client', 126 | ok: 'OK', 127 | OV: 'OV', 128 | OC: 'OC', 129 | OT: 'OT', 130 | UV: 'UV', 131 | F: 'FALSE', 132 | host: 'Host', 133 | single: 'Single', 134 | test: 'Test', 135 | auto: 'Auto', 136 | homeData: { 137 | // 当前画面 138 | nowPic: 1, 139 | auto: 'Auto', 140 | // 速度和类型 141 | speed: 0, // 速度为0-9 142 | type: 1, // 1GPRS 2BLE 143 | // // 年月日 144 | // year_l: 0, 145 | // year_r: 0, 146 | // month_l: 0, 147 | // month_r: 0, 148 | // day_l: 0, 149 | // day_r: 0, 150 | // // 时分秒 151 | // hour_l: 0, 152 | // hour_r: 0, 153 | // minute_l: 0, 154 | // minute_r: 0, 155 | // second_l: 0, 156 | // second_r: 0, 157 | d_l: 0, 158 | d_r: 0, 159 | f_l: 0, 160 | f_r: 0, 161 | h_l: 0, 162 | h_r: 0, 163 | m_l: 0, 164 | m_r: 0, 165 | s_l: 0, 166 | s_r: 0, 167 | y_l: 0, 168 | y_r: 0, 169 | // 第四行 170 | runStatus: 0, // 运行状态 0自动 7自检 3急停 4或1手动 2 或6 微调 8暂时未用到 171 | // 主从机模式 172 | model: 0, // 0-独立 1-主机,2-从机 173 | /* 主板工作状态 174 | 0--系统正常 2--过温保护 3--过流保护 4--过压保护 5--欠压保护) 其它:连接失败 */ 175 | workStatus: 0 176 | }, 177 | // 设置项数据 178 | setItemData: [ 179 | { id: 1, value: 'Page Number' }, 180 | { id: 2, value: 'Show Time' }, 181 | { id: 3, value: 'Timer' }, 182 | { id: 4, value: 'Speed' } 183 | ], 184 | setItemsData1: [ 185 | { id: 1, value: 'Page Number' }, 186 | { id: 2, value: 'Show Time' }, 187 | { id: 3, value: 'Timer' }, 188 | { id: 4, value: 'Speed' } 189 | ], 190 | setItemsData2: [ 191 | { id: 5, value: 'Stop Page' }, 192 | { id: 6, value: 'System Time' }, 193 | { id: 7, value: 'Run Mode' }, 194 | { id: 8, value: 'Sync. Time' } 195 | ], 196 | setItemsData3: [ 197 | { id: 9, value: 'Data Copy' }, 198 | { id: 10, value: 'Board ID' }, 199 | { id: 11, value: 'Parameter Set' }, 200 | { id: 12, value: 'BPP Set' } 201 | ], 202 | showTimeData: [ 203 | { id: 1, value: 'All Show Time' }, 204 | { id: 2, value: 'One Show Time' } 205 | ], 206 | // 定时设置 207 | timerData: [ 208 | { id: 1, value: 'ON Time A' }, 209 | { id: 2, value: 'OFF Time A' }, 210 | { id: 3, value: 'ON Time B' }, 211 | { id: 4, value: 'OFF Time B' }, 212 | ], 213 | timerData1: [ 214 | { id: 1, value: 'ON Time A' }, 215 | { id: 2, value: 'OFF Time A' }, 216 | { id: 3, value: 'ON Time B' }, 217 | { id: 4, value: 'OFF Time B' }, 218 | ], 219 | timerData2: [ 220 | { id: 5, value: 'ON Light Time' }, 221 | { id: 6, value: 'OFF Light Time' }, 222 | ], 223 | // 其他参数设置数据 224 | otherParametersData: [ 225 | {id: 1, value: 'OV Parameter'}, 226 | {id: 2, value: 'OC Parameter'}, 227 | {id: 3, value: 'OT Parameter'}, 228 | {id: 4, value: 'UV Parameter'} 229 | ], 230 | otherParametersData1: [ 231 | {id: 1, value: 'OV Parameter'}, 232 | {id: 2, value: 'OC Parameter'}, 233 | {id: 3, value: 'OT Parameter'}, 234 | {id: 4, value: 'UV Parameter'} 235 | ], 236 | otherParametersData2: [ 237 | {id: 5, value: 'UVT Parameter'} 238 | ], 239 | setBLEData: [ 240 | { id: 1, value: 'Search BLE list' }, 241 | { id: 2, value: 'Close the BLE' } 242 | ], 243 | }, 244 | toast: 'You have cancelled authorization. Login failed', 245 | Toast: { 246 | complete: 'Please complete the information', 247 | pswdSuc: 'Password changed successfully', 248 | fillPswd: 'Please fill in the password', 249 | unKnown: 'Unknown error, please contact administrator', 250 | scanFalse: 'Sweep code failure', 251 | scanFailed: 'Please make sure the device code is correct and scan the code again', 252 | systemErr: 'System exceptions.', 253 | NotOpen: 'Temporary not open', 254 | modifie:'Modifie fail' 255 | } 256 | 257 | } -------------------------------------------------------------------------------- /lang/index.js: -------------------------------------------------------------------------------- 1 | import LangEn from './en.js' 2 | import LangChs from './zh.js' 3 | import Vue from 'vue' 4 | import VueI18n from './vue-i18n' 5 | Vue.use(VueI18n) 6 | const system_info = uni.getStorageSync('system_info') 7 | if (!system_info) { 8 | // 获取设备信息 9 | uni.getSystemInfo({ 10 | success: function (res) { 11 | uni.setStorageSync('system_info', res); 12 | } 13 | }) 14 | } 15 | const cur_lang = system_info.language == 'en' ? 'en' : 'zh_CN' 16 | const i18n = new VueI18n({ 17 | locale: cur_lang || 'zh_CN', // 默认选择的语言 18 | messages: { 19 | 'en': LangEn, 20 | 'zh_CN': LangChs 21 | } 22 | }) 23 | export default i18n -------------------------------------------------------------------------------- /lang/vue-i18n.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-i18n v8.10.0 3 | * (c) 2019 kazuya kawaguchi 4 | * Released under the MIT License. 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global.VueI18n = factory()); 10 | }(this, (function () { 'use strict'; 11 | 12 | /* */ 13 | 14 | /** 15 | * constants 16 | */ 17 | 18 | var numberFormatKeys = [ 19 | 'style', 20 | 'currency', 21 | 'currencyDisplay', 22 | 'useGrouping', 23 | 'minimumIntegerDigits', 24 | 'minimumFractionDigits', 25 | 'maximumFractionDigits', 26 | 'minimumSignificantDigits', 27 | 'maximumSignificantDigits', 28 | 'localeMatcher', 29 | 'formatMatcher' 30 | ]; 31 | 32 | /** 33 | * utilities 34 | */ 35 | 36 | function warn (msg, err) { 37 | if (typeof console !== 'undefined') { 38 | console.warn('[vue-i18n] ' + msg); 39 | /* istanbul ignore if */ 40 | if (err) { 41 | console.warn(err.stack); 42 | } 43 | } 44 | } 45 | 46 | function isObject (obj) { 47 | return obj !== null && typeof obj === 'object' 48 | } 49 | 50 | var toString = Object.prototype.toString; 51 | var OBJECT_STRING = '[object Object]'; 52 | function isPlainObject (obj) { 53 | return toString.call(obj) === OBJECT_STRING 54 | } 55 | 56 | function isNull (val) { 57 | return val === null || val === undefined 58 | } 59 | 60 | function parseArgs () { 61 | var args = [], len = arguments.length; 62 | while ( len-- ) args[ len ] = arguments[ len ]; 63 | 64 | var locale = null; 65 | var params = null; 66 | if (args.length === 1) { 67 | if (isObject(args[0]) || Array.isArray(args[0])) { 68 | params = args[0]; 69 | } else if (typeof args[0] === 'string') { 70 | locale = args[0]; 71 | } 72 | } else if (args.length === 2) { 73 | if (typeof args[0] === 'string') { 74 | locale = args[0]; 75 | } 76 | /* istanbul ignore if */ 77 | if (isObject(args[1]) || Array.isArray(args[1])) { 78 | params = args[1]; 79 | } 80 | } 81 | 82 | return { locale: locale, params: params } 83 | } 84 | 85 | function looseClone (obj) { 86 | return JSON.parse(JSON.stringify(obj)) 87 | } 88 | 89 | function remove (arr, item) { 90 | if (arr.length) { 91 | var index = arr.indexOf(item); 92 | if (index > -1) { 93 | return arr.splice(index, 1) 94 | } 95 | } 96 | } 97 | 98 | var hasOwnProperty = Object.prototype.hasOwnProperty; 99 | function hasOwn (obj, key) { 100 | return hasOwnProperty.call(obj, key) 101 | } 102 | 103 | function merge (target) { 104 | var arguments$1 = arguments; 105 | 106 | var output = Object(target); 107 | for (var i = 1; i < arguments.length; i++) { 108 | var source = arguments$1[i]; 109 | if (source !== undefined && source !== null) { 110 | var key = (void 0); 111 | for (key in source) { 112 | if (hasOwn(source, key)) { 113 | if (isObject(source[key])) { 114 | output[key] = merge(output[key], source[key]); 115 | } else { 116 | output[key] = source[key]; 117 | } 118 | } 119 | } 120 | } 121 | } 122 | return output 123 | } 124 | 125 | function looseEqual (a, b) { 126 | if (a === b) { return true } 127 | var isObjectA = isObject(a); 128 | var isObjectB = isObject(b); 129 | if (isObjectA && isObjectB) { 130 | try { 131 | var isArrayA = Array.isArray(a); 132 | var isArrayB = Array.isArray(b); 133 | if (isArrayA && isArrayB) { 134 | return a.length === b.length && a.every(function (e, i) { 135 | return looseEqual(e, b[i]) 136 | }) 137 | } else if (!isArrayA && !isArrayB) { 138 | var keysA = Object.keys(a); 139 | var keysB = Object.keys(b); 140 | return keysA.length === keysB.length && keysA.every(function (key) { 141 | return looseEqual(a[key], b[key]) 142 | }) 143 | } else { 144 | /* istanbul ignore next */ 145 | return false 146 | } 147 | } catch (e) { 148 | /* istanbul ignore next */ 149 | return false 150 | } 151 | } else if (!isObjectA && !isObjectB) { 152 | return String(a) === String(b) 153 | } else { 154 | return false 155 | } 156 | } 157 | 158 | /* */ 159 | 160 | function extend (Vue) { 161 | if (!Vue.prototype.hasOwnProperty('$i18n')) { 162 | // $FlowFixMe 163 | Object.defineProperty(Vue.prototype, '$i18n', { 164 | get: function get () { return this._i18n } 165 | }); 166 | } 167 | 168 | Vue.prototype.$t = function (key) { 169 | var values = [], len = arguments.length - 1; 170 | while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; 171 | 172 | var i18n = this.$i18n; 173 | return i18n._t.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this ].concat( values )) 174 | }; 175 | 176 | Vue.prototype.$tc = function (key, choice) { 177 | var values = [], len = arguments.length - 2; 178 | while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ]; 179 | 180 | var i18n = this.$i18n; 181 | return i18n._tc.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this, choice ].concat( values )) 182 | }; 183 | 184 | Vue.prototype.$te = function (key, locale) { 185 | var i18n = this.$i18n; 186 | return i18n._te(key, i18n.locale, i18n._getMessages(), locale) 187 | }; 188 | 189 | Vue.prototype.$d = function (value) { 190 | var ref; 191 | 192 | var args = [], len = arguments.length - 1; 193 | while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; 194 | return (ref = this.$i18n).d.apply(ref, [ value ].concat( args )) 195 | }; 196 | 197 | Vue.prototype.$n = function (value) { 198 | var ref; 199 | 200 | var args = [], len = arguments.length - 1; 201 | while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; 202 | return (ref = this.$i18n).n.apply(ref, [ value ].concat( args )) 203 | }; 204 | } 205 | 206 | /* */ 207 | 208 | var mixin = { 209 | beforeCreate: function beforeCreate () { 210 | var options = this.$options; 211 | options.i18n = options.i18n || (options.__i18n ? {} : null); 212 | 213 | if (options.i18n) { 214 | if (options.i18n instanceof VueI18n) { 215 | // init locale messages via custom blocks 216 | if (options.__i18n) { 217 | try { 218 | var localeMessages = {}; 219 | options.__i18n.forEach(function (resource) { 220 | localeMessages = merge(localeMessages, JSON.parse(resource)); 221 | }); 222 | Object.keys(localeMessages).forEach(function (locale) { 223 | options.i18n.mergeLocaleMessage(locale, localeMessages[locale]); 224 | }); 225 | } catch (e) { 226 | { 227 | warn("Cannot parse locale messages via custom blocks.", e); 228 | } 229 | } 230 | } 231 | this._i18n = options.i18n; 232 | this._i18nWatcher = this._i18n.watchI18nData(); 233 | this._i18n.subscribeDataChanging(this); 234 | this._subscribing = true; 235 | } else if (isPlainObject(options.i18n)) { 236 | // component local i18n 237 | if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { 238 | options.i18n.root = this.$root; 239 | options.i18n.formatter = this.$root.$i18n.formatter; 240 | options.i18n.fallbackLocale = this.$root.$i18n.fallbackLocale; 241 | options.i18n.silentTranslationWarn = this.$root.$i18n.silentTranslationWarn; 242 | options.i18n.silentFallbackWarn = this.$root.$i18n.silentFallbackWarn; 243 | options.i18n.pluralizationRules = this.$root.$i18n.pluralizationRules; 244 | options.i18n.preserveDirectiveContent = this.$root.$i18n.preserveDirectiveContent; 245 | } 246 | 247 | // init locale messages via custom blocks 248 | if (options.__i18n) { 249 | try { 250 | var localeMessages$1 = {}; 251 | options.__i18n.forEach(function (resource) { 252 | localeMessages$1 = merge(localeMessages$1, JSON.parse(resource)); 253 | }); 254 | options.i18n.messages = localeMessages$1; 255 | } catch (e) { 256 | { 257 | warn("Cannot parse locale messages via custom blocks.", e); 258 | } 259 | } 260 | } 261 | 262 | this._i18n = new VueI18n(options.i18n); 263 | this._i18nWatcher = this._i18n.watchI18nData(); 264 | this._i18n.subscribeDataChanging(this); 265 | this._subscribing = true; 266 | 267 | if (options.i18n.sync === undefined || !!options.i18n.sync) { 268 | this._localeWatcher = this.$i18n.watchLocale(); 269 | } 270 | } else { 271 | { 272 | warn("Cannot be interpreted 'i18n' option."); 273 | } 274 | } 275 | } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) { 276 | // root i18n 277 | this._i18n = this.$root.$i18n; 278 | this._i18n.subscribeDataChanging(this); 279 | this._subscribing = true; 280 | } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) { 281 | // parent i18n 282 | this._i18n = options.parent.$i18n; 283 | this._i18n.subscribeDataChanging(this); 284 | this._subscribing = true; 285 | } 286 | }, 287 | 288 | beforeDestroy: function beforeDestroy () { 289 | if (!this._i18n) { return } 290 | 291 | var self = this; 292 | this.$nextTick(function () { 293 | if (self._subscribing) { 294 | self._i18n.unsubscribeDataChanging(self); 295 | delete self._subscribing; 296 | } 297 | 298 | if (self._i18nWatcher) { 299 | self._i18nWatcher(); 300 | self._i18n.destroyVM(); 301 | delete self._i18nWatcher; 302 | } 303 | 304 | if (self._localeWatcher) { 305 | self._localeWatcher(); 306 | delete self._localeWatcher; 307 | } 308 | 309 | self._i18n = null; 310 | }); 311 | } 312 | }; 313 | 314 | /* */ 315 | 316 | var interpolationComponent = { 317 | name: 'i18n', 318 | functional: true, 319 | props: { 320 | tag: { 321 | type: String, 322 | default: 'span' 323 | }, 324 | path: { 325 | type: String, 326 | required: true 327 | }, 328 | locale: { 329 | type: String 330 | }, 331 | places: { 332 | type: [Array, Object] 333 | } 334 | }, 335 | render: function render (h, ref) { 336 | var props = ref.props; 337 | var data = ref.data; 338 | var children = ref.children; 339 | var parent = ref.parent; 340 | 341 | var i18n = parent.$i18n; 342 | 343 | children = (children || []).filter(function (child) { 344 | return child.tag || (child.text = child.text.trim()) 345 | }); 346 | 347 | if (!i18n) { 348 | { 349 | warn('Cannot find VueI18n instance!'); 350 | } 351 | return children 352 | } 353 | 354 | var path = props.path; 355 | var locale = props.locale; 356 | 357 | var params = {}; 358 | var places = props.places || {}; 359 | 360 | var hasPlaces = Array.isArray(places) 361 | ? places.length > 0 362 | : Object.keys(places).length > 0; 363 | 364 | var everyPlace = children.every(function (child) { 365 | if (child.data && child.data.attrs) { 366 | var place = child.data.attrs.place; 367 | return (typeof place !== 'undefined') && place !== '' 368 | } 369 | }); 370 | 371 | if (hasPlaces && children.length > 0 && !everyPlace) { 372 | warn('If places prop is set, all child elements must have place prop set.'); 373 | } 374 | 375 | if (Array.isArray(places)) { 376 | places.forEach(function (el, i) { 377 | params[i] = el; 378 | }); 379 | } else { 380 | Object.keys(places).forEach(function (key) { 381 | params[key] = places[key]; 382 | }); 383 | } 384 | 385 | children.forEach(function (child, i) { 386 | var key = everyPlace 387 | ? ("" + (child.data.attrs.place)) 388 | : ("" + i); 389 | params[key] = child; 390 | }); 391 | 392 | return h(props.tag, data, i18n.i(path, locale, params)) 393 | } 394 | }; 395 | 396 | /* */ 397 | 398 | var numberComponent = { 399 | name: 'i18n-n', 400 | functional: true, 401 | props: { 402 | tag: { 403 | type: String, 404 | default: 'span' 405 | }, 406 | value: { 407 | type: Number, 408 | required: true 409 | }, 410 | format: { 411 | type: [String, Object] 412 | }, 413 | locale: { 414 | type: String 415 | } 416 | }, 417 | render: function render (h, ref) { 418 | var props = ref.props; 419 | var parent = ref.parent; 420 | var data = ref.data; 421 | 422 | var i18n = parent.$i18n; 423 | 424 | if (!i18n) { 425 | { 426 | warn('Cannot find VueI18n instance!'); 427 | } 428 | return null 429 | } 430 | 431 | var key = null; 432 | var options = null; 433 | 434 | if (typeof props.format === 'string') { 435 | key = props.format; 436 | } else if (isObject(props.format)) { 437 | if (props.format.key) { 438 | key = props.format.key; 439 | } 440 | 441 | // Filter out number format options only 442 | options = Object.keys(props.format).reduce(function (acc, prop) { 443 | var obj; 444 | 445 | if (numberFormatKeys.includes(prop)) { 446 | return Object.assign({}, acc, ( obj = {}, obj[prop] = props.format[prop], obj )) 447 | } 448 | return acc 449 | }, null); 450 | } 451 | 452 | var locale = props.locale || i18n.locale; 453 | var parts = i18n._ntp(props.value, locale, key, options); 454 | 455 | var values = parts.map(function (part, index) { 456 | var obj; 457 | 458 | var slot = data.scopedSlots && data.scopedSlots[part.type]; 459 | return slot ? slot(( obj = {}, obj[part.type] = part.value, obj.index = index, obj.parts = parts, obj )) : part.value 460 | }); 461 | 462 | return h(props.tag, { 463 | attrs: data.attrs, 464 | 'class': data['class'], 465 | staticClass: data.staticClass 466 | }, values) 467 | } 468 | }; 469 | 470 | /* */ 471 | 472 | function bind (el, binding, vnode) { 473 | if (!assert(el, vnode)) { return } 474 | 475 | t(el, binding, vnode); 476 | } 477 | 478 | function update (el, binding, vnode, oldVNode) { 479 | if (!assert(el, vnode)) { return } 480 | 481 | var i18n = vnode.context.$i18n; 482 | if (localeEqual(el, vnode) && 483 | (looseEqual(binding.value, binding.oldValue) && 484 | looseEqual(el._localeMessage, i18n.getLocaleMessage(i18n.locale)))) { return } 485 | 486 | t(el, binding, vnode); 487 | } 488 | 489 | function unbind (el, binding, vnode, oldVNode) { 490 | var vm = vnode.context; 491 | if (!vm) { 492 | warn('Vue instance does not exists in VNode context'); 493 | return 494 | } 495 | 496 | var i18n = vnode.context.$i18n || {}; 497 | if (!binding.modifiers.preserve && !i18n.preserveDirectiveContent) { 498 | el.textContent = ''; 499 | } 500 | el._vt = undefined; 501 | delete el['_vt']; 502 | el._locale = undefined; 503 | delete el['_locale']; 504 | el._localeMessage = undefined; 505 | delete el['_localeMessage']; 506 | } 507 | 508 | function assert (el, vnode) { 509 | var vm = vnode.context; 510 | if (!vm) { 511 | warn('Vue instance does not exists in VNode context'); 512 | return false 513 | } 514 | 515 | if (!vm.$i18n) { 516 | warn('VueI18n instance does not exists in Vue instance'); 517 | return false 518 | } 519 | 520 | return true 521 | } 522 | 523 | function localeEqual (el, vnode) { 524 | var vm = vnode.context; 525 | return el._locale === vm.$i18n.locale 526 | } 527 | 528 | function t (el, binding, vnode) { 529 | var ref$1, ref$2; 530 | 531 | var value = binding.value; 532 | 533 | var ref = parseValue(value); 534 | var path = ref.path; 535 | var locale = ref.locale; 536 | var args = ref.args; 537 | var choice = ref.choice; 538 | if (!path && !locale && !args) { 539 | warn('value type not supported'); 540 | return 541 | } 542 | 543 | if (!path) { 544 | warn('`path` is required in v-t directive'); 545 | return 546 | } 547 | 548 | var vm = vnode.context; 549 | if (choice) { 550 | el._vt = el.textContent = (ref$1 = vm.$i18n).tc.apply(ref$1, [ path, choice ].concat( makeParams(locale, args) )); 551 | } else { 552 | el._vt = el.textContent = (ref$2 = vm.$i18n).t.apply(ref$2, [ path ].concat( makeParams(locale, args) )); 553 | } 554 | el._locale = vm.$i18n.locale; 555 | el._localeMessage = vm.$i18n.getLocaleMessage(vm.$i18n.locale); 556 | } 557 | 558 | function parseValue (value) { 559 | var path; 560 | var locale; 561 | var args; 562 | var choice; 563 | 564 | if (typeof value === 'string') { 565 | path = value; 566 | } else if (isPlainObject(value)) { 567 | path = value.path; 568 | locale = value.locale; 569 | args = value.args; 570 | choice = value.choice; 571 | } 572 | 573 | return { path: path, locale: locale, args: args, choice: choice } 574 | } 575 | 576 | function makeParams (locale, args) { 577 | var params = []; 578 | 579 | locale && params.push(locale); 580 | if (args && (Array.isArray(args) || isPlainObject(args))) { 581 | params.push(args); 582 | } 583 | 584 | return params 585 | } 586 | 587 | var Vue; 588 | 589 | function install (_Vue) { 590 | /* istanbul ignore if */ 591 | if (install.installed && _Vue === Vue) { 592 | warn('already installed.'); 593 | return 594 | } 595 | install.installed = true; 596 | 597 | Vue = _Vue; 598 | 599 | var version = (Vue.version && Number(Vue.version.split('.')[0])) || -1; 600 | /* istanbul ignore if */ 601 | if (version < 2) { 602 | warn(("vue-i18n (" + (install.version) + ") need to use Vue 2.0 or later (Vue: " + (Vue.version) + ").")); 603 | return 604 | } 605 | 606 | extend(Vue); 607 | Vue.mixin(mixin); 608 | Vue.directive('t', { bind: bind, update: update, unbind: unbind }); 609 | Vue.component(interpolationComponent.name, interpolationComponent); 610 | Vue.component(numberComponent.name, numberComponent); 611 | 612 | // use simple mergeStrategies to prevent i18n instance lose '__proto__' 613 | var strats = Vue.config.optionMergeStrategies; 614 | strats.i18n = function (parentVal, childVal) { 615 | return childVal === undefined 616 | ? parentVal 617 | : childVal 618 | }; 619 | } 620 | 621 | /* */ 622 | 623 | var BaseFormatter = function BaseFormatter () { 624 | this._caches = Object.create(null); 625 | }; 626 | 627 | BaseFormatter.prototype.interpolate = function interpolate (message, values) { 628 | if (!values) { 629 | return [message] 630 | } 631 | var tokens = this._caches[message]; 632 | if (!tokens) { 633 | tokens = parse(message); 634 | this._caches[message] = tokens; 635 | } 636 | return compile(tokens, values) 637 | }; 638 | 639 | 640 | 641 | var RE_TOKEN_LIST_VALUE = /^(?:\d)+/; 642 | var RE_TOKEN_NAMED_VALUE = /^(?:\w)+/; 643 | 644 | function parse (format) { 645 | var tokens = []; 646 | var position = 0; 647 | 648 | var text = ''; 649 | while (position < format.length) { 650 | var char = format[position++]; 651 | if (char === '{') { 652 | if (text) { 653 | tokens.push({ type: 'text', value: text }); 654 | } 655 | 656 | text = ''; 657 | var sub = ''; 658 | char = format[position++]; 659 | while (char !== undefined && char !== '}') { 660 | sub += char; 661 | char = format[position++]; 662 | } 663 | var isClosed = char === '}'; 664 | 665 | var type = RE_TOKEN_LIST_VALUE.test(sub) 666 | ? 'list' 667 | : isClosed && RE_TOKEN_NAMED_VALUE.test(sub) 668 | ? 'named' 669 | : 'unknown'; 670 | tokens.push({ value: sub, type: type }); 671 | } else if (char === '%') { 672 | // when found rails i18n syntax, skip text capture 673 | if (format[(position)] !== '{') { 674 | text += char; 675 | } 676 | } else { 677 | text += char; 678 | } 679 | } 680 | 681 | text && tokens.push({ type: 'text', value: text }); 682 | 683 | return tokens 684 | } 685 | 686 | function compile (tokens, values) { 687 | var compiled = []; 688 | var index = 0; 689 | 690 | var mode = Array.isArray(values) 691 | ? 'list' 692 | : isObject(values) 693 | ? 'named' 694 | : 'unknown'; 695 | if (mode === 'unknown') { return compiled } 696 | 697 | while (index < tokens.length) { 698 | var token = tokens[index]; 699 | switch (token.type) { 700 | case 'text': 701 | compiled.push(token.value); 702 | break 703 | case 'list': 704 | compiled.push(values[parseInt(token.value, 10)]); 705 | break 706 | case 'named': 707 | if (mode === 'named') { 708 | compiled.push((values)[token.value]); 709 | } else { 710 | { 711 | warn(("Type of token '" + (token.type) + "' and format of value '" + mode + "' don't match!")); 712 | } 713 | } 714 | break 715 | case 'unknown': 716 | { 717 | warn("Detect 'unknown' type of token!"); 718 | } 719 | break 720 | } 721 | index++; 722 | } 723 | 724 | return compiled 725 | } 726 | 727 | /* */ 728 | 729 | /** 730 | * Path parser 731 | * - Inspired: 732 | * Vue.js Path parser 733 | */ 734 | 735 | // actions 736 | var APPEND = 0; 737 | var PUSH = 1; 738 | var INC_SUB_PATH_DEPTH = 2; 739 | var PUSH_SUB_PATH = 3; 740 | 741 | // states 742 | var BEFORE_PATH = 0; 743 | var IN_PATH = 1; 744 | var BEFORE_IDENT = 2; 745 | var IN_IDENT = 3; 746 | var IN_SUB_PATH = 4; 747 | var IN_SINGLE_QUOTE = 5; 748 | var IN_DOUBLE_QUOTE = 6; 749 | var AFTER_PATH = 7; 750 | var ERROR = 8; 751 | 752 | var pathStateMachine = []; 753 | 754 | pathStateMachine[BEFORE_PATH] = { 755 | 'ws': [BEFORE_PATH], 756 | 'ident': [IN_IDENT, APPEND], 757 | '[': [IN_SUB_PATH], 758 | 'eof': [AFTER_PATH] 759 | }; 760 | 761 | pathStateMachine[IN_PATH] = { 762 | 'ws': [IN_PATH], 763 | '.': [BEFORE_IDENT], 764 | '[': [IN_SUB_PATH], 765 | 'eof': [AFTER_PATH] 766 | }; 767 | 768 | pathStateMachine[BEFORE_IDENT] = { 769 | 'ws': [BEFORE_IDENT], 770 | 'ident': [IN_IDENT, APPEND], 771 | '0': [IN_IDENT, APPEND], 772 | 'number': [IN_IDENT, APPEND] 773 | }; 774 | 775 | pathStateMachine[IN_IDENT] = { 776 | 'ident': [IN_IDENT, APPEND], 777 | '0': [IN_IDENT, APPEND], 778 | 'number': [IN_IDENT, APPEND], 779 | 'ws': [IN_PATH, PUSH], 780 | '.': [BEFORE_IDENT, PUSH], 781 | '[': [IN_SUB_PATH, PUSH], 782 | 'eof': [AFTER_PATH, PUSH] 783 | }; 784 | 785 | pathStateMachine[IN_SUB_PATH] = { 786 | "'": [IN_SINGLE_QUOTE, APPEND], 787 | '"': [IN_DOUBLE_QUOTE, APPEND], 788 | '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH], 789 | ']': [IN_PATH, PUSH_SUB_PATH], 790 | 'eof': ERROR, 791 | 'else': [IN_SUB_PATH, APPEND] 792 | }; 793 | 794 | pathStateMachine[IN_SINGLE_QUOTE] = { 795 | "'": [IN_SUB_PATH, APPEND], 796 | 'eof': ERROR, 797 | 'else': [IN_SINGLE_QUOTE, APPEND] 798 | }; 799 | 800 | pathStateMachine[IN_DOUBLE_QUOTE] = { 801 | '"': [IN_SUB_PATH, APPEND], 802 | 'eof': ERROR, 803 | 'else': [IN_DOUBLE_QUOTE, APPEND] 804 | }; 805 | 806 | /** 807 | * Check if an expression is a literal value. 808 | */ 809 | 810 | var literalValueRE = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/; 811 | function isLiteral (exp) { 812 | return literalValueRE.test(exp) 813 | } 814 | 815 | /** 816 | * Strip quotes from a string 817 | */ 818 | 819 | function stripQuotes (str) { 820 | var a = str.charCodeAt(0); 821 | var b = str.charCodeAt(str.length - 1); 822 | return a === b && (a === 0x22 || a === 0x27) 823 | ? str.slice(1, -1) 824 | : str 825 | } 826 | 827 | /** 828 | * Determine the type of a character in a keypath. 829 | */ 830 | 831 | function getPathCharType (ch) { 832 | if (ch === undefined || ch === null) { return 'eof' } 833 | 834 | var code = ch.charCodeAt(0); 835 | 836 | switch (code) { 837 | case 0x5B: // [ 838 | case 0x5D: // ] 839 | case 0x2E: // . 840 | case 0x22: // " 841 | case 0x27: // ' 842 | return ch 843 | 844 | case 0x5F: // _ 845 | case 0x24: // $ 846 | case 0x2D: // - 847 | return 'ident' 848 | 849 | case 0x09: // Tab 850 | case 0x0A: // Newline 851 | case 0x0D: // Return 852 | case 0xA0: // No-break space 853 | case 0xFEFF: // Byte Order Mark 854 | case 0x2028: // Line Separator 855 | case 0x2029: // Paragraph Separator 856 | return 'ws' 857 | } 858 | 859 | return 'ident' 860 | } 861 | 862 | /** 863 | * Format a subPath, return its plain form if it is 864 | * a literal string or number. Otherwise prepend the 865 | * dynamic indicator (*). 866 | */ 867 | 868 | function formatSubPath (path) { 869 | var trimmed = path.trim(); 870 | // invalid leading 0 871 | if (path.charAt(0) === '0' && isNaN(path)) { return false } 872 | 873 | return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed 874 | } 875 | 876 | /** 877 | * Parse a string path into an array of segments 878 | */ 879 | 880 | function parse$1 (path) { 881 | var keys = []; 882 | var index = -1; 883 | var mode = BEFORE_PATH; 884 | var subPathDepth = 0; 885 | var c; 886 | var key; 887 | var newChar; 888 | var type; 889 | var transition; 890 | var action; 891 | var typeMap; 892 | var actions = []; 893 | 894 | actions[PUSH] = function () { 895 | if (key !== undefined) { 896 | keys.push(key); 897 | key = undefined; 898 | } 899 | }; 900 | 901 | actions[APPEND] = function () { 902 | if (key === undefined) { 903 | key = newChar; 904 | } else { 905 | key += newChar; 906 | } 907 | }; 908 | 909 | actions[INC_SUB_PATH_DEPTH] = function () { 910 | actions[APPEND](); 911 | subPathDepth++; 912 | }; 913 | 914 | actions[PUSH_SUB_PATH] = function () { 915 | if (subPathDepth > 0) { 916 | subPathDepth--; 917 | mode = IN_SUB_PATH; 918 | actions[APPEND](); 919 | } else { 920 | subPathDepth = 0; 921 | key = formatSubPath(key); 922 | if (key === false) { 923 | return false 924 | } else { 925 | actions[PUSH](); 926 | } 927 | } 928 | }; 929 | 930 | function maybeUnescapeQuote () { 931 | var nextChar = path[index + 1]; 932 | if ((mode === IN_SINGLE_QUOTE && nextChar === "'") || 933 | (mode === IN_DOUBLE_QUOTE && nextChar === '"')) { 934 | index++; 935 | newChar = '\\' + nextChar; 936 | actions[APPEND](); 937 | return true 938 | } 939 | } 940 | 941 | while (mode !== null) { 942 | index++; 943 | c = path[index]; 944 | 945 | if (c === '\\' && maybeUnescapeQuote()) { 946 | continue 947 | } 948 | 949 | type = getPathCharType(c); 950 | typeMap = pathStateMachine[mode]; 951 | transition = typeMap[type] || typeMap['else'] || ERROR; 952 | 953 | if (transition === ERROR) { 954 | return // parse error 955 | } 956 | 957 | mode = transition[0]; 958 | action = actions[transition[1]]; 959 | if (action) { 960 | newChar = transition[2]; 961 | newChar = newChar === undefined 962 | ? c 963 | : newChar; 964 | if (action() === false) { 965 | return 966 | } 967 | } 968 | 969 | if (mode === AFTER_PATH) { 970 | return keys 971 | } 972 | } 973 | } 974 | 975 | 976 | 977 | 978 | 979 | var I18nPath = function I18nPath () { 980 | this._cache = Object.create(null); 981 | }; 982 | 983 | /** 984 | * External parse that check for a cache hit first 985 | */ 986 | I18nPath.prototype.parsePath = function parsePath (path) { 987 | var hit = this._cache[path]; 988 | if (!hit) { 989 | hit = parse$1(path); 990 | if (hit) { 991 | this._cache[path] = hit; 992 | } 993 | } 994 | return hit || [] 995 | }; 996 | 997 | /** 998 | * Get path value from path string 999 | */ 1000 | I18nPath.prototype.getPathValue = function getPathValue (obj, path) { 1001 | if (!isObject(obj)) { return null } 1002 | 1003 | var paths = this.parsePath(path); 1004 | if (paths.length === 0) { 1005 | return null 1006 | } else { 1007 | var length = paths.length; 1008 | var last = obj; 1009 | var i = 0; 1010 | while (i < length) { 1011 | var value = last[paths[i]]; 1012 | if (value === undefined) { 1013 | return null 1014 | } 1015 | last = value; 1016 | i++; 1017 | } 1018 | 1019 | return last 1020 | } 1021 | }; 1022 | 1023 | /* */ 1024 | 1025 | 1026 | 1027 | var linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g; 1028 | var linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/; 1029 | var bracketsMatcher = /[()]/g; 1030 | var formatters = { 1031 | 'upper': function (str) { return str.toLocaleUpperCase(); }, 1032 | 'lower': function (str) { return str.toLocaleLowerCase(); } 1033 | }; 1034 | 1035 | var defaultFormatter = new BaseFormatter(); 1036 | 1037 | var VueI18n = function VueI18n (options) { 1038 | var this$1 = this; 1039 | if ( options === void 0 ) options = {}; 1040 | 1041 | // Auto install if it is not done yet and `window` has `Vue`. 1042 | // To allow users to avoid auto-installation in some cases, 1043 | // this code should be placed here. See #290 1044 | /* istanbul ignore if */ 1045 | if (!Vue && typeof window !== 'undefined' && window.Vue) { 1046 | install(window.Vue); 1047 | } 1048 | 1049 | var locale = options.locale || 'en-US'; 1050 | var fallbackLocale = options.fallbackLocale || 'en-US'; 1051 | var messages = options.messages || {}; 1052 | var dateTimeFormats = options.dateTimeFormats || {}; 1053 | var numberFormats = options.numberFormats || {}; 1054 | 1055 | this._vm = null; 1056 | this._formatter = options.formatter || defaultFormatter; 1057 | this._missing = options.missing || null; 1058 | this._root = options.root || null; 1059 | this._sync = options.sync === undefined ? true : !!options.sync; 1060 | this._fallbackRoot = options.fallbackRoot === undefined 1061 | ? true 1062 | : !!options.fallbackRoot; 1063 | this._silentTranslationWarn = options.silentTranslationWarn === undefined 1064 | ? false 1065 | : !!options.silentTranslationWarn; 1066 | this._silentFallbackWarn = options.silentFallbackWarn === undefined 1067 | ? false 1068 | : !!options.silentFallbackWarn; 1069 | this._dateTimeFormatters = {}; 1070 | this._numberFormatters = {}; 1071 | this._path = new I18nPath(); 1072 | this._dataListeners = []; 1073 | this._preserveDirectiveContent = options.preserveDirectiveContent === undefined 1074 | ? false 1075 | : !!options.preserveDirectiveContent; 1076 | this.pluralizationRules = options.pluralizationRules || {}; 1077 | 1078 | this._exist = function (message, key) { 1079 | if (!message || !key) { return false } 1080 | if (!isNull(this$1._path.getPathValue(message, key))) { return true } 1081 | // fallback for flat key 1082 | if (message[key]) { return true } 1083 | return false 1084 | }; 1085 | 1086 | this._initVM({ 1087 | locale: locale, 1088 | fallbackLocale: fallbackLocale, 1089 | messages: messages, 1090 | dateTimeFormats: dateTimeFormats, 1091 | numberFormats: numberFormats 1092 | }); 1093 | }; 1094 | 1095 | var prototypeAccessors = { vm: { configurable: true },messages: { configurable: true },dateTimeFormats: { configurable: true },numberFormats: { configurable: true },availableLocales: { configurable: true },locale: { configurable: true },fallbackLocale: { configurable: true },missing: { configurable: true },formatter: { configurable: true },silentTranslationWarn: { configurable: true },silentFallbackWarn: { configurable: true },preserveDirectiveContent: { configurable: true } }; 1096 | 1097 | VueI18n.prototype._initVM = function _initVM (data) { 1098 | var silent = Vue.config.silent; 1099 | Vue.config.silent = true; 1100 | this._vm = new Vue({ data: data }); 1101 | Vue.config.silent = silent; 1102 | }; 1103 | 1104 | VueI18n.prototype.destroyVM = function destroyVM () { 1105 | this._vm.$destroy(); 1106 | }; 1107 | 1108 | VueI18n.prototype.subscribeDataChanging = function subscribeDataChanging (vm) { 1109 | this._dataListeners.push(vm); 1110 | }; 1111 | 1112 | VueI18n.prototype.unsubscribeDataChanging = function unsubscribeDataChanging (vm) { 1113 | remove(this._dataListeners, vm); 1114 | }; 1115 | 1116 | VueI18n.prototype.watchI18nData = function watchI18nData () { 1117 | var self = this; 1118 | return this._vm.$watch('$data', function () { 1119 | var i = self._dataListeners.length; 1120 | while (i--) { 1121 | Vue.nextTick(function () { 1122 | self._dataListeners[i] && self._dataListeners[i].$forceUpdate(); 1123 | }); 1124 | } 1125 | }, { deep: true }) 1126 | }; 1127 | 1128 | VueI18n.prototype.watchLocale = function watchLocale () { 1129 | /* istanbul ignore if */ 1130 | if (!this._sync || !this._root) { return null } 1131 | var target = this._vm; 1132 | return this._root.$i18n.vm.$watch('locale', function (val) { 1133 | target.$set(target, 'locale', val); 1134 | target.$forceUpdate(); 1135 | }, { immediate: true }) 1136 | }; 1137 | 1138 | prototypeAccessors.vm.get = function () { return this._vm }; 1139 | 1140 | prototypeAccessors.messages.get = function () { return looseClone(this._getMessages()) }; 1141 | prototypeAccessors.dateTimeFormats.get = function () { return looseClone(this._getDateTimeFormats()) }; 1142 | prototypeAccessors.numberFormats.get = function () { return looseClone(this._getNumberFormats()) }; 1143 | prototypeAccessors.availableLocales.get = function () { return Object.keys(this.messages).sort() }; 1144 | 1145 | prototypeAccessors.locale.get = function () { return this._vm.locale }; 1146 | prototypeAccessors.locale.set = function (locale) { 1147 | this._vm.$set(this._vm, 'locale', locale); 1148 | }; 1149 | 1150 | prototypeAccessors.fallbackLocale.get = function () { return this._vm.fallbackLocale }; 1151 | prototypeAccessors.fallbackLocale.set = function (locale) { 1152 | this._vm.$set(this._vm, 'fallbackLocale', locale); 1153 | }; 1154 | 1155 | prototypeAccessors.missing.get = function () { return this._missing }; 1156 | prototypeAccessors.missing.set = function (handler) { this._missing = handler; }; 1157 | 1158 | prototypeAccessors.formatter.get = function () { return this._formatter }; 1159 | prototypeAccessors.formatter.set = function (formatter) { this._formatter = formatter; }; 1160 | 1161 | prototypeAccessors.silentTranslationWarn.get = function () { return this._silentTranslationWarn }; 1162 | prototypeAccessors.silentTranslationWarn.set = function (silent) { this._silentTranslationWarn = silent; }; 1163 | 1164 | prototypeAccessors.silentFallbackWarn.get = function () { return this._silentFallbackWarn }; 1165 | prototypeAccessors.silentFallbackWarn.set = function (silent) { this._silentFallbackWarn = silent; }; 1166 | 1167 | prototypeAccessors.preserveDirectiveContent.get = function () { return this._preserveDirectiveContent }; 1168 | prototypeAccessors.preserveDirectiveContent.set = function (preserve) { this._preserveDirectiveContent = preserve; }; 1169 | 1170 | VueI18n.prototype._getMessages = function _getMessages () { return this._vm.messages }; 1171 | VueI18n.prototype._getDateTimeFormats = function _getDateTimeFormats () { return this._vm.dateTimeFormats }; 1172 | VueI18n.prototype._getNumberFormats = function _getNumberFormats () { return this._vm.numberFormats }; 1173 | 1174 | VueI18n.prototype._warnDefault = function _warnDefault (locale, key, result, vm, values) { 1175 | if (!isNull(result)) { return result } 1176 | if (this._missing) { 1177 | var missingRet = this._missing.apply(null, [locale, key, vm, values]); 1178 | if (typeof missingRet === 'string') { 1179 | return missingRet 1180 | } 1181 | } else { 1182 | if (!this._silentTranslationWarn) { 1183 | warn( 1184 | "Cannot translate the value of keypath '" + key + "'. " + 1185 | 'Use the value of keypath as default.' 1186 | ); 1187 | } 1188 | } 1189 | return key 1190 | }; 1191 | 1192 | VueI18n.prototype._isFallbackRoot = function _isFallbackRoot (val) { 1193 | return !val && !isNull(this._root) && this._fallbackRoot 1194 | }; 1195 | 1196 | VueI18n.prototype._isSilentFallback = function _isSilentFallback (locale) { 1197 | return this._silentFallbackWarn && (this._isFallbackRoot() || locale !== this.fallbackLocale) 1198 | }; 1199 | 1200 | VueI18n.prototype._interpolate = function _interpolate ( 1201 | locale, 1202 | message, 1203 | key, 1204 | host, 1205 | interpolateMode, 1206 | values, 1207 | visitedLinkStack 1208 | ) { 1209 | if (!message) { return null } 1210 | 1211 | var pathRet = this._path.getPathValue(message, key); 1212 | if (Array.isArray(pathRet) || isPlainObject(pathRet)) { return pathRet } 1213 | 1214 | var ret; 1215 | if (isNull(pathRet)) { 1216 | /* istanbul ignore else */ 1217 | if (isPlainObject(message)) { 1218 | ret = message[key]; 1219 | if (typeof ret !== 'string') { 1220 | if (!this._silentTranslationWarn && !this._isSilentFallback(locale)) { 1221 | warn(("Value of key '" + key + "' is not a string!")); 1222 | } 1223 | return null 1224 | } 1225 | } else { 1226 | return null 1227 | } 1228 | } else { 1229 | /* istanbul ignore else */ 1230 | if (typeof pathRet === 'string') { 1231 | ret = pathRet; 1232 | } else { 1233 | if (!this._silentTranslationWarn && !this._isSilentFallback(locale)) { 1234 | warn(("Value of key '" + key + "' is not a string!")); 1235 | } 1236 | return null 1237 | } 1238 | } 1239 | 1240 | // Check for the existence of links within the translated string 1241 | if (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0) { 1242 | ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack); 1243 | } 1244 | 1245 | return this._render(ret, interpolateMode, values, key) 1246 | }; 1247 | 1248 | VueI18n.prototype._link = function _link ( 1249 | locale, 1250 | message, 1251 | str, 1252 | host, 1253 | interpolateMode, 1254 | values, 1255 | visitedLinkStack 1256 | ) { 1257 | var ret = str; 1258 | 1259 | // Match all the links within the local 1260 | // We are going to replace each of 1261 | // them with its translation 1262 | var matches = ret.match(linkKeyMatcher); 1263 | for (var idx in matches) { 1264 | // ie compatible: filter custom array 1265 | // prototype method 1266 | if (!matches.hasOwnProperty(idx)) { 1267 | continue 1268 | } 1269 | var link = matches[idx]; 1270 | var linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher); 1271 | var linkPrefix = linkKeyPrefixMatches[0]; 1272 | var formatterName = linkKeyPrefixMatches[1]; 1273 | 1274 | // Remove the leading @:, @.case: and the brackets 1275 | var linkPlaceholder = link.replace(linkPrefix, '').replace(bracketsMatcher, ''); 1276 | 1277 | if (visitedLinkStack.includes(linkPlaceholder)) { 1278 | { 1279 | warn(("Circular reference found. \"" + link + "\" is already visited in the chain of " + (visitedLinkStack.reverse().join(' <- ')))); 1280 | } 1281 | return ret 1282 | } 1283 | visitedLinkStack.push(linkPlaceholder); 1284 | 1285 | // Translate the link 1286 | var translated = this._interpolate( 1287 | locale, message, linkPlaceholder, host, 1288 | interpolateMode === 'raw' ? 'string' : interpolateMode, 1289 | interpolateMode === 'raw' ? undefined : values, 1290 | visitedLinkStack 1291 | ); 1292 | 1293 | if (this._isFallbackRoot(translated)) { 1294 | if (!this._silentTranslationWarn) { 1295 | warn(("Fall back to translate the link placeholder '" + linkPlaceholder + "' with root locale.")); 1296 | } 1297 | /* istanbul ignore if */ 1298 | if (!this._root) { throw Error('unexpected error') } 1299 | var root = this._root.$i18n; 1300 | translated = root._translate( 1301 | root._getMessages(), root.locale, root.fallbackLocale, 1302 | linkPlaceholder, host, interpolateMode, values 1303 | ); 1304 | } 1305 | translated = this._warnDefault( 1306 | locale, linkPlaceholder, translated, host, 1307 | Array.isArray(values) ? values : [values] 1308 | ); 1309 | if (formatters.hasOwnProperty(formatterName)) { 1310 | translated = formatters[formatterName](translated); 1311 | } 1312 | 1313 | visitedLinkStack.pop(); 1314 | 1315 | // Replace the link with the translated 1316 | ret = !translated ? ret : ret.replace(link, translated); 1317 | } 1318 | 1319 | return ret 1320 | }; 1321 | 1322 | VueI18n.prototype._render = function _render (message, interpolateMode, values, path) { 1323 | var ret = this._formatter.interpolate(message, values, path); 1324 | 1325 | // If the custom formatter refuses to work - apply the default one 1326 | if (!ret) { 1327 | ret = defaultFormatter.interpolate(message, values, path); 1328 | } 1329 | 1330 | // if interpolateMode is **not** 'string' ('row'), 1331 | // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter 1332 | return interpolateMode === 'string' ? ret.join('') : ret 1333 | }; 1334 | 1335 | VueI18n.prototype._translate = function _translate ( 1336 | messages, 1337 | locale, 1338 | fallback, 1339 | key, 1340 | host, 1341 | interpolateMode, 1342 | args 1343 | ) { 1344 | var res = 1345 | this._interpolate(locale, messages[locale], key, host, interpolateMode, args, [key]); 1346 | if (!isNull(res)) { return res } 1347 | 1348 | res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args, [key]); 1349 | if (!isNull(res)) { 1350 | if (!this._silentTranslationWarn && !this._silentFallbackWarn) { 1351 | warn(("Fall back to translate the keypath '" + key + "' with '" + fallback + "' locale.")); 1352 | } 1353 | return res 1354 | } else { 1355 | return null 1356 | } 1357 | }; 1358 | 1359 | VueI18n.prototype._t = function _t (key, _locale, messages, host) { 1360 | var ref; 1361 | 1362 | var values = [], len = arguments.length - 4; 1363 | while ( len-- > 0 ) values[ len ] = arguments[ len + 4 ]; 1364 | if (!key) { return '' } 1365 | 1366 | var parsedArgs = parseArgs.apply(void 0, values); 1367 | var locale = parsedArgs.locale || _locale; 1368 | 1369 | var ret = this._translate( 1370 | messages, locale, this.fallbackLocale, key, 1371 | host, 'string', parsedArgs.params 1372 | ); 1373 | if (this._isFallbackRoot(ret)) { 1374 | if (!this._silentTranslationWarn && !this._silentFallbackWarn) { 1375 | warn(("Fall back to translate the keypath '" + key + "' with root locale.")); 1376 | } 1377 | /* istanbul ignore if */ 1378 | if (!this._root) { throw Error('unexpected error') } 1379 | return (ref = this._root).$t.apply(ref, [ key ].concat( values )) 1380 | } else { 1381 | return this._warnDefault(locale, key, ret, host, values) 1382 | } 1383 | }; 1384 | 1385 | VueI18n.prototype.t = function t (key) { 1386 | var ref; 1387 | 1388 | var values = [], len = arguments.length - 1; 1389 | while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ]; 1390 | return (ref = this)._t.apply(ref, [ key, this.locale, this._getMessages(), null ].concat( values )) 1391 | }; 1392 | 1393 | VueI18n.prototype._i = function _i (key, locale, messages, host, values) { 1394 | var ret = 1395 | this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values); 1396 | if (this._isFallbackRoot(ret)) { 1397 | if (!this._silentTranslationWarn) { 1398 | warn(("Fall back to interpolate the keypath '" + key + "' with root locale.")); 1399 | } 1400 | if (!this._root) { throw Error('unexpected error') } 1401 | return this._root.$i18n.i(key, locale, values) 1402 | } else { 1403 | return this._warnDefault(locale, key, ret, host, [values]) 1404 | } 1405 | }; 1406 | 1407 | VueI18n.prototype.i = function i (key, locale, values) { 1408 | /* istanbul ignore if */ 1409 | if (!key) { return '' } 1410 | 1411 | if (typeof locale !== 'string') { 1412 | locale = this.locale; 1413 | } 1414 | 1415 | return this._i(key, locale, this._getMessages(), null, values) 1416 | }; 1417 | 1418 | VueI18n.prototype._tc = function _tc ( 1419 | key, 1420 | _locale, 1421 | messages, 1422 | host, 1423 | choice 1424 | ) { 1425 | var ref; 1426 | 1427 | var values = [], len = arguments.length - 5; 1428 | while ( len-- > 0 ) values[ len ] = arguments[ len + 5 ]; 1429 | if (!key) { return '' } 1430 | if (choice === undefined) { 1431 | choice = 1; 1432 | } 1433 | 1434 | var predefined = { 'count': choice, 'n': choice }; 1435 | var parsedArgs = parseArgs.apply(void 0, values); 1436 | parsedArgs.params = Object.assign(predefined, parsedArgs.params); 1437 | values = parsedArgs.locale === null ? [parsedArgs.params] : [parsedArgs.locale, parsedArgs.params]; 1438 | return this.fetchChoice((ref = this)._t.apply(ref, [ key, _locale, messages, host ].concat( values )), choice) 1439 | }; 1440 | 1441 | VueI18n.prototype.fetchChoice = function fetchChoice (message, choice) { 1442 | /* istanbul ignore if */ 1443 | if (!message && typeof message !== 'string') { return null } 1444 | var choices = message.split('|'); 1445 | 1446 | choice = this.getChoiceIndex(choice, choices.length); 1447 | if (!choices[choice]) { return message } 1448 | return choices[choice].trim() 1449 | }; 1450 | 1451 | /** 1452 | * @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)` 1453 | * @param choicesLength {number} an overall amount of available choices 1454 | * @returns a final choice index 1455 | */ 1456 | VueI18n.prototype.getChoiceIndex = function getChoiceIndex (choice, choicesLength) { 1457 | // Default (old) getChoiceIndex implementation - english-compatible 1458 | var defaultImpl = function (_choice, _choicesLength) { 1459 | _choice = Math.abs(_choice); 1460 | 1461 | if (_choicesLength === 2) { 1462 | return _choice 1463 | ? _choice > 1 1464 | ? 1 1465 | : 0 1466 | : 1 1467 | } 1468 | 1469 | return _choice ? Math.min(_choice, 2) : 0 1470 | }; 1471 | 1472 | if (this.locale in this.pluralizationRules) { 1473 | return this.pluralizationRules[this.locale].apply(this, [choice, choicesLength]) 1474 | } else { 1475 | return defaultImpl(choice, choicesLength) 1476 | } 1477 | }; 1478 | 1479 | VueI18n.prototype.tc = function tc (key, choice) { 1480 | var ref; 1481 | 1482 | var values = [], len = arguments.length - 2; 1483 | while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ]; 1484 | return (ref = this)._tc.apply(ref, [ key, this.locale, this._getMessages(), null, choice ].concat( values )) 1485 | }; 1486 | 1487 | VueI18n.prototype._te = function _te (key, locale, messages) { 1488 | var args = [], len = arguments.length - 3; 1489 | while ( len-- > 0 ) args[ len ] = arguments[ len + 3 ]; 1490 | 1491 | var _locale = parseArgs.apply(void 0, args).locale || locale; 1492 | return this._exist(messages[_locale], key) 1493 | }; 1494 | 1495 | VueI18n.prototype.te = function te (key, locale) { 1496 | return this._te(key, this.locale, this._getMessages(), locale) 1497 | }; 1498 | 1499 | VueI18n.prototype.getLocaleMessage = function getLocaleMessage (locale) { 1500 | return looseClone(this._vm.messages[locale] || {}) 1501 | }; 1502 | 1503 | VueI18n.prototype.setLocaleMessage = function setLocaleMessage (locale, message) { 1504 | this._vm.$set(this._vm.messages, locale, message); 1505 | }; 1506 | 1507 | VueI18n.prototype.mergeLocaleMessage = function mergeLocaleMessage (locale, message) { 1508 | this._vm.$set(this._vm.messages, locale, merge(this._vm.messages[locale] || {}, message)); 1509 | }; 1510 | 1511 | VueI18n.prototype.getDateTimeFormat = function getDateTimeFormat (locale) { 1512 | return looseClone(this._vm.dateTimeFormats[locale] || {}) 1513 | }; 1514 | 1515 | VueI18n.prototype.setDateTimeFormat = function setDateTimeFormat (locale, format) { 1516 | this._vm.$set(this._vm.dateTimeFormats, locale, format); 1517 | }; 1518 | 1519 | VueI18n.prototype.mergeDateTimeFormat = function mergeDateTimeFormat (locale, format) { 1520 | this._vm.$set(this._vm.dateTimeFormats, locale, merge(this._vm.dateTimeFormats[locale] || {}, format)); 1521 | }; 1522 | 1523 | VueI18n.prototype._localizeDateTime = function _localizeDateTime ( 1524 | value, 1525 | locale, 1526 | fallback, 1527 | dateTimeFormats, 1528 | key 1529 | ) { 1530 | var _locale = locale; 1531 | var formats = dateTimeFormats[_locale]; 1532 | 1533 | // fallback locale 1534 | if (isNull(formats) || isNull(formats[key])) { 1535 | if (!this._silentTranslationWarn) { 1536 | warn(("Fall back to '" + fallback + "' datetime formats from '" + locale + " datetime formats.")); 1537 | } 1538 | _locale = fallback; 1539 | formats = dateTimeFormats[_locale]; 1540 | } 1541 | 1542 | if (isNull(formats) || isNull(formats[key])) { 1543 | return null 1544 | } else { 1545 | var format = formats[key]; 1546 | var id = _locale + "__" + key; 1547 | var formatter = this._dateTimeFormatters[id]; 1548 | if (!formatter) { 1549 | formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format); 1550 | } 1551 | return formatter.format(value) 1552 | } 1553 | }; 1554 | 1555 | VueI18n.prototype._d = function _d (value, locale, key) { 1556 | /* istanbul ignore if */ 1557 | if (!VueI18n.availabilities.dateTimeFormat) { 1558 | warn('Cannot format a Date value due to not supported Intl.DateTimeFormat.'); 1559 | return '' 1560 | } 1561 | 1562 | if (!key) { 1563 | return new Intl.DateTimeFormat(locale).format(value) 1564 | } 1565 | 1566 | var ret = 1567 | this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key); 1568 | if (this._isFallbackRoot(ret)) { 1569 | if (!this._silentTranslationWarn) { 1570 | warn(("Fall back to datetime localization of root: key '" + key + "' .")); 1571 | } 1572 | /* istanbul ignore if */ 1573 | if (!this._root) { throw Error('unexpected error') } 1574 | return this._root.$i18n.d(value, key, locale) 1575 | } else { 1576 | return ret || '' 1577 | } 1578 | }; 1579 | 1580 | VueI18n.prototype.d = function d (value) { 1581 | var args = [], len = arguments.length - 1; 1582 | while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; 1583 | 1584 | var locale = this.locale; 1585 | var key = null; 1586 | 1587 | if (args.length === 1) { 1588 | if (typeof args[0] === 'string') { 1589 | key = args[0]; 1590 | } else if (isObject(args[0])) { 1591 | if (args[0].locale) { 1592 | locale = args[0].locale; 1593 | } 1594 | if (args[0].key) { 1595 | key = args[0].key; 1596 | } 1597 | } 1598 | } else if (args.length === 2) { 1599 | if (typeof args[0] === 'string') { 1600 | key = args[0]; 1601 | } 1602 | if (typeof args[1] === 'string') { 1603 | locale = args[1]; 1604 | } 1605 | } 1606 | 1607 | return this._d(value, locale, key) 1608 | }; 1609 | 1610 | VueI18n.prototype.getNumberFormat = function getNumberFormat (locale) { 1611 | return looseClone(this._vm.numberFormats[locale] || {}) 1612 | }; 1613 | 1614 | VueI18n.prototype.setNumberFormat = function setNumberFormat (locale, format) { 1615 | this._vm.$set(this._vm.numberFormats, locale, format); 1616 | }; 1617 | 1618 | VueI18n.prototype.mergeNumberFormat = function mergeNumberFormat (locale, format) { 1619 | this._vm.$set(this._vm.numberFormats, locale, merge(this._vm.numberFormats[locale] || {}, format)); 1620 | }; 1621 | 1622 | VueI18n.prototype._getNumberFormatter = function _getNumberFormatter ( 1623 | value, 1624 | locale, 1625 | fallback, 1626 | numberFormats, 1627 | key, 1628 | options 1629 | ) { 1630 | var _locale = locale; 1631 | var formats = numberFormats[_locale]; 1632 | 1633 | // fallback locale 1634 | if (isNull(formats) || isNull(formats[key])) { 1635 | if (!this._silentTranslationWarn) { 1636 | warn(("Fall back to '" + fallback + "' number formats from '" + locale + " number formats.")); 1637 | } 1638 | _locale = fallback; 1639 | formats = numberFormats[_locale]; 1640 | } 1641 | 1642 | if (isNull(formats) || isNull(formats[key])) { 1643 | return null 1644 | } else { 1645 | var format = formats[key]; 1646 | 1647 | var formatter; 1648 | if (options) { 1649 | // If options specified - create one time number formatter 1650 | formatter = new Intl.NumberFormat(_locale, Object.assign({}, format, options)); 1651 | } else { 1652 | var id = _locale + "__" + key; 1653 | formatter = this._numberFormatters[id]; 1654 | if (!formatter) { 1655 | formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format); 1656 | } 1657 | } 1658 | return formatter 1659 | } 1660 | }; 1661 | 1662 | VueI18n.prototype._n = function _n (value, locale, key, options) { 1663 | /* istanbul ignore if */ 1664 | if (!VueI18n.availabilities.numberFormat) { 1665 | { 1666 | warn('Cannot format a Number value due to not supported Intl.NumberFormat.'); 1667 | } 1668 | return '' 1669 | } 1670 | 1671 | if (!key) { 1672 | var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options); 1673 | return nf.format(value) 1674 | } 1675 | 1676 | var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options); 1677 | var ret = formatter && formatter.format(value); 1678 | if (this._isFallbackRoot(ret)) { 1679 | if (!this._silentTranslationWarn) { 1680 | warn(("Fall back to number localization of root: key '" + key + "' .")); 1681 | } 1682 | /* istanbul ignore if */ 1683 | if (!this._root) { throw Error('unexpected error') } 1684 | return this._root.$i18n.n(value, Object.assign({}, { key: key, locale: locale }, options)) 1685 | } else { 1686 | return ret || '' 1687 | } 1688 | }; 1689 | 1690 | VueI18n.prototype.n = function n (value) { 1691 | var args = [], len = arguments.length - 1; 1692 | while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; 1693 | 1694 | var locale = this.locale; 1695 | var key = null; 1696 | var options = null; 1697 | 1698 | if (args.length === 1) { 1699 | if (typeof args[0] === 'string') { 1700 | key = args[0]; 1701 | } else if (isObject(args[0])) { 1702 | if (args[0].locale) { 1703 | locale = args[0].locale; 1704 | } 1705 | if (args[0].key) { 1706 | key = args[0].key; 1707 | } 1708 | 1709 | // Filter out number format options only 1710 | options = Object.keys(args[0]).reduce(function (acc, key) { 1711 | var obj; 1712 | 1713 | if (numberFormatKeys.includes(key)) { 1714 | return Object.assign({}, acc, ( obj = {}, obj[key] = args[0][key], obj )) 1715 | } 1716 | return acc 1717 | }, null); 1718 | } 1719 | } else if (args.length === 2) { 1720 | if (typeof args[0] === 'string') { 1721 | key = args[0]; 1722 | } 1723 | if (typeof args[1] === 'string') { 1724 | locale = args[1]; 1725 | } 1726 | } 1727 | 1728 | return this._n(value, locale, key, options) 1729 | }; 1730 | 1731 | VueI18n.prototype._ntp = function _ntp (value, locale, key, options) { 1732 | /* istanbul ignore if */ 1733 | if (!VueI18n.availabilities.numberFormat) { 1734 | { 1735 | warn('Cannot format to parts a Number value due to not supported Intl.NumberFormat.'); 1736 | } 1737 | return [] 1738 | } 1739 | 1740 | if (!key) { 1741 | var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options); 1742 | return nf.formatToParts(value) 1743 | } 1744 | 1745 | var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options); 1746 | var ret = formatter && formatter.formatToParts(value); 1747 | if (this._isFallbackRoot(ret)) { 1748 | if (!this._silentTranslationWarn) { 1749 | warn(("Fall back to format number to parts of root: key '" + key + "' .")); 1750 | } 1751 | /* istanbul ignore if */ 1752 | if (!this._root) { throw Error('unexpected error') } 1753 | return this._root.$i18n._ntp(value, locale, key, options) 1754 | } else { 1755 | return ret || [] 1756 | } 1757 | }; 1758 | 1759 | Object.defineProperties( VueI18n.prototype, prototypeAccessors ); 1760 | 1761 | var availabilities; 1762 | // $FlowFixMe 1763 | Object.defineProperty(VueI18n, 'availabilities', { 1764 | get: function get () { 1765 | if (!availabilities) { 1766 | var intlDefined = typeof Intl !== 'undefined'; 1767 | availabilities = { 1768 | dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined', 1769 | numberFormat: intlDefined && typeof Intl.NumberFormat !== 'undefined' 1770 | }; 1771 | } 1772 | 1773 | return availabilities 1774 | } 1775 | }); 1776 | 1777 | VueI18n.install = install; 1778 | VueI18n.version = '8.10.0'; 1779 | 1780 | return VueI18n; 1781 | 1782 | }))); 1783 | -------------------------------------------------------------------------------- /lang/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | lang: 'zh', 3 | loading: '加载中...', 4 | title: '广告机', 5 | login: { 6 | button: '扫码录入', 7 | more: '更多' 8 | 9 | }, 10 | password: { 11 | placeholder: '请输入设备密码', 12 | change: '修改密码', 13 | oldPswd: '请输入旧密码', 14 | newPswd: '请输入新密码', 15 | submit: '提交', 16 | confirm: '确定', 17 | cancel: '取消' 18 | }, 19 | turnPage: { 20 | title: '翻页模式', 21 | last: '最后一副', 22 | first: '第一幅画', 23 | down: '下翻键向下翻页', 24 | // down: '下翻键向下翻页', 25 | up: '上翻键向上翻页' 26 | // up: '上翻键向上翻页' 27 | }, 28 | calibration: { 29 | title: '画面校正', 30 | click: '短按', 31 | Inching: '点动运行', 32 | pres: '长按', 33 | moving: '低速运行', 34 | 35 | }, 36 | other: { 37 | closing: '关闭中...', 38 | sureConnBLE: '请确保连接了蓝牙', 39 | corretTime: '是否自动校时?', 40 | y: '是', 41 | n: '否', 42 | login: '登录中', 43 | AutoCan: '自动模式可以进入', 44 | settingFail: '设置失败', 45 | offlineOrOther: '设备离线或异常', 46 | outLine: '设备不在线', 47 | setting1: '设置中', 48 | BLEList: '查询蓝牙列表', 49 | BLEConn: '蓝牙列表(点击连接设备)', 50 | BLENone: '暂无蓝牙设备', 51 | search: '搜索中', 52 | reconnection: '重连中', 53 | fail: '连接失败', 54 | checking: '检测中', 55 | corrected: '您当前连接的是此设备', 56 | conning: '连接中,请稍等', 57 | 58 | notStorage: '设备未入库', 59 | sucWait: '连接成功,请稍等', 60 | searchNone: '未搜索到设备/正在使用中', 61 | searchFail: '查询失败,请确保蓝牙可用/定位开启', 62 | stopMode: '急停模式', 63 | back: '手动自动键退出', 64 | save: '确认保存', 65 | toMenu: '返回退出', 66 | move: '键点击运行', 67 | enter: '按', 68 | setItems: '按确认键设置参数', 69 | sysTitle: '系统日期时间设置', 70 | modeTitle: '运行模式设置', 71 | host: '主机', 72 | single: '独立', 73 | test: '自检', 74 | client: '从机', 75 | delayTitle: '同步延时设置', 76 | copyTitle: '参数复制', 77 | copy: '复制中...', 78 | setIdTitle: '主板ID号设置' 79 | }, 80 | Manu: { 81 | title: '手动运行模式', 82 | title2: '自动运行模式' 83 | }, 84 | pageNum: { 85 | title: '画面幅数设置', 86 | set: '画面总数:', 87 | change: '键改变幅数' 88 | }, 89 | showTime: { 90 | all: '全部展示时间设置', 91 | single: '单幅展示时间设置', 92 | No: '第', 93 | set: '幅展示时间:' 94 | }, 95 | speedSet: { 96 | title: '运行速度设置', 97 | set: '运行速度为:', 98 | change: '键改变速度' 99 | }, 100 | timer: { 101 | title1: '开机定时A设置', 102 | title2: '开机定时B设置', 103 | title3: '关机定时A设置', 104 | title4: '关机定时B设置', 105 | title5: '开灯定时设置', 106 | title6: '关灯定时设置' 107 | 108 | }, 109 | stopPic: { 110 | title: '停机画面设置' 111 | }, 112 | parameters: { 113 | OV: '过压保护', 114 | OC: '过流保护', 115 | OT: '过温保护', 116 | UV: '欠压保护', 117 | UVT: '欠压保护时间' 118 | }, 119 | index: { 120 | auto: '自动', 121 | GPRS: '有线', 122 | BLE: '蓝牙', 123 | Speed: '速度', 124 | modelContent: '您确定要返回扫码页面吗?', 125 | manu: '手动', 126 | client: '从机', 127 | ok: '系统正常', 128 | OV: '过压保护', 129 | OC: '过流保护', 130 | OT: '过温保护 ', 131 | UV: '欠压保护', 132 | F: '连接失败', 133 | host: '主机', 134 | single: '独立', 135 | test: '自检', 136 | setBLEData: [ 137 | { id: 1, value: '搜索蓝牙设备' }, 138 | { id: 2, value: '断开连接' } 139 | ], 140 | homeData: { 141 | d_l: 0, 142 | d_r: 0, 143 | f_l: 0, 144 | f_r: 0, 145 | h_l: 0, 146 | h_r: 0, 147 | m_l: 0, 148 | m_r: 0, 149 | s_l: 0, 150 | s_r: 0, 151 | y_l: 0, 152 | y_r: 0, 153 | // // 年月日 154 | // year_l: 0, 155 | // year_r: 0, 156 | // month_l: 0, 157 | // month_r: 0, 158 | // day_l: 0, 159 | // day_r: 0, 160 | // // 时分秒 161 | // hour_l: 0, 162 | // hour_r: 0, 163 | // minute_l: 0, 164 | // minute_r: 0, 165 | // second_l: 0, 166 | // second_r: 0, 167 | // 第四行 168 | runStatus: 0, // 运行状态 0自动 7自检 3急停 4或1手动 2 或6 微调 8暂时未用到 169 | // 主从机模式 170 | model: 0, // 0-独立 1-主机,2-从机 171 | // 速度和类型 172 | speed: 0, // 速度为0-9 173 | /* 主板工作状态 174 | 0--系统正常 2--过温保护 3--过流保护 4--过压保护 5--欠压保护) 其它:连接失败 */ 175 | workStatus: 6, 176 | type: 1, // 1GPRS 2BLE 177 | // 当前画面 178 | nowPic: 1 179 | 180 | }, 181 | // 设置项数据 182 | setItemData: [ 183 | { id: 1, value: '画面副数' }, 184 | { id: 2, value: '展示时间' }, 185 | { id: 3, value: '定时时间' }, 186 | { id: 4, value: '运行速度' } 187 | ], 188 | setItemsData1: [ 189 | { id: 1, value: '画面副数' }, 190 | { id: 2, value: '展示时间' }, 191 | { id: 3, value: '定时时间' }, 192 | { id: 4, value: '运行速度' } 193 | ], 194 | setItemsData2: [ 195 | { id: 5, value: '停机画面' }, 196 | { id: 6, value: '系统时间' }, 197 | { id: 7, value: '运行模式' }, 198 | { id: 8, value: '同步延时' } 199 | ], 200 | setItemsData3: [ 201 | { id: 9, value: '参数复制' }, 202 | { id: 10, value: '主板ID号' }, 203 | { id: 11, value: '其他参数' }, 204 | { id: 12, value: '蓝牙设置' } 205 | ], 206 | // 展示时间数据 207 | showTimeData: [ 208 | { id: 1, value: '全部展示时间' }, 209 | { id: 2, value: '单幅展示时间' } 210 | ], 211 | // 定时设置 212 | timerData: [ 213 | { id: 1, value: '开机定时A' }, 214 | { id: 2, value: '关机定时A' }, 215 | { id: 3, value: '开机定时B' }, 216 | { id: 4, value: '关机定时B' }, 217 | ], 218 | timerData1: [ 219 | { id: 1, value: '开机定时A' }, 220 | { id: 2, value: '关机定时A' }, 221 | { id: 3, value: '开机定时B' }, 222 | { id: 4, value: '关机定时B' }, 223 | ], 224 | timerData2: [ 225 | { id: 5, value: '开灯定时' }, 226 | { id: 6, value: '关灯定时' }, 227 | ], 228 | // 其他参数设置数据 229 | otherParametersData: [ 230 | {id: 1, value: '过压保护'}, 231 | {id: 2, value: '过流保护'}, 232 | {id: 3, value: '过温保护'}, 233 | {id: 4, value: '欠压保护'} 234 | ], 235 | otherParametersData1: [ 236 | {id: 1, value: '过压保护'}, 237 | {id: 2, value: '过流保护'}, 238 | {id: 3, value: '过温保护'}, 239 | {id: 4, value: '欠压保护'} 240 | ], 241 | otherParametersData2: [ 242 | {id: 5, value: '欠压保护时间'} 243 | ], 244 | }, 245 | toast: '您取消了授权,登录失败', 246 | Toast: { 247 | complete: '请将信息填写完整', 248 | pswdSuc: '密码修改成功', 249 | fillPswd: '请填写密码', 250 | unKnown: '未知错误,请联系管理员', 251 | scanFalse: '扫码失败', 252 | scanFailed: '请确保设备码正确后重新扫码', 253 | systemErr: '系统异常', 254 | NotOpen: '暂未开通', 255 | modifie:'修改失败' 256 | } 257 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | import { http } from '@/utils/request/index.js' 5 | Vue.config.productionTip = false 6 | Vue.prototype.$http = http 7 | 8 | App.mpType = 'app' 9 | 10 | import i18n from './lang/index' 11 | Vue.prototype._i18n = i18n 12 | // import { conn } from './utils/socket/send.js' 13 | // conn() 14 | 15 | const app = new Vue({ 16 | i18n, 17 | ...App, 18 | 19 | }) 20 | app.$mount() 21 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "AdverPlayer", 3 | "appid" : "__UNI__1E99334", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueCompiler" : "uni-app", 12 | "splashscreen" : { 13 | "alwaysShowBeforeRender" : true, 14 | "waiting" : true, 15 | "autoclose" : true, 16 | "delay" : 0 17 | }, 18 | /* 模块配置 */ 19 | "modules" : {}, 20 | /* 应用发布信息 */ 21 | "distribute" : { 22 | /* android打包配置 */ 23 | "android" : { 24 | "permissions" : [ 25 | "", 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "", 41 | "", 42 | "", 43 | "", 44 | "", 45 | "", 46 | "" 47 | ] 48 | }, 49 | /* ios打包配置 */ 50 | "ios" : {}, 51 | /* SDK配置 */ 52 | "sdkConfigs" : {} 53 | } 54 | }, 55 | /* 快应用特有相关 */ 56 | "quickapp" : {}, 57 | /* 小程序特有相关 */ 58 | "mp-weixin" : { 59 | "appid" : "", 60 | "setting" : { 61 | "urlCheck" : false, 62 | "es6" : true, 63 | "postcss" : true, 64 | "minified" : true 65 | }, 66 | "usingComponents" : true, 67 | "permission" : {} 68 | }, 69 | "mp-alipay" : { 70 | "usingComponents" : true 71 | }, 72 | "mp-baidu" : { 73 | "usingComponents" : true 74 | }, 75 | "mp-toutiao" : { 76 | "usingComponents" : true 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AdverPlayer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "js-md5": { 8 | "version": "0.7.3", 9 | "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", 10 | "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AdverPlayer", 3 | "version": "1.0.0", 4 | "description": "1. 页面命名小驼峰\r 2. request 请求使用全局 ` this.$http `\r 3. 本地储存storage key 必须在‘/utils/storageTypes.js’ 里定义常量,使用时也必须引入常量\r 4. 微信小程序现无法做到*强制登录*,在明确需要token的页面必须使用‘utils/user’ 里 ` checkLogin `,判断是否登录。如必须登录,弹框询问用户是否去登陆,如用户点击确定,则调用‘utils/myUtils.js’ 里 ` redirectLogin ` 函数\r 5. 全局css命名空间` am `", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "js-md5": "^0.7.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | 4 | { 5 | "path": "pages/login/login", 6 | "permission": { 7 | "scope.userLocation": { 8 | "desc": "你的位置信息将用于小程序位置接口的效果展示" // 高速公路行驶持续后台定位 9 | } 10 | }, 11 | "style": { 12 | "navigationBarTitleText": "低功耗蓝牙" 13 | } 14 | } 15 | ], 16 | "globalStyle": { 17 | "navigationBarTextStyle": "#fff", 18 | "navigationBarTitleText": "低功耗蓝牙", 19 | "navigationBarBackgroundColor": "#1D1D1D", 20 | "backgroundColor": "#F8F8F8" 21 | }, 22 | "condition": { //模式配置,仅开发期间生效 23 | "current": 0, //当前激活的模式(list 的索引项) 24 | "list": [{ 25 | "name": "", //模式名称 26 | "path": "", //启动页面,必选 27 | "query": "" //启动参数,在页面的onLoad函数里面得到 28 | }] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages/login/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 163 | 164 | 185 | -------------------------------------------------------------------------------- /static/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/1.gif -------------------------------------------------------------------------------- /static/images/bg_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/bg_circle.png -------------------------------------------------------------------------------- /static/images/bg_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/bg_login.png -------------------------------------------------------------------------------- /static/images/big_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/big_btn.png -------------------------------------------------------------------------------- /static/images/bottom_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/bottom_btn.png -------------------------------------------------------------------------------- /static/images/bottom_btn_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/bottom_btn_tap.png -------------------------------------------------------------------------------- /static/images/circel_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/circel_btn.png -------------------------------------------------------------------------------- /static/images/circel_btn_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/circel_btn_tap.png -------------------------------------------------------------------------------- /static/images/left-tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/left-tap.png -------------------------------------------------------------------------------- /static/images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/left.png -------------------------------------------------------------------------------- /static/images/left_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/left_btn.png -------------------------------------------------------------------------------- /static/images/left_btn_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/left_btn_tap.png -------------------------------------------------------------------------------- /static/images/logo/LOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/logo/LOGO.png -------------------------------------------------------------------------------- /static/images/logo/LOGO1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/logo/LOGO1.png -------------------------------------------------------------------------------- /static/images/right-tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/right-tap.png -------------------------------------------------------------------------------- /static/images/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/right.png -------------------------------------------------------------------------------- /static/images/right_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/right_btn.png -------------------------------------------------------------------------------- /static/images/right_btn_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/right_btn_tap.png -------------------------------------------------------------------------------- /static/images/tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/tap.png -------------------------------------------------------------------------------- /static/images/top_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/top_btn.png -------------------------------------------------------------------------------- /static/images/top_btn_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/images/top_btn_tap.png -------------------------------------------------------------------------------- /static/neil-modal/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menglin1997/BLEConn/dea76b6666dc8d2c61a26d308c4b255b13dc2614/static/neil-modal/logo.png -------------------------------------------------------------------------------- /uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24upx; 43 | $uni-font-size-base:28upx; 44 | $uni-font-size-lg:32upx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40upx; 48 | $uni-img-size-base:52upx; 49 | $uni-img-size-lg:80upx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4upx; 53 | $uni-border-radius-base: 6upx; 54 | $uni-border-radius-lg: 12upx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20upx; 60 | $uni-spacing-row-lg: 30upx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8upx; 64 | $uni-spacing-col-base: 16upx; 65 | $uni-spacing-col-lg: 24upx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40upx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36upx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30upx; 77 | 78 | 79 | $am-active-color: #2dc9ac; // 激活色 绿色 80 | $am-ornamentally-color: #feb374; // 装饰色 橘色 -------------------------------------------------------------------------------- /utils/request/index.js: -------------------------------------------------------------------------------- 1 | import Request from './request' 2 | import { 3 | apiBaseUrl 4 | } from '@/utils/service.js' 5 | 6 | const http = new Request() 7 | 8 | http.setConfig((config) => { 9 | config.baseUrl = apiBaseUrl 10 | config.header = { 11 | ...config.header 12 | } 13 | return config 14 | }) 15 | 16 | 17 | http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */ 18 | config.header = { 19 | ...config.header, 20 | b: 1 21 | } 22 | 23 | return config 24 | }) 25 | 26 | http.interceptor.response((response) => { /* 请求之后拦截器 */ 27 | return response 28 | }, (response) => { // 请求错误做点什么 29 | return response 30 | }) 31 | 32 | export { 33 | http 34 | } 35 | -------------------------------------------------------------------------------- /utils/request/readme.md: -------------------------------------------------------------------------------- 1 | **插件使用说明** 2 | 3 | - 基于 Promise 对象实现更简单的 request 使用方式,支持请求和响应拦截 4 | - 支持全局挂载 5 | - 支持多个全局配置实例 6 | - 支持自定义验证器 7 | - 支持文件上传(如不使用可以删除class里upload 方法) 8 | - 支持` typescript `、` javascript ` 版本(如果不使用ts版本,则可以把luch-request-ts 文件夹删除) 9 | - 下载后把 http-request 文件夹放到项目 utils/ 目录下 10 | 11 | 12 | **Example** 13 | --- 14 | 创建实例 15 | 16 | ``` javascript 17 | const http = new Request(); 18 | ``` 19 | 20 | 执行` GET `请求 21 | 22 | ``` javascript 23 | http.get('/user/login', {params: {userName: 'name', password: '123456'}}).then(res => { 24 | 25 | }).catch(err => { 26 | 27 | }) 28 | // 局部修改配置,局部配置优先级高于全局配置 29 | http.get('/user/login', { 30 | params: {userName: 'name', password: '123456'}, /* 会加在url上 */ 31 | header: {}, /* 会覆盖全局header */ 32 | dataType: 'json', 33 | responseType: 'text' 34 | }).then(res => { 35 | 36 | }).catch(err => { 37 | 38 | }) 39 | ``` 40 | 执行` POST `请求 41 | 42 | ``` javascript 43 | http.post('/user/login', {userName: 'name', password: '123456'} ).then(res => { 44 | 45 | }).catch(err => { 46 | 47 | }) 48 | // 局部修改配置,局部配置优先级高于全局配置 49 | http.post('/user/login', {userName: 'name', password: '123456'}, { 50 | params: {}, /* 会加在url上 */ 51 | header: {}, /* 会覆盖全局header */ 52 | dataType: 'json', 53 | responseType: 'text' 54 | }).then(res => { 55 | 56 | }).catch(err => { 57 | 58 | }) 59 | ``` 60 | 执行` upload `请求 61 | 62 | ``` javascript 63 | http.upload('api/upload/img', { 64 | files: [], // 仅5+App支持 65 | fileType:'image/video/audio', // 仅支付宝小程序,且必填。 66 | filePath: '', // 要上传文件资源的路径。 67 | name: 'file', // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容 68 | header: {}, 69 | formData: {}, // HTTP 请求中其他额外的 form data 70 | }).then(res => { 71 | 72 | }).catch(err => { 73 | 74 | }) 75 | ``` 76 | **luch-request API** 77 | -- 78 | ``` javascript 79 | http.request({ 80 | method: 'POST', // 请求方法必须大写 81 | url: '/user/12345', 82 | data: { 83 | firstName: 'Fred', 84 | lastName: 'Flintstone' 85 | }, 86 | params: { // 会拼接到url上 87 | token: '1111' 88 | } 89 | }) 90 | 91 | 具体参数说明:[uni.uploadFile](https://uniapp.dcloud.io/api/request/network-file) 92 | http.upload('api/upload/img', { 93 | files: [], // 仅5+App支持 94 | fileType:'image/video/audio', // 仅支付宝小程序,且必填。 95 | filePath: '', // 要上传文件资源的路径。 96 | name: 'file', // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容 97 | header: {}, // 如填写,会覆盖全局header 98 | formData: {}, // HTTP 请求中其他额外的 form data 99 | }) 100 | ``` 101 | 102 | 103 | 请求方法别名 / 实例方法 104 | 105 | ``` javascript 106 | http.request(config) 107 | http.get(url[, config]) 108 | http.upload(url[, config]) 109 | http.delete(url[, data[, config]]) 110 | http.head(url[, data[, config]]) 111 | http.post(url[, data[, config]]) 112 | http.put(url[, data[, config]]) 113 | http.connect(url[, data[, config]]) 114 | http.options(url[, data[, config]]) 115 | http.trace(url[, data[, config]]) 116 | ``` 117 | 118 | **全局请求配置** 119 | -- 120 | ``` javascript 121 | { 122 | baseUrl: '', /* 全局根路径,需要注意,如果请求的路径为绝对路径,则不会应用baseUrl */ 123 | header: { 124 | 'Content-Type': 'application/json;charset=UTF-8' 125 | }, 126 | method: 'GET', 127 | dataType: 'json', 128 | responseType: 'text' 129 | } 130 | ``` 131 | 132 | 133 | 全局配置修改` setConfig ` 134 | 135 | ``` javascript 136 | /** 137 | * @description 修改全局默认配置 138 | * @param {Function} 139 | */ 140 | http.setConfig((config) => { /* config 为默认全局配置*/ 141 | config.baseUrl = 'http://www.bbb.cn'; /* 根域名 */ 142 | config.header = { 143 | a: 1, 144 | b: 2 145 | } 146 | return config 147 | }) 148 | ``` 149 | 150 | 自定义验证器` validateStatus ` 151 | 152 | ``` javascript 153 | /** 154 | * 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject) 155 | * @param { Number } statusCode - 请求响应体statusCode(只读) 156 | * @return { Boolean } 如果为true,则 resolve, 否则 reject 157 | */ 158 | http.validateStatus = (statusCode) => { // 默认 159 | return statusCode === 200 160 | } 161 | 162 | // 举个栗子 163 | http.validateStatus = (statusCode) => { 164 | return statusCode >= 200 && statusCode < 300 165 | } 166 | ``` 167 | 168 | **拦截器** 169 | -- 170 | 171 | 在请求之前拦截 172 | 173 | ``` javascript 174 | /** 175 | * @param { Function} cancel - 取消请求,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行; 不会进入响应拦截器 176 | * 177 | * @param {String} text ['handle cancel'| any] - catch((err) => {}) err.errMsg === 'handle cancel'。非必传,默认'handle cancel' 178 | * @cancel {Object} config - catch((err) => {}) err.config === config; 非必传,默认为request拦截器修改之前的config 179 | * function cancel(text, config) {} 180 | */ 181 | http.interceptor.request((config, cancel) => { /* cancel 为函数,如果调用会取消本次请求。需要注意:调用cancel,本次请求的catch仍会执行。必须return config */ 182 | config.header = { 183 | ...config.header, 184 | a: 1 185 | } 186 | /* 187 | if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行 188 | cancel('token 不存在', config) // 把修改后的config传入,之后响应就可以拿到修改后的config。 如果调用了cancel但是不传修改后的config,则catch((err) => {}) err.config 为request拦截器修改之前的config 189 | } 190 | */ 191 | return config; 192 | }) 193 | ``` 194 | 195 | 在请求之后拦截 196 | 197 | ``` javascript 198 | http.interceptor.response((response) => { /* 对响应成功做点什么 (statusCode === 200),必须return response*/ 199 | // if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject() 200 | // return Promise.reject(response) 201 | // } 202 | console.log(response) 203 | return response 204 | }, (response) => { /* 对响应错误做点什么 (statusCode !== 200),必须return response*/ 205 | console.log(response) 206 | return response 207 | }) 208 | ``` 209 | 210 | **typescript使用** 211 | -- 212 | 在` request.ts `里还暴露了五个接口 213 | ```javascript 214 | { 215 | options, // request 方法配置参数 216 | handleOptions, // get/post 方法配置参数 217 | config, // init 全局config接口(setConfig 参数接口) 218 | requestConfig, // 请求之前参数配置项 219 | response // 响应体 220 | } 221 | ``` 222 | 223 | **常见问题** 224 | -- 225 | 1. 为什么会请求两次? 226 | - 总有些小白问这些很那啥的问题,有两种可能,一种是‘post三次握手’(不知道的请先给个五星好评,然后打自己一巴掌,并问自己,为什么这都不知道),还有一种可能是`本地访问接口时跨域请求,所以浏览器会先发一个option 去预测能否成功,然后再发一个真正的请求`(没有自己观察请求头,Request Method,就跑来问的,请再打自己一巴掌,并问自己,为什么这都不知道,不知道也行,为什么不百度)。 227 | 2. 如何跨域? 228 | - 问的人不少,可以先百度了解一下。如何跨域 229 | 3. post 怎么传不了数组的参数啊? 230 | - uni-request
231 | 可以点击看一下uni-request 的api 文档,data支持的文件类型只有Object/String/ArrayBuffer这个真跟我没啥关系 0.0 232 | 4. 'Content-Type' 为什么要小写? 233 | - hbuilderX 更新至‘2.3.0.20190919’ 后,uni.request post请求,如果 ‘Content-Type’ 大写,就会在后面自动拼接‘ application/json’,请求头变成 234 | `Content-Type: application/json;charset=UTF-8 application/json`,导致后端无法解析类型,`Status Code 415`,post 请求失败。但是小写就不会出现这个问题。至于为什么我也没有深究,我现在也不清楚这是他们的bug,还是以后就这样规范了。我能做的只有立马兼容,至于后边uni官方会不会继续变动也不清楚。 235 | 236 | 237 | **tip** 238 | -- 239 | - 不想使用upload 可把class 里的upload 删除 240 | 241 | 242 | **issue** 243 | -- 244 | 有任何问题或者建议可以=> issue提交,先给个五星好评QAQ!! 245 | 246 | 247 | **作者想说** 248 | -- 249 | - 主体代码3kb 250 | - 目前该插件已经上项目,遇到任何问题请先检查自己的代码(排除新版本发布的情况)。最近新上了` typescript ` 版本,因为本人没使用过ts,所以写的不好的地方,还请见谅~ 251 | - 写代码很容易,为了让你们看懂写文档真的很lei 0.0 252 | - 最近发现有插件与我雷同,当初接触uni-app 就发现插件市场虽然有封装的不错的request库,但是都没有对多全局配置做处理,都是通过修改源码的方式配置。我首先推出通过class类,并仿照axios的api实现request请求库,并起名‘仿axios封装request网络请求库,支持拦截器全局配置’。他们虽然修改了部分代码,但是功能与性能并没有优化,反而使代码很冗余。希望能推出新的功能,和性能更加强悍的请求库。 253 | - 任何形式的‘参考’、‘借鉴’,请标明作者 254 | ```javascript 255 | luch-request 256 | ``` 257 | - 关于问问题 258 | 1. 首先请善于利用搜索引擎,不管百度,还是Google,遇到问题请先自己尝试解决。自己尝试过无法解决,再问。 259 | 2. 不要问类似为什么我的xx无法使用这种问题。请仔细阅读文档,检查代码,或者说明运行环境,把相关代码贴至评论或者发送至我的邮箱,还可以点击上面的issue提交,在里面提问,可能我在里面已经回答了。 260 | 3. 我的代码如果真的出现bug,或者你有好的建议、需求,可以提issue,我看到后会立即解决 261 | 4. 不要问一些弱智问题!!! 262 | - 如何问问题 263 | 1. 仔细阅读文档,检查代码 264 | 2. 说明运行环境,比如:app端 ios、android 版本号、手机机型、普遍现象还是个别现象(越详细越好) 265 | 3. 发出代码片段或者截图至邮箱(很重要) 266 | 4. 或者可以在上方的'issue提交' 里发出详细的问题描述 267 | 5. 以上都觉得解决不了你的问题,可以加QQ:`370306150` 268 | 269 | **土豪赞赏** 270 | -- 271 | 272 | 273 | ####创作不易,五星好评你懂得! 274 | -------------------------------------------------------------------------------- /utils/request/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Request 1.0.2 3 | * @Class Request 4 | * @description luch-request 1.0.2 http请求插件 5 | 6 | */ 7 | import { 8 | TOKEN 9 | } from '@/utils/storageTypes.js' 10 | export default class Request { 11 | config = { 12 | baseUrl: '', 13 | header: { 14 | 'content-type': 'application/json;charset=UTF-8' 15 | }, 16 | method: 'GET', 17 | dataType: 'json', 18 | responseType: 'text' 19 | } 20 | 21 | // uni.getlo 22 | // var token = getLocal(TOKEN) 23 | // console.log(Window.localStorage.getItem(TOKEN)) 24 | // if (Window.localStorage.getItem(TOKEN)) { 25 | // config.headers.token = Window.localStorage.getItem(TOKEN) 26 | // } 27 | // if (config.headers.token = `${store.state.token}`;) 28 | static posUrl (url) { /* 判断url是否为绝对路径 */ 29 | return /(http|https):\/\/([\w.]+\/?)\S*/.test(url) 30 | } 31 | 32 | static addQueryString (params) { 33 | let paramsData = '' 34 | Object.keys(params).forEach(function (key) { 35 | paramsData += key + '=' + params[key] + '&' 36 | }) 37 | return paramsData.substring(0, paramsData.length - 1) 38 | } 39 | 40 | /** 41 | * @property {Function} request 请求拦截器 42 | * @property {Function} response 响应拦截器 43 | * @type {{request: Request.interceptor.request, response: Request.interceptor.response}} 44 | */ 45 | interceptor = { 46 | /** 47 | * @param {Request~requestCallback} cb - 请求之前拦截,接收一个函数(config, cancel)=> {return config}。第一个参数为全局config,第二个参数为函数,调用则取消本次请求。 48 | */ 49 | request: (cb) => { 50 | if (cb) { 51 | this.requestBeforeFun = cb 52 | } 53 | }, 54 | /** 55 | * @param {Request~responseCallback} cb 响应拦截器,对响应数据做点什么 56 | * @param {Request~responseErrCallback} ecb 响应拦截器,对响应错误做点什么 57 | */ 58 | response: (cb, ecb) => { 59 | if (cb && ecb) { 60 | this.requestComFun = cb 61 | this.requestComFail = ecb 62 | } 63 | } 64 | } 65 | 66 | requestBeforeFun (config) { 67 | return config 68 | } 69 | 70 | requestComFun (response) { 71 | return response 72 | } 73 | 74 | requestComFail (response) { 75 | return response 76 | } 77 | 78 | /** 79 | * 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject) 80 | * @param { Number } statusCode - 请求响应体statusCode(只读) 81 | * @return { Boolean } 如果为true,则 resolve, 否则 reject 82 | */ 83 | validateStatus (statusCode) { 84 | return statusCode === 200 85 | } 86 | 87 | /** 88 | * @Function 89 | * @param {Request~setConfigCallback} f - 设置全局默认配置 90 | */ 91 | setConfig (f) { 92 | this.config = f(this.config) 93 | } 94 | 95 | /** 96 | * @Function 97 | * @param {Object} options - 请求配置项 98 | * @prop {String} options.url - 请求路径 99 | * @prop {Object} options.data - 请求参数 100 | * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型 101 | * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse 102 | * @prop {Object} [options.header = config.header] - 请求header 103 | * @prop {Object} [options.method = config.method] - 请求方法 104 | * @returns {Promise} 105 | */ 106 | async request (options = {}) { 107 | options.baseUrl = this.config.baseUrl 108 | options.dataType = options.dataType || this.config.dataType 109 | options.responseType = options.responseType || this.config.responseType 110 | options.url = options.url || '' 111 | options.data = options.data || {} 112 | options.params = options.params || {} 113 | options.header = options.header || this.config.header 114 | options.method = options.method || this.config.method 115 | return new Promise((resolve, reject) => { 116 | let next = true 117 | 118 | let handleRe = {} 119 | options.complete = (response) => { 120 | response.config = handleRe 121 | if (this.validateStatus(response.statusCode)) { // 成功 122 | response = this.requestComFun(response) 123 | resolve(response) 124 | } else { 125 | response = this.requestComFail(response) 126 | reject(response) 127 | } 128 | } 129 | const cancel = (t = 'handle cancel', config = options) => { 130 | const err = { 131 | errMsg: t, 132 | config: config 133 | } 134 | reject(err) 135 | next = false 136 | } 137 | 138 | handleRe = { ...this.requestBeforeFun(options, cancel) } 139 | const _config = { ...handleRe } 140 | if (!next) return 141 | 142 | let mergeUrl = Request.posUrl(options.url) ? options.url : (options.baseUrl + options.url) 143 | if (JSON.stringify(options.params) !== '{}') { 144 | const paramsH = Request.addQueryString(options.params) 145 | mergeUrl += mergeUrl.indexOf('?') === -1 ? `?${paramsH}` : `&${paramsH}` 146 | } 147 | _config.url = mergeUrl 148 | uni.request(_config) 149 | }) 150 | } 151 | 152 | get (url, options = {}) { 153 | return this.request({ 154 | url, 155 | method: 'GET', 156 | ...options 157 | }) 158 | } 159 | 160 | post (url, data, options = {}) { 161 | return this.request({ 162 | url, 163 | data, 164 | method: 'POST', 165 | ...options 166 | }) 167 | } 168 | 169 | // #ifndef MP-ALIPAY 170 | put (url, data, options = {}) { 171 | return this.request({ 172 | url, 173 | data, 174 | method: 'PUT', 175 | ...options 176 | }) 177 | } 178 | 179 | // #endif 180 | 181 | // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU 182 | delete (url, data, options = {}) { 183 | return this.request({ 184 | url, 185 | data, 186 | method: 'DELETE', 187 | ...options 188 | }) 189 | } 190 | 191 | // #endif 192 | 193 | // #ifdef APP-PLUS || H5 || MP-WEIXIN 194 | connect (url, data, options = {}) { 195 | return this.request({ 196 | url, 197 | data, 198 | method: 'CONNECT', 199 | ...options 200 | }) 201 | } 202 | 203 | // #endif 204 | 205 | // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU 206 | head (url, data, options = {}) { 207 | return this.request({ 208 | url, 209 | data, 210 | method: 'HEAD', 211 | ...options 212 | }) 213 | } 214 | 215 | // #endif 216 | 217 | // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU 218 | options (url, data, options = {}) { 219 | return this.request({ 220 | url, 221 | data, 222 | method: 'OPTIONS', 223 | ...options 224 | }) 225 | } 226 | 227 | // #endif 228 | 229 | // #ifdef APP-PLUS || H5 || MP-WEIXIN 230 | trace (url, data, options = {}) { 231 | return this.request({ 232 | url, 233 | data, 234 | method: 'TRACE', 235 | ...options 236 | }) 237 | } 238 | 239 | // #endif 240 | 241 | upload (url, { 242 | // #ifdef APP-PLUS 243 | files, 244 | // #endif 245 | // #ifdef MP-ALIPAY 246 | fileType, 247 | // #endif 248 | filePath, 249 | name, 250 | header, 251 | formData 252 | }) { 253 | return new Promise((resolve, reject) => { 254 | let next = true 255 | let handleRe = {} 256 | const pubConfig = { 257 | baseUrl: this.config.baseUrl, 258 | url, 259 | // #ifdef APP-PLUS 260 | files, 261 | // #endif 262 | // #ifdef MP-ALIPAY 263 | fileType, 264 | // #endif 265 | filePath, 266 | method: 'UPLOAD', 267 | name, 268 | header: header || this.config.header, 269 | formData, 270 | complete: (response) => { 271 | response.config = handleRe 272 | if (response.statusCode === 200) { // 成功 273 | response = this.requestComFun(response) 274 | resolve(response) 275 | } else { 276 | response = this.requestComFail(response) 277 | reject(response) 278 | } 279 | } 280 | } 281 | const cancel = (t = 'handle cancel', config = pubConfig) => { 282 | const err = { 283 | errMsg: t, 284 | config: config 285 | } 286 | reject(err) 287 | next = false 288 | } 289 | 290 | handleRe = { ...this.requestBeforeFun(pubConfig, cancel) } 291 | const _config = { ...handleRe } 292 | if (!next) return 293 | _config.url = Request.posUrl(url) ? url : (this.config.baseUrl + url) 294 | uni.uploadFile(_config) 295 | }) 296 | } 297 | } 298 | 299 | /** 300 | * setConfig回调 301 | * @return {Object} - 返回操作后的config 302 | * @callback Request~setConfigCallback 303 | * @param {Object} config - 全局默认config 304 | */ 305 | /** 306 | * 请求拦截器回调 307 | * @return {Object} - 返回操作后的config 308 | * @callback Request~requestCallback 309 | * @param {Object} config - 全局config 310 | * @param {Function} [cancel] - 取消请求钩子,调用会取消本次请求 311 | */ 312 | /** 313 | * 响应拦截器回调 314 | * @return {Object} - 返回操作后的response 315 | * @callback Request~responseCallback 316 | * @param {Object} response - 请求结果 response 317 | */ 318 | /** 319 | * 响应错误拦截器回调 320 | * @return {Object} - 返回操作后的response 321 | * @callback Request~responseErrCallback 322 | * @param {Object} response - 请求结果 response 323 | */ 324 | -------------------------------------------------------------------------------- /utils/service.js: -------------------------------------------------------------------------------- 1 | const apiBaseUrl = 'www.123.com' // api 根路径 2 | 3 | 4 | 5 | export { 6 | apiBaseUrl, 7 | 8 | } -------------------------------------------------------------------------------- /utils/socket/BLEConn.js: -------------------------------------------------------------------------------- 1 | let BLEList = [] 2 | // 蓝牙模块 3 | // 注意: 小程序端不支持蓝牙4.0以下的版本 4 | // 1. 初始化(搜索蓝牙列表) 5 | function inArray(arr, key, val) { 6 | for (let i = 0; i < arr.length; i++) { 7 | if (arr[i][key] === val) { 8 | return i 9 | } 10 | } 11 | return -1 12 | } 13 | export function getBlooth() { 14 | BLEList = [] 15 | return new Promise((resolve, reject) => { 16 | uni.openBluetoothAdapter({ 17 | success(res) { 18 | searchBlooth().then(res => { 19 | console.log(res, 'searchBlooth') 20 | uni.onBluetoothDeviceFound(function (devices) { 21 | console.log(devices, '搜索到蓝牙设备') 22 | // let result = BLEList.find(devices.devices[0]) 23 | // console.log(result, 'res') 24 | console.log(devices.devices[0]) 25 | let idx = inArray(BLEList, 'deviceId', devices.devices[0].deviceId) 26 | console.log(idx, 'idx') 27 | if (idx == '-1') { 28 | if (devices.devices[0].localName && (!(devices.devices[0].name) || devices.devices[0].name == '未知设备')) { 29 | devices.devices[0].name = devices.devices[0].localName 30 | BLEList.push(devices.devices[0]) 31 | } else if (devices.devices[0].name && devices.devices[0].name != '未知设备') { 32 | BLEList.push(devices.devices[0]) 33 | } 34 | } 35 | 36 | // BLEList.push(devices.devices[0]) 37 | // 5秒后停止搜索 38 | }) 39 | setTimeout(() => { 40 | DeviceFound().then(res => { 41 | console.log(res, 'DeviceFound') 42 | console.log(BLEList, 'BLEList') 43 | resolve(res) 44 | }).catch(err => { 45 | console.log(err, 'searchBlooth1') 46 | reject(err) 47 | }) 48 | }, 5000) 49 | }).catch(err => { 50 | console.log(err, 'DeviceFound1') 51 | reject(err) 52 | }) 53 | }, 54 | fail(err) { 55 | console.log(err, 'err') 56 | reject(err) 57 | } 58 | }) 59 | }) 60 | } 61 | // 2. 检查手机蓝牙开启状态 62 | function searchBlooth() { 63 | return new Promise((resolve, reject) => { 64 | uni.startBluetoothDevicesDiscovery({ 65 | success(res) { 66 | console.log(res, 'startBluetoothDevicesDiscovery') 67 | // DeviceFound() 68 | resolve(res) 69 | }, 70 | fail(err) { 71 | reject(err) 72 | } 73 | }) 74 | }) 75 | } 76 | 77 | // 3. 搜索蓝牙列表 78 | export function DeviceFound() { 79 | 80 | // uni.onBluetoothDeviceFound(function (devices) { 81 | // console.log(devices, '搜索到蓝牙设备') 82 | // // 5秒后停止搜索 83 | // }) 84 | // setTimeout(() => { 85 | stopBlueth() 86 | 87 | // 搜索到的设备列表 88 | return new Promise((resolve, reject) => { 89 | uni.getBluetoothDevices({ 90 | success(res) { 91 | console.log(res, '蓝牙列表') 92 | console.log(BLEList, 'BLEListBLEList') 93 | resolve({ 94 | devices: BLEList 95 | }) 96 | }, 97 | fail(err) { 98 | console.log(err, '错误的') 99 | 100 | reject(err) 101 | } 102 | }) 103 | }) 104 | 105 | // }, 5000); 106 | 107 | 108 | 109 | } 110 | // 4.停止搜索蓝牙 111 | function stopBlueth() { 112 | uni.stopBluetoothDevicesDiscovery({ 113 | success(res) { 114 | console.log(res, '蓝牙停止') 115 | } 116 | }) 117 | } 118 | 119 | // 5. 断开蓝牙连接 120 | export function closeBle() { 121 | let deviceId = uni.getStorageSync('BLECONNID') 122 | // console.log(deviceId, 'closeBle') 123 | if (deviceId) { 124 | uni.closeBLEConnection({ 125 | deviceId: deviceId, 126 | success(res) { 127 | // console.log(res) 128 | // 记得打开 129 | uni.removeStorageSync('RESCODE') 130 | }, 131 | fail: (err) => { 132 | // console.log(err) 133 | } 134 | }) 135 | // 断开蓝牙模块 136 | uni.closeBluetoothAdapter({ 137 | success(res) { 138 | console.log(res) 139 | } 140 | }) 141 | } 142 | } 143 | // 6.连接蓝牙(连接之前应该把之前连接的蓝牙设备断开) 144 | /* 145 | item,要连接的蓝牙 146 | uuid 147 | */ 148 | export function createBLE(item, uuid) { 149 | // 连接之前应该把之前连接的蓝牙设备断开 150 | // closeBle() 151 | return new Promise((resolve, reject) => { 152 | uni.createBLEConnection({ 153 | // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 154 | deviceId: item.deviceId, 155 | success(res) { 156 | console.log(res) 157 | // 监听蓝牙连接 158 | watchBle(item.name).then(res => { 159 | console.log(res) 160 | getAllService(item.deviceId, uuid).then(res => { 161 | console.log(res) 162 | getCharacteristics(item.deviceId, res.uuid).then(res => { 163 | console.log(res) 164 | resolve(res) 165 | }).catch(err => { 166 | reject(err) 167 | }) 168 | }).catch(err => { 169 | reject(err) 170 | }) 171 | }).catch(err => { 172 | reject(err) 173 | }) 174 | }, 175 | fail(err) { 176 | reject(err) 177 | } 178 | }) 179 | }) 180 | 181 | } 182 | // 7.监听蓝牙连接 183 | function watchBle(name) { 184 | return new Promise((resolve, reject) => { 185 | uni.onBLEConnectionStateChange(res => { 186 | // console.log(res, '监听蓝牙连接') 187 | // 该方法回调中可以用于处理连接意外断开等异常情况 188 | // console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`) 189 | if (res.deviceId && res.connected) { 190 | // 连接成功将本次连接的设备deviceId和name保存本地 191 | uni.setStorageSync('BLECONNID', res.deviceId) 192 | uni.setStorageSync('deviceName', name) 193 | // 获取所有服务 194 | setTimeout(() => { 195 | resolve(res) 196 | }, 500); 197 | } else { 198 | // uni.showToast({ 199 | // title: res.name + '断开', 200 | // icon: "none" 201 | // }) 202 | reject(res) 203 | } 204 | }) 205 | }) 206 | 207 | } 208 | // 8.获取所有服务 209 | function getAllService(deviceId, uuid) { 210 | return new Promise((resolve, reject) => { 211 | uni.getBLEDeviceServices({ 212 | // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 213 | deviceId, 214 | success: (res) => { 215 | console.log(res, 'res') 216 | res.services.forEach((item,index) => { 217 | // console.log(item, 'deviceid') 218 | // 这里的uuid'0000FFE0-0000-1000-8000-00805F9B34FB' 219 | if (item.isPrimary && item.uuid === uuid) { 220 | uni.setStorageSync("deviceId", deviceId) 221 | uni.setStorageSync("serviceId", item.uuid) 222 | resolve(item) 223 | } else if (index == (res.services).length) { 224 | console.log(index, item, 'getBLEDeviceServices') 225 | reject(res) 226 | } else { 227 | console.log(index, item, 'getBLEDeviceServices') 228 | reject(res) 229 | } 230 | }) 231 | 232 | // getCharacteristics(deviceId, res.services[0].uuid) 233 | }, 234 | fail: (err) => { 235 | // console.log(err) 236 | reject(err) 237 | } 238 | }) 239 | }) 240 | 241 | } 242 | // 9. 获取某个服务的特征值 243 | function getCharacteristics(deviceId, serviceId) { 244 | return new Promise((resolve, reject) => { 245 | uni.getBLEDeviceCharacteristics({ 246 | // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 247 | deviceId, 248 | // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取 249 | serviceId, 250 | success(res) { 251 | // console.log(res, 'getCharacteristics') 252 | // console.log('device getBLEDeviceCharacteristics:', res.characteristics) 253 | uni.showLoading({ 254 | title: "检测中...", 255 | icon: "loading", 256 | mask: true 257 | }) 258 | res.characteristics.forEach((item, index) => { 259 | if (item.properties.notify) { 260 | // 启用 notify 功能(成功 监听 失败 进行读取操作) 261 | uni.setStorageSync("characteristicId", item.uuid) 262 | // console.log(1) 263 | setTimeout(function() { 264 | uni.hideLoading() 265 | }, 500); 266 | // console.log(deviceId, serviceId, item.uuid, 'res.deviceId, res.serviceId, item.uuid') 267 | notify(deviceId, serviceId, item.uuid).then(res => { 268 | // console.log(res, 'notify') 269 | resolve(res) 270 | }) 271 | // watchNotify() 272 | 273 | } else if (index == (res.characteristics).length - 1 && !item.properties.write) { 274 | // console.log(index, item, 'fsakdfjslfj') 275 | setTimeout(function() { 276 | uni.hideLoading() 277 | uni.showToast({ 278 | title: "该设备不支持读和写", 279 | icon: "none" 280 | }) 281 | }, 500); 282 | reject(res) 283 | } 284 | }) 285 | }, 286 | fail(err) { 287 | reject(err) 288 | } 289 | }) 290 | }) 291 | 292 | } 293 | // 10. 启用 notify 功能 294 | export function notify(deviceId, serviceId, characteristicId) { 295 | return new Promise((resolve, reject) => { 296 | uni.notifyBLECharacteristicValueChange({ 297 | state: true, // 启用 notify 功能 298 | // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 299 | deviceId, 300 | // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取 301 | serviceId, 302 | // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取 303 | characteristicId, 304 | success(res) { 305 | // console.log('notifyBLECharacteristicValueChange success', res.errMsg) 306 | resolve(res) 307 | }, 308 | fail(err) { 309 | // console.log(err, 'notifuerr') 310 | // readBLE(deviceId, serviceId, characteristicId) 311 | reject(err) 312 | } 313 | }) 314 | }) 315 | 316 | } 317 | /** 318 | * 字符串转换为数组,个数为n,s为字符串 319 | 320 | */ 321 | function strToArr(s, g) { 322 | // var s = "051102003" 323 | var re = new RegExp(".{" + g +"}","g") 324 | var a = [] 325 | var n 326 | while ((n=re.exec(s)) != null){ 327 | a[a.length] = n[0] 328 | } 329 | return a 330 | } 331 | 332 | // 11 监听notify功能 333 | var packData = '' // 分包操作 334 | var first = '' // 开始部分 335 | var center = '' // 中间拼接部分 336 | var last = '' // 结尾部分 337 | var len = 0 // 包的长度 338 | // var flag = true // flag判断是否进行了分包操作 如果分包需要清空开始部分 中间部分 结尾部分 339 | export function watchNotify() { 340 | // ArrayBuffer转16进度字符串示例 341 | function ab2hex(buffer) { 342 | const hexArr = Array.prototype.map.call( 343 | new Uint8Array(buffer), 344 | function (bit) { 345 | return ('00' + bit.toString(16)).slice(-2) 346 | } 347 | ) 348 | return hexArr.join('') 349 | } 350 | 351 | return new Promise((resolve, reject) => { 352 | uni.onBLECharacteristicValueChange(function (res) { 353 | console.log(res) 354 | // 监听帧头帧尾 355 | var resCode1 = ab2hex(res.value) 356 | var resCode = resCode1.toUpperCase() // 收到蓝牙返回的命令(16进制) 357 | console.log(resCode, '收到的') 358 | /** 359 | * 360 | * 以下是我项目中的分包接收操作(可注释) 361 | * 362 | */ 363 | 364 | // 1. 如果开头结尾和帧头帧尾一致并且长度一致就保留(说明长度完整) 365 | if (resCode.substring(resCode.length - 2, resCode.length) == 'FE' && resCode.substring(0, 2) == '05') { 366 | let lenTo10 = parseInt(resCode.substring(2,resCode.length - 2).length)/2 367 | // 1.1找出内容16进制 368 | let lengthTo16 = lenTo10.toString(16) + '' 369 | // 2.1 对16进制进行大小写转换并且补零操作 370 | lengthTo16 = lengthTo16.length == 1 ? '0' + lengthTo16.toUpperCase() : lengthTo16.toUpperCase() 371 | let length1 = resCode.substring(2,4) 372 | console.log(lengthTo16, length1) 373 | if (lengthTo16 == length1) { 374 | if (resCode.substring(8,12) == 'F020') { 375 | uni.setStorageSync('RESCODE', resCode) 376 | console.log(resCode, 'F020') 377 | } else { 378 | uni.setStorageSync('RESPARAMS', resCode) 379 | } 380 | } 381 | 382 | } else if (resCode.substring(resCode.length - 2, resCode.length) != 'FE' && resCode.substring(0, 2) == '05') { 383 | // 2. 如果帧头对 帧尾不对 则为前边部分 384 | first = resCode 385 | 386 | console.log(first, '这个是开头部分first') 387 | } else if (resCode.substring(resCode.length - 2, resCode.length) != 'FE' && resCode.substring(0, 2) != '05') { 388 | // 3. 如果帧头不对 帧尾不对 则为中间部分 389 | center = center + resCode 390 | console.log(center, '中间部分center') 391 | } else if (resCode.substring(resCode.length - 2, resCode.length) == 'FE' && resCode.substring(0, 2) != '05') { 392 | // 4. 如果帧头不对 帧尾对 则为最后一部分 393 | last = resCode 394 | console.log(last, '最后一部分last') 395 | 396 | // 得到实际内容 397 | 398 | var content = first + center + last 399 | console.log(content, '实际的内容') 400 | // 4.1找出内容16进制 401 | var resLen = (content.length - 4) / 2 // 减去4是剪掉帧头和帧尾,除以2是为了得到字节 402 | console.log(resLen, '实际的长度10进制') 403 | var lenTo16 = resLen.toString(16) + '' 404 | // 4.2 对16进制进行大小写转换并且补零操作 405 | lenTo16 = lenTo16.length == 1 ? '0' + lenTo16.toUpperCase() : lenTo16.toUpperCase() 406 | console.log(lenTo16, 'lenTo16') 407 | // 得到包的长度 408 | len = content.substring(2,4) 409 | console.log(len, 'len') 410 | // 判断是否得到长度和实际长度相等 411 | if (lenTo16 == len) { 412 | if (content.substring(8,12) == 'F020') { 413 | uni.setStorageSync('RESCODE', content) 414 | console.log(content, 'F020') 415 | } else { 416 | uni.setStorageSync('RESPARAMS', content) 417 | console.log(content, '!F020') 418 | } 419 | } else { 420 | // 如果不相等, 找一下字符串中间有没有FE 然后再进行长度 帧头帧尾判断 421 | 422 | // 1.截取帧尾的FE 求出帧尾之前是否有FE 423 | let substrFE = content.substring(0, content.length - 2) 424 | console.log(substrFE, 'substrFE') 425 | // 2.判断是否还有FE 426 | // 2.1 转换为两个字符一起的数组 427 | let hasFEArr = strToArr(substrFE, 2) 428 | // 2.2 找出是否有FE 并确定位置 429 | let hasFE = hasFEArr.indexOf('FE') 430 | 431 | console.log(hasFEArr, 'hasFEArr') 432 | console.log(hasFE, 'hasFE') 433 | // 如果有FE,截取到FE之处 判断帧头帧尾长度 434 | if (hasFE != -1) { 435 | // 将最后的数据保存到这个数组里面 436 | let lastConArr = [] 437 | // 得到05-FE的内容 438 | hasFEArr.forEach((item, index) => { 439 | if (index <= hasFE) { 440 | lastConArr.push(item) 441 | } 442 | }) 443 | console.log(lastConArr, 'lastConArr') 444 | // 判断是否是05开头FE结尾 445 | console.log(lastConArr[lastConArr.length - 1], 'lastConArr[lastConArr.length - 1]') 446 | console.log(lastConArr[0], 'lastConArr[0]') 447 | if (lastConArr[0] == '05' && lastConArr[lastConArr.length - 1] == 'FE') { 448 | // 判断得到的长度与实际长度 449 | let getLen = lastConArr[1] 450 | console.log(getLen, 'getLen') 451 | // 实际长度 并且转换为十六进制字符串 452 | let resLen = (lastConArr.length - 2).toString(16) + '' 453 | console.log(resLen, 'resLen1') 454 | // 实际长度补零操作 455 | resLen = resLen.length == 1 ? '0' + resLen : resLen, 456 | console.log(resLen, 'resLen2') 457 | // 判断得到的长度与实际长度相等之后才是实际的结果 458 | if (getLen == resLen.toUpperCase()) { 459 | // 数组转换为字符串 460 | let lastStr = lastConArr.join('') 461 | console.log(lastStr, 'lastStr') 462 | if (lastStr.substring(8,12) == 'F020') { 463 | uni.setStorageSync('RESCODE', lastStr) 464 | console.log(lastStr, 'F020') 465 | } else { 466 | uni.setStorageSync('RESPARAMS', lastStr) 467 | console.log('!f') 468 | } 469 | } 470 | } 471 | } 472 | } 473 | // 清空内容和收到的数据 474 | first = '' // 开始部分 475 | center = '' // 中间拼接部分 476 | last = '' // 结尾部分 477 | len = 0 // 包的长度 478 | 479 | } 480 | }) 481 | }) 482 | 483 | } 484 | 485 | // 12.读取功能 486 | export function readBLE(deviceId, serviceId, characteristicId) { 487 | uni.readBLECharacteristicValue({ 488 | // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 489 | deviceId, 490 | // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取 491 | serviceId, 492 | // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取 493 | characteristicId, 494 | success(res) { 495 | // console.log('readBLECharacteristicValue:', res.errCode) 496 | }, 497 | fail(err) { 498 | // console.log('readBLEerr:', err) 499 | } 500 | }) 501 | } 502 | 503 | /** 504 |  * s为要转换的字符串 505 |  * n为转换为几个一起的数 506 | *'0102031A1B'类型转换为['0x11','0x02'] (n传2) 507 |  */ 508 | function strToArr1(s, n) { 509 |     // var s = "051102003" 510 |     // console.log(s) 511 |     var re = new RegExp(".{" + n + "}", "g") 512 |     var a = [] 513 |     var n 514 |     while ((n = re.exec(s)) != null) { 515 |         a[a.length] = '0x' + n[0] 516 |     } 517 |     return a 518 | } 519 | 520 | // 14.写入功能. 521 | /** 522 | * 523 | * @param {*} e 524 | * 需要发送给蓝牙的数据格式:['0x11','0x02'] 525 | * 526 | * strToArr1函数可将十六进制转换为数组'0102031A1B'类型转换为['0x11','0x02'] 527 | * 528 | */ 529 | export function writeBLE(e) { 530 | var deviceId = uni.getStorageSync("deviceId") 531 | var serviceId = uni.getStorageSync("serviceId") 532 | var characteristicId = uni.getStorageSync("characteristicId") 533 | console.log(deviceId,serviceId, characteristicId) 534 | // 向蓝牙设备发送一个0x00的16进制数据 535 | return new Promise((resolve, reject) => { 536 | // 分包发送 537 | for (var i = 0;i < e.length; i += 20) { 538 | var endLength = 0 539 | if (i + 20 < e.length) { 540 | var senddata = e 541 | let buffer = new ArrayBuffer(20) 542 | let dataView = new DataView(buffer) 543 | for (var j = i; j < i + 20; j++) { 544 | dataView.setUint8(j - i, senddata[j]) 545 | } 546 | uni.writeBLECharacteristicValue({ 547 | // 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取 548 | deviceId: deviceId, 549 | // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取 550 | serviceId: serviceId, 551 | // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取 552 | characteristicId: characteristicId, 553 | // 这里的value是ArrayBuffer类型 554 | value: dataView.buffer, 555 | success(res) { 556 | resolve(res) 557 | }, 558 | fail(err) { 559 | reject(err) 560 | } 561 | }) 562 | // 等待 563 | sleep(0.02) 564 | } else { 565 | var senddata = e 566 | if (20 < e.length) { 567 | endLength = senddata.length - i 568 | } else{ 569 | endLength = senddata.length 570 | } 571 | 572 | let buffer = new ArrayBuffer(endLength) 573 | let dataViewLast = new DataView(buffer) 574 | for (var k = i; k < senddata.length; k++) { 575 | dataViewLast.setUint8(k-i, senddata[k]) 576 | } 577 | console.log('最后一包或第一数据:' + dataViewLast.buffer) 578 | uni.writeBLECharacteristicValue({ 579 | // 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取 580 | deviceId: deviceId, 581 | // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取 582 | serviceId: serviceId, 583 | // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取 584 | characteristicId: characteristicId, 585 | // 这里的value是ArrayBuffer类型 586 | value: dataViewLast.buffer, 587 | success(res) { 588 | resolve(res) 589 | }, 590 | fail(err) { 591 | reject(err) 592 | } 593 | }) 594 | sleep(0.02) 595 | } 596 | } 597 | }) 598 | } 599 | function sleep(delay) { 600 | var start = (new Date()).getTime(); 601 | while ((new Date()).getTime() - start < delay) { 602 | continue; 603 | } 604 | } 605 | 606 | // 其他转换方法 607 | //16进制字符串转 ArrayBuffer 608 | const hexToArrayBuffer = (hex) => { 609 | return new Uint8Array( 610 | hex.match(/[\da-f]{2}/gi).map((byte) => { 611 | return parseInt(byte, 16) 612 | }) 613 | ).buffer 614 | } 615 | 616 | //ArrayBuffer类型数据转为16进制字符串 617 | const bufToHex = (buffer) => { 618 | return Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('') 619 | } 620 | 621 | 622 | 623 | -------------------------------------------------------------------------------- /utils/storageTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本地储存键 types 3 | * **/ 4 | 5 | const TOKEN = 'm_token'; // token 6 | /** 7 | * 用户信息 8 | * nickname 昵称; avatar 头像; 9 | * 10 | */ 11 | const USERINFO = 'm_user_info'; // userinfo 12 | const CODE = 'm_code'; // code 13 | const OPENID = 'm_openid'; // openid 14 | const RECEIVE = 'm_receive'; // 收到的消息 15 | const EQUIPID = 'm_equipid'; // 设备id 16 | const REGION = 'm_region'; // 用户的地区 [省,市,区] 17 | const HEARTCODE = 'heart_code'; // 心跳数据 18 | 19 | export { 20 | TOKEN, 21 | USERINFO, 22 | CODE, 23 | REGION, 24 | OPENID, 25 | EQUIPID, 26 | HEARTCODE 27 | } 28 | --------------------------------------------------------------------------------