zhangbin vor 4 Jahren
Commit
3ca689eb53
100 geänderte Dateien mit 2625 neuen und 0 gelöschten Zeilen
  1. 4 0
      .gitignore
  2. 201 0
      LICENSE
  3. 66 0
      README.md
  4. 120 0
      app.js
  5. 146 0
      app.json
  6. 80 0
      app.wxss
  7. 849 0
      components/TRTCCalling/TRTCCalling.js
  8. 8 0
      components/TRTCCalling/TRTCCalling.json
  9. 17 0
      components/TRTCCalling/TRTCCalling.wxml
  10. 10 0
      components/TRTCCalling/TRTCCalling.wxss
  11. 49 0
      components/TRTCCalling/common/constants.js
  12. 329 0
      components/TRTCCalling/controller/user-controller.js
  13. 14 0
      components/TRTCCalling/libs/mta_analysis.js
  14. 38 0
      components/TRTCCalling/model/stream.js
  15. 17 0
      components/TRTCCalling/model/user.js
  16. BIN
      components/TRTCCalling/static/audio-active.png
  17. BIN
      components/TRTCCalling/static/audio-false.png
  18. BIN
      components/TRTCCalling/static/audio-true.png
  19. BIN
      components/TRTCCalling/static/avatar0_100.png
  20. BIN
      components/TRTCCalling/static/avatar1_100.png
  21. BIN
      components/TRTCCalling/static/avatar2_100.png
  22. BIN
      components/TRTCCalling/static/avatar3_100.png
  23. BIN
      components/TRTCCalling/static/avatar4_100.png
  24. BIN
      components/TRTCCalling/static/avatar5_100.png
  25. BIN
      components/TRTCCalling/static/hangup.png
  26. BIN
      components/TRTCCalling/static/micro-open.png
  27. BIN
      components/TRTCCalling/static/phone.png
  28. BIN
      components/TRTCCalling/static/speaker-true.png
  29. 64 0
      components/TRTCCalling/template/audio-template/audio-template.wxml
  30. 92 0
      components/TRTCCalling/template/audio-template/audio-template.wxss
  31. 47 0
      components/TRTCCalling/template/video-template/video-template.wxml
  32. 69 0
      components/TRTCCalling/template/video-template/video-template.wxss
  33. 21 0
      components/TRTCCalling/utils/compare-version.js
  34. 50 0
      components/TRTCCalling/utils/environment.js
  35. 62 0
      components/TRTCCalling/utils/event.js
  36. 0 0
      components/TRTCCalling/utils/tsignaling-wx.js
  37. 61 0
      components/float-menu/index.js
  38. 6 0
      components/float-menu/index.json
  39. 28 0
      components/float-menu/index.wxml
  40. 52 0
      components/float-menu/index.wxss
  41. 1 0
      components/parser.20200414.min/libs/CssHandler.js
  42. 1 0
      components/parser.20200414.min/libs/MpHtmlParser.js
  43. 1 0
      components/parser.20200414.min/libs/config.js
  44. 1 0
      components/parser.20200414.min/parser.js
  45. 1 0
      components/parser.20200414.min/parser.json
  46. 1 0
      components/parser.20200414.min/parser.wxml
  47. 1 0
      components/parser.20200414.min/parser.wxss
  48. 1 0
      components/parser.20200414.min/trees/handler.wxs
  49. 1 0
      components/parser.20200414.min/trees/trees.js
  50. 1 0
      components/parser.20200414.min/trees/trees.json
  51. 0 0
      components/parser.20200414.min/trees/trees.wxml
  52. 1 0
      components/parser.20200414.min/trees/trees.wxss
  53. 7 0
      config.js
  54. BIN
      images/face.png
  55. BIN
      images/fx.png
  56. BIN
      images/fxad.jpeg
  57. BIN
      images/gift.png
  58. 0 0
      images/icon/car.svg
  59. 1 0
      images/icon/close.svg
  60. 1 0
      images/icon/close0.svg
  61. 12 0
      images/icon/coupons-active.svg
  62. 12 0
      images/icon/coupons-off.svg
  63. 1 0
      images/icon/delete.svg
  64. 1 0
      images/icon/edit.svg
  65. 1 0
      images/icon/fav0.svg
  66. 1 0
      images/icon/fav1.svg
  67. 1 0
      images/icon/go-l.svg
  68. 1 0
      images/icon/go-r.svg
  69. 1 0
      images/icon/kf.svg
  70. 1 0
      images/icon/list1.svg
  71. 1 0
      images/icon/list2.svg
  72. BIN
      images/icon/next.png
  73. 13 0
      images/icon/next.svg
  74. 15 0
      images/icon/pos-gray.svg
  75. 1 0
      images/icon/search.svg
  76. 9 0
      images/icon/shop-on.svg
  77. 9 0
      images/icon/shop.svg
  78. 10 0
      images/icon/tel-gray.svg
  79. 15 0
      images/icon/time-gray.svg
  80. BIN
      images/live.jpg
  81. BIN
      images/live.png
  82. BIN
      images/nav/cart-off.png
  83. BIN
      images/nav/cart-on.png
  84. BIN
      images/nav/coupon-off.png
  85. BIN
      images/nav/coupon-on.png
  86. BIN
      images/nav/fl-off.png
  87. BIN
      images/nav/fl-on.png
  88. BIN
      images/nav/home-off.png
  89. BIN
      images/nav/home-on.png
  90. BIN
      images/nav/my-off.png
  91. BIN
      images/nav/my-on.png
  92. BIN
      images/nav/order-off.png
  93. BIN
      images/nav/order-on.png
  94. BIN
      images/no-order.png
  95. BIN
      images/nologin.png
  96. BIN
      images/nologin2.png
  97. BIN
      images/order-details/icon-address.png
  98. BIN
      images/order-details/icon-ddfh.png
  99. BIN
      images/order-details/icon-ddfk.png
  100. BIN
      images/order-details/icon-ddgb.png

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+dist/
+package-lock.json
+node_modules/
+.DS_Store

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 66 - 0
README.md

@@ -0,0 +1,66 @@
+# 微信小程序商城
+
+微信小程序商城,微信小程序微店,长期维护版本,欢迎大家踊跃提交贡献代码;
+
+使用说明和常见问题,可参阅下面的说明,如还有疑问,可访问工厂官网 [https://www.it120.cc/](https://www.it120.cc/) 寻求帮助!
+
+新增直播带货支持,具体详见使用说明
+
+# 今日头条/抖音小程序版本
+
+本项目的今日头条/抖音小程序版本,请移步至下面的地址:
+
+[https://github.com/EastWorld/tt-app-mall](https://github.com/EastWorld/tt-app-mall)
+
+## 扫码体验
+
+<img src="https://cdn.it120.cc/apifactory/2019/06/28/a8304003-3218-4a47-95cf-84d82ebdc07b.jpg" width="200px">
+
+## 详细配置/使用教程
+
+[https://www.it120.cc/help/ikfe2k.html](https://www.it120.cc/help/ikfe2k.html)
+
+**遇到使用问题?**
+
+[点击这里找答案,可用关键词搜索](https://www.it120.cc/help.html)
+
+## 其他开源模板
+
+| 舔果果小铺(升级版) | 面馆风格小程序 | AI名片 |
+| :------: | :------: | :------: |
+| <img src="https://dcdn.it120.cc/2020/08/30/fd095c58-e655-4785-af0a-89e0b4da7cfe.jpg" width="200px"> | <img src="https://cdn.it120.cc/apifactory/2019/03/29/9e30cfe31eabcd218eb9c434f17e9295.jpg" width="200px"> | <img src="https://cdn.it120.cc/apifactory/2018/12/18/c2324da4eea91602f385db5b523b13ca.jpg" width="200px"> | 
+| [开源地址](https://github.com/gooking/TianguoguoXiaopu) | [开源地址](https://gitee.com/javazj/noodle_shop_procedures) | [开源地址](https://github.com/gooking/visitingCard) |
+
+## 联系作者
+
+| 微信好友 | 支付宝好友 |
+| :------: | :------: |
+| <img src="https://cdn.it120.cc/apifactory/2019/07/03/a86f7e46-1dbc-42fe-9495-65403659671e.jpeg" width="200px"> | <img src="https://cdn.it120.cc/apifactory/2019/07/03/fda59aeb-4943-4379-93bb-92856740bd6a.jpeg" width="200px"> |
+
+## 本项目使用了下面的组件,在此鸣谢
+
+- [接口 SDK](https://github.com/gooking/apifm-wxapi)
+
+- [api工厂](https://admin.it120.cc)
+
+- [WeUI](https://github.com/Tencent/weui-wxss/)
+
+- [vant-weapp](https://youzan.github.io/vant-weapp)
+
+- [小程序富文本插件(html 渲染)](https://github.com/jin-yufeng/Parser)
+
+- [小程序海报组件-生成朋友圈分享海报并生成图片](https://github.com/jasondu/wxa-plugin-canvas)
+
+底部ICON图标使用:
+https://www.iconfont.cn/collections/detail?spm=a313x.7781069.0.da5a778a4&cid=18904
+
+  
+## 如何升级到最新版
+
+- 小程序程序的修改和您后台的数据是独立的,所以不用担心您会丢失数据
+- 先把你开发工具下的现有版本程序备份
+- 下载最新版的程序,直接覆盖您本地的程序
+- 用开发工具修改域名 mall 为你自己的域名
+- 开发工具里面上传代码提交微信审核
+- 审核通过后,小程序后台去发布新版本即可
+- 用户无需重新扫码,关闭小程序重新打开就是新版本了

+ 120 - 0
app.js

@@ -0,0 +1,120 @@
+const WXAPI = require('apifm-wxapi')
+const CONFIG = require('config.js')
+const AUTH = require('utils/auth')
+App({
+  onLaunch: function() {
+    const subDomain = wx.getExtConfigSync().subDomain
+    const componentAppid = wx.getExtConfigSync().componentAppid
+    if (componentAppid) {
+      wx.setStorageSync('appid', wx.getAccountInfoSync().miniProgram.appId)
+      wx.setStorageSync('componentAppid', componentAppid)
+    }
+    if (subDomain) {
+      WXAPI.init(subDomain)
+    } else {
+      WXAPI.init(CONFIG.subDomain)
+    }
+    
+    const that = this;
+    // 检测新版本
+    const updateManager = wx.getUpdateManager()
+    updateManager.onUpdateReady(function () {
+      wx.showModal({
+        title: '更新提示',
+        content: '新版本已经准备好,是否重启应用?',
+        success(res) {
+          if (res.confirm) {
+            // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+            updateManager.applyUpdate()
+          }
+        }
+      })
+    })
+    /**
+     * 初次加载判断网络情况
+     * 无网络状态下根据实际情况进行调整
+     */
+    wx.getNetworkType({
+      success(res) {
+        const networkType = res.networkType
+        if (networkType === 'none') {
+          that.globalData.isConnected = false
+          wx.showToast({
+            title: '当前无网络',
+            icon: 'loading',
+            duration: 2000
+          })
+        }
+      }
+    });
+    /**
+     * 监听网络状态变化
+     * 可根据业务需求进行调整
+     */
+    wx.onNetworkStatusChange(function(res) {
+      if (!res.isConnected) {
+        that.globalData.isConnected = false
+        wx.showToast({
+          title: '网络已断开',
+          icon: 'loading',
+          duration: 2000
+        })
+      } else {
+        that.globalData.isConnected = true
+        wx.hideToast()
+      }
+    })
+    WXAPI.queryConfigBatch('mallName,WITHDRAW_MIN,ALLOW_SELF_COLLECTION,order_hx_uids,subscribe_ids,share_profile,adminUserIds,goodsDetailSkuShowType,shopMod,needIdCheck,balance_pay_pwd').then(res => {
+      if (res.code == 0) {
+        res.data.forEach(config => {
+          wx.setStorageSync(config.key, config.value);
+        })
+        if (this.configLoadOK) {
+          this.configLoadOK()
+        }
+      }
+    })
+  },
+
+  onShow (e) {
+    // 保存邀请人
+    if (e && e.query && e.query.inviter_id) {
+      wx.setStorageSync('referrer', e.query.inviter_id)
+      if (e.shareTicket) {
+        wx.getShareInfo({
+          shareTicket: e.shareTicket,
+          success: res => {
+            wx.login({
+              success(loginRes) {
+                if (loginRes.code) {
+                  WXAPI.shareGroupGetScore(
+                    loginRes.code,
+                    e.query.inviter_id,
+                    res.encryptedData,
+                    res.iv
+                  ).then(_res => {
+                    console.log(_res)
+                  }).catch(err => {
+                    console.error(err)
+                  })
+                } else {
+                  console.error('登录失败!' + loginRes.errMsg)
+                }
+              }
+            })
+          }
+        })
+      }
+    }
+    // 自动登录
+    AUTH.checkHasLogined().then(isLogined => {
+      if (!isLogined) {
+        AUTH.login()
+      }
+    })
+  },
+  globalData: {
+    isConnected: true,
+    sdkAppID: CONFIG.sdkAppID
+  }
+})

+ 146 - 0
app.json

@@ -0,0 +1,146 @@
+{
+  "pages": [
+    "pages/start/start",
+    "pages/shop/select",
+    "pages/index/index",
+    "pages/notice/index",
+    "pages/notice/show",
+    "pages/category/category",
+    "pages/goods/list",
+    "pages/goods/fav",
+    "pages/goods-details/index",
+    "pages/shop-cart/index",
+    "pages/to-pay-order/index",
+    "pages/select-address/index",
+    "pages/address-add/index",
+    "pages/order-list/index",
+    "pages/order-details/index",
+    "pages/order-details/scan-result",
+    "pages/order/refundApply",
+    "pages/wuliu/index",
+    "pages/my/index",
+    "pages/withdraw/index",
+    "pages/score-excharge/index",
+    "pages/score-excharge/growth",
+    "pages/asset/index",
+    "pages/score/index",
+    "pages/score/growth",
+    "pages/sign/index",
+    "pages/maidan/index",
+    "pages/fx/apply",
+    "pages/fx/apply-status",
+    "pages/fx/members",
+    "pages/fx/commisionLog",
+    "pages/coupons/index",
+    "pages/coupons/merge",
+    "pages/invoice/list",
+    "pages/invoice/apply",
+    "pages/deposit/pay",
+    "pages/live/index",
+    "pages/help/detail",
+    "pages/help/index",
+    "pages/about/index",
+    "pages/idCheck/index",
+    "pages/videoCall/videoCall",
+    "pages/peisong/detail",
+    "pages/peisong/orders",
+    "pages/pwd-pay/reset",
+    "pages/pwd-pay/modify",
+    "pages/pwd-pay/set",
+    "pages/my/feedback",
+    "pages/search/index",
+    "pages/my/info",
+    "pages/my/setting",
+    "pages/my/info-menu"
+  ],
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTitleText": "",
+    "navigationBarTextStyle": "black",
+    "onReachBottomDistance": 50
+  },
+  "tabBar": {
+    "color": "#6e6d6b",
+    "selectedColor": "#e64340",
+    "borderStyle": "white",
+    "backgroundColor": "#fff",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "iconPath": "images/nav/home-off.png",
+        "selectedIconPath": "images/nav/home-on.png",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/category/category",
+        "iconPath": "images/nav/fl-off.png",
+        "selectedIconPath": "images/nav/fl-on.png",
+        "text": "分类"
+      },
+      {
+        "pagePath": "pages/coupons/index",
+        "iconPath": "images/nav/coupon-off.png",
+        "selectedIconPath": "images/nav/coupon-on.png",
+        "text": "优惠券"
+      },
+      {
+        "pagePath": "pages/shop-cart/index",
+        "iconPath": "images/nav/cart-off.png",
+        "selectedIconPath": "images/nav/cart-on.png",
+        "text": "购物车"
+      },
+      {
+        "pagePath": "pages/my/index",
+        "iconPath": "images/nav/my-off.png",
+        "selectedIconPath": "images/nav/my-on.png",
+        "text": "我的"
+      }
+    ]
+  },
+  "permission": {
+    "scope.userLocation": {
+      "desc": "获取离你最近的门店"
+    }
+  },
+  "usingComponents": {
+    "float-menu": "/components/float-menu/index",
+    "parser":"/components/parser.20200414.min/parser",
+    "van-notice-bar": "@vant/weapp/notice-bar/index",
+    "van-search": "@vant/weapp/search/index",
+    "van-divider": "@vant/weapp/divider/index",
+    "van-icon": "@vant/weapp/icon/index",
+    "van-count-down": "@vant/weapp/count-down/index",
+    "van-button": "@vant/weapp/button/index",
+    "van-cell": "@vant/weapp/cell/index",
+    "van-cell-group": "@vant/weapp/cell-group/index",
+    "van-tag": "@vant/weapp/tag/index",
+    "van-card": "@vant/weapp/card/index",
+    "van-progress": "@vant/weapp/progress/index",
+    "van-submit-bar": "@vant/weapp/submit-bar/index",
+    "van-field": "@vant/weapp/field/index",
+    "van-radio": "@vant/weapp/radio/index",
+    "van-radio-group": "@vant/weapp/radio-group/index",
+    "van-sidebar": "@vant/weapp/sidebar/index",
+    "van-sidebar-item": "@vant/weapp/sidebar-item/index",
+    "van-empty": "@vant/weapp/empty/index",
+    "van-goods-action": "@vant/weapp/goods-action/index",
+    "van-goods-action-icon": "@vant/weapp/goods-action-icon/index",
+    "van-goods-action-button": "@vant/weapp/goods-action-button/index",
+    "van-popup": "@vant/weapp/popup/index",
+    "van-stepper": "@vant/weapp/stepper/index",
+    "van-sticky": "@vant/weapp/sticky/index",
+    "van-dialog": "@vant/weapp/dialog/index",
+    "van-swipe-cell": "@vant/weapp/swipe-cell/index",
+    "van-calendar": "@vant/weapp/calendar/index",
+    "van-switch": "@vant/weapp/switch/index",
+    "van-rate": "@vant/weapp/rate/index",
+    "van-uploader": "@vant/weapp/uploader/index",
+    "van-grid": "@vant/weapp/grid/index",
+    "van-grid-item": "@vant/weapp/grid-item/index",
+    "van-image": "@vant/weapp/image/index",
+    "van-tab": "@vant/weapp/tab/index",
+    "van-tabs": "@vant/weapp/tabs/index"
+  },
+  "sitemapLocation": "sitemap.json"
+}

+ 80 - 0
app.wxss

@@ -0,0 +1,80 @@
+@import 'weui/weui.wxss';
+
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+}
+.space {
+  height:20rpx;
+  background-color: #F2f2f2;
+}
+.safeAreaOldPaddingBttom {
+  padding-bottom: env(safe-area-inset-bottom);
+}
+.safeAreaNewPaddingBttom{
+  padding-bottom: constant(safe-area-inset-bottom);  
+}
+
+.safeAreaOldMarginBttom {
+  margin-bottom: env(safe-area-inset-bottom);
+}
+.safeAreaNewMarginBttom{
+  margin-bottom: constant(safe-area-inset-bottom);  
+}
+
+.no-data {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.no-data .line {
+  width:132rpx;
+  height:2rpx;
+  background: #999;
+}
+.no-data .txt {
+  font-size:26rpx;
+  color:rgba(153,153,153,1);
+  margin: 0 16rpx;
+}
+.ad-img {
+  width: 100vw;
+}
+.badge {
+  position: absolute;
+  top: 0;
+  right: 0;
+  box-sizing: border-box;
+  padding: 6rpx;
+  color: #fff;
+  font-size: 18rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #e64340;
+  border: 1rpx solid #fff;
+  border-radius: 50%;
+}
+.vw100 {
+  width: 100vw !important;
+}
+
+page {
+  font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica,
+    Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei',
+    sans-serif;
+}
+.block-btn {
+  padding: 0 32rpx;
+}
+
+.safe-bottom-box {
+  position: fixed;
+  bottom: calc(env(safe-area-inset-bottom) / 2);
+  left: 0;
+  width: 100vw;
+}

+ 849 - 0
components/TRTCCalling/TRTCCalling.js

@@ -0,0 +1,849 @@
+import EventEmitter from './utils/event.js'
+import TSignaling from './utils/tsignaling-wx'
+import * as ENV from 'utils/environment.js'
+import MTA from 'libs/mta_analysis.js'
+import { EVENT, TRTC_EVENT } from './common/constants.js'
+import UserController from './controller/user-controller'
+
+const app = getApp()
+
+const TAG_NAME = 'TRTCCalling-Component'
+// 组件旨在跨终端维护一个通话状态管理机,以事件发布机制驱动上层进行管理,并通过API调用进行状态变更。
+// 组件设计思路将UI和状态管理分离。您可以通过修改`template`文件夹下的文件,适配您的业务场景,
+// 在UI展示上,您可以通过属性的方式,将上层的用户头像,名称等数据传入组件内部,`static`下的用户头像图片,
+// 只是为了展示基础的效果,您需要根据业务场景进行修改。
+Component({
+  properties: {
+    config: {
+      type: Object,
+      value: {
+        sdkAppID: app.globalData.sdkAppID,
+        userID: '',
+        userSig: '',
+        type: 0,
+      },
+      observer: function(newVal, oldVal) {
+        this.setData({
+          config: newVal,
+        })
+      },
+    },
+    pusherAvatar: {
+      type: String,
+      value: '',
+    },
+    remoteAvatar: {
+      type: String,
+      value: '',
+    },
+
+  },
+  data: {
+    soundMode: 'speaker', // 声音模式 听筒/扬声器
+    callingFlag: false,
+    active: false,
+    pusherConfig: { // 本地上行状态管理
+      pushUrl: '',
+      frontCamera: 'front',
+      enableMic: true,
+      enableCamera: true,
+      volume: 0,
+    },
+    playerList: [], // 通话成员列表
+    streamList: [],
+    invitation: { // 接收到的邀请
+      inviteID: '',
+    },
+    invitationAccept: { // 发出的邀请,以及返回的状态
+      inviteID: '',
+      acceptFlag: false,
+      rejectFlag: false,
+    },
+    _historyUserList: [], // 房间内进过房的成员列表,用于发送call_end
+  },
+
+  methods: {
+    _initEventEmitter() {
+      // 监听TSignaling事件
+      this.tsignaling.on(TSignaling.EVENT.NEW_INVITATION_RECEIVED, (event) => {
+        console.log(TAG_NAME, 'onNewInvitationReceived', `是否在通话:${this.data.callingFlag || this.data.invitationAccept.acceptFlag}, inviteID:${event.data.inviteID} inviter:${event.data.inviter} inviteeList:${event.data.inviteeList} data:${event.data.data}`)
+        const data = JSON.parse(event.data.data)
+        // 新的通话邀请
+        // {
+        //   "version":0,
+        //   "call_type":1,
+        //   "room_id":"123"
+        // }
+        // 通话结束,发出的 call_end
+        // {
+        //   "version":0,
+        //   "call_type":1,
+        //   "call_end":123123
+        // }
+        // 通话中,接收的新的邀请时,忙线拒绝
+        if (this.data.callingFlag || this.data.invitationAccept.acceptFlag) {
+          this.tsignaling.reject({
+            inviteID: event.data.inviteID,
+            data: JSON.stringify({
+              version: 0,
+              call_type: data.call_type,
+              line_busy: '',
+            }),
+          })
+          return
+        }
+        if (data.call_end) {
+          // 小程序端对 call_end 的接收不做业务处理,这里只是向外抛出这个事件
+          this._emitter.emit(EVENT.CALL_END, {
+            call_end: data.call_end,
+          })
+        } else {
+          this.data.invitation.inviteID = event.data.inviteID
+          this.data.invitation.inviter = event.data.inviter
+
+          this.data.invitation.type = data.call_type
+          this.data.invitation.roomID = data.room_id
+          this.setData({
+            invitation: this.data.invitation,
+            callingFlag: true, // 当前invitation未处理完成时,下一个invitation都将会忙线
+          }, () => {
+            console.log(`${TAG_NAME} NEW_INVITATION_RECEIVED invitation: `, this.data.callingFlag, this.data.invitation)
+            this._emitter.emit(EVENT.INVITED, {
+              inviter: this.data.invitation.inviter,
+              type: this.data.invitation.type,
+            })
+          })
+        }
+      })
+      this.tsignaling.on(TSignaling.EVENT.INVITEE_ACCEPTED, (event) => {
+        // 发出的邀请收到接受的回调
+        console.log(`${TAG_NAME} INVITEE_ACCEPTED inviteID:${event.data.inviteID} invitee:${event.data.invitee} data:${event.data.data}`)
+        if (this.data.invitationAccept.inviteID === event.data.inviteID) {
+          this.data.invitationAccept.acceptFlag = true
+          this.setData({
+            invitationAccept: this.data.invitationAccept,
+          })
+        }
+      })
+      this.tsignaling.on(TSignaling.EVENT.INVITEE_REJECTED, (event) => {
+        // 发出的邀请收到拒绝的回调
+        console.log(`${TAG_NAME} INVITEE_REJECTED inviteID:${event.data.inviteID} invitee:${event.data.invitee} data:${event.data.data}`)
+        // 小程序使用双向绑定模式,这里只向外抛出拒绝的用户 userID 业务逻辑由业务层进行处理
+        const data = JSON.parse(event.data.data)
+        if (this.data.invitationAccept.inviteID === event.data.inviteID) {
+          this.data.invitationAccept.rejectFlag = true
+          this.setData({
+            invitationAccept: this.data.invitationAccept,
+          }, () => {
+            if (data.line_busy === '') {
+              this._emitter.emit(EVENT.LINE_BUSY, {
+                inviteID: event.data.inviteID,
+                invitee: event.data.invitee,
+                reason: 'line busy',
+              })
+            } else {
+              this._emitter.emit(EVENT.REJECT, {
+                inviteID: event.data.inviteID,
+                invitee: event.data.invitee,
+                reason: 'reject',
+              })
+            }
+          })
+        }
+      })
+      this.tsignaling.on(TSignaling.EVENT.INVITATION_CANCELLED, (event) => {
+        // 收到的邀请收到该邀请取消的回调
+        console.log('demo | onInvitationCancelled', `inviteID:${event.data.inviteID} inviter:${event.data.invitee} data:${event.data.data}`)
+        // invitation取消,收到此消息的时候应该还没有接通,为防止时序的问题,还是走一下挂断流程
+        this.setData({
+          callingFlag: false,
+        })
+        this._emitter.emit(EVENT.CALLING_CANCEL, {
+          inviteID: event.data.inviteID,
+          invitee: event.data.invitee,
+        })
+      })
+      this.tsignaling.on(TSignaling.EVENT.INVITATION_TIMEOUT, (event) => {
+        console.log(TAG_NAME, 'onInvitationTimeout 邀请超时', `inviteID:${event.data.inviteID} inviteeList:${event.data.inviteeList}`)
+        // 邀请超时, 无人应答
+        this._emitter.emit(EVENT.NO_RESP, {
+          inviteID: event.data.inviteID,
+          inviteeList: event.data.inviteeList,
+        })
+      })
+      this.tsignaling.on(TSignaling.EVENT.SDK_READY, () => {
+        console.log(TAG_NAME, 'TSignaling SDK ready')
+      })
+      this.tsignaling.on(TSignaling.EVENT.SDK_NOT_READY, () => {
+        this._emitter.emit(EVENT.ERROR, {
+          errorMsg: 'TSignaling SDK not ready !!! 如果想使用发送消息等功能,接入侧需驱动 SDK 进入 ready 状态',
+        })
+      })
+      this.tsignaling.on(TSignaling.EVENT.TEXT_MESSAGE_RECEIVED, () => {
+
+      })
+      this.tsignaling.on(TSignaling.EVENT.CUSTOM_MESSAGE_RECEIVED, () => {
+
+      })
+      this.tsignaling.on(TSignaling.EVENT.REMOTE_USER_JOIN, () => {
+        //
+      })
+      this.tsignaling.on(TSignaling.EVENT.REMOTE_USER_LEAVE, () => {
+        // 离开
+      })
+      this.tsignaling.on(TSignaling.EVENT.KICKED_OUT, () => {
+        // 被踢下线 TODO
+        wx.showToast({
+          title: '您已被踢下线',
+        })
+        this.hangup()
+      })
+      this.tsignaling.on(TSignaling.EVENT.NET_STATE_CHANGE, () => {
+
+      })
+      // 监听TRTC SDK抛出的事件
+      this.userController.on(TRTC_EVENT.REMOTE_USER_JOIN, (event)=>{
+        console.log(TAG_NAME, '远端用户进房', event, event.data.userID)
+        this.setData({
+          playerList: event.data.userList,
+        }, () => {
+          // this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: event.data.userID })
+          this._emitter.emit(EVENT.USER_ENTER, {
+            userID: event.data.userID,
+          })
+          if (this.data._historyUserList.indexOf(event.data.userID) > -1) {
+            this.data._historyUserList.push(event.data.userID)
+          }
+        })
+        console.log(TAG_NAME, 'REMOTE_USER_JOIN', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+      // 远端用户离开
+      this.userController.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event)=>{
+        console.log(TAG_NAME, '远端用户离开', event, event.data.userID)
+        if (event.data.userID) {
+          this.setData({
+            playerList: event.data.userList,
+            streamList: event.data.streamList,
+          }, () => {
+            // TODO: 房间内没有远端用户时就退房, 并且发出invite消息,带call_end信息
+            this._emitter.emit(EVENT.USER_LEAVE, {
+              userID: event.data.userID,
+            })
+          })
+        }
+        console.log(TAG_NAME, 'REMOTE_USER_LEAVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+      // 视频状态 true
+      this.userController.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event)=>{
+        console.log(TAG_NAME, '远端视频可用', event, event.data.stream.userID)
+        const stream = event.data.stream
+        this.setData({
+          playerList: event.data.userList,
+          streamList: event.data.streamList,
+        }, () => {
+          stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
+        })
+        console.log(TAG_NAME, 'REMOTE_VIDEO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+      // 视频状态 false
+      this.userController.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event)=>{
+        console.log(TAG_NAME, '远端视频移除', event, event.data.stream.userID)
+        const stream = event.data.stream
+        this.setData({
+          playerList: event.data.userList,
+          streamList: event.data.streamList,
+        }, () => {
+          // 有可能先触发了退房事件,用户名下的所有stream都已清除
+          if (stream.userID && stream.streamType) {
+            // this._emitter.emit(EVENT.REMOTE_VIDEO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
+          }
+        })
+        console.log(TAG_NAME, 'REMOTE_VIDEO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+      // 音频可用
+      this.userController.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event)=>{
+        console.log(TAG_NAME, '远端音频可用', event)
+        const stream = event.data.stream
+        this.setData({
+          playerList: event.data.userList,
+          streamList: event.data.streamList,
+        }, () => {
+          stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
+          // 新增的需要触发一次play 默认属性才能生效
+          // stream.playerContext.play()
+          // console.log(TAG_NAME, 'REMOTE_AUDIO_ADD playerContext.play()', stream)
+          // this._emitter.emit(EVENT.REMOTE_AUDIO_ADD, { userID: stream.userID, streamType: stream.streamType })
+        })
+        console.log(TAG_NAME, 'REMOTE_AUDIO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+      // 音频不可用
+      this.userController.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event)=>{
+        console.log(TAG_NAME, '远端音频移除', event, event.data.stream.userID)
+        const stream = event.data.stream
+        this.setData({
+          playerList: event.data.userList,
+          streamList: event.data.streamList,
+        }, () => {
+          // 有可能先触发了退房事件,用户名下的所有stream都已清除
+          if (stream.userID && stream.streamType) {
+            // this._emitter.emit(EVENT.REMOTE_AUDIO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
+          }
+        })
+        console.log(TAG_NAME, 'REMOTE_AUDIO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
+      })
+    },
+    /**
+     * 登录IM接口,所有功能需要先进行登录后才能使用
+     *
+     */
+    login() {
+      return new Promise((resolve, reject) => {
+        this.tsignaling.setLogLevel(0)
+        MTA.Page.stat()
+        this.tsignaling.login({
+          userID: this.data.config.userID,
+          userSig: this.data.config.userSig,
+        }).then( () => {
+          console.log(TAG_NAME, 'login', 'IM登入成功')
+          this._initEventEmitter()
+          resolve()
+        })
+      })
+    },
+    /**
+     * 登出接口,登出后无法再进行拨打操作
+     */
+    logout() {
+      this.tsignaling.logout({
+        userID: this.data.config.userID,
+        userSig: this.data.config.userSig,
+      }).then( () => {
+        console.log(TAG_NAME, 'login', 'IM登出成功')
+      }).catch( () => {
+        console.error(TAG_NAME, 'login', 'IM登出失败')
+      })
+    },
+    /**
+     * 监听事件
+     *
+     * @param eventCode 事件名
+     * @param handler 事件响应回调
+     */
+    on(eventCode, handler, context) {
+      this._emitter.on(eventCode, handler, context)
+    },
+
+    off(eventCode, handler) {
+      this._emitter.off(eventCode, handler)
+    },
+    /**
+     * C2C邀请通话,被邀请方会收到的回调
+     * 如果当前处于通话中,可以调用该函数以邀请第三方进入通话
+     *
+     * @param userID 被邀请方
+     * @param type 0-为之, 1-语音通话,2-视频通话
+     */
+    call({ userID, type }) {
+      // 生成房间号,拼接URL地址
+      const roomID = Math.floor(Math.random() * 100000000 + 1) // 随机生成房间号
+      this._getPushUrl(roomID)
+      this._enterTRTCRoom()
+      this.tsignaling.invite({
+        userID: userID,
+        data: JSON.stringify({
+          version: 0,
+          call_type: type,
+          room_id: roomID,
+        }),
+        timeout: 30,
+      }).then( (res) => {
+        console.log(`${TAG_NAME} call(userID: ${userID}, type: ${type}) success`)
+        this.data.invitationAccept.inviteID = res.inviteID
+        this.setData({
+          invitationAccept: this.data.invitationAccept,
+          callingFlag: true,
+        })
+      }).catch((error) => {
+        console.log(`${TAG_NAME} call(userID:${userID},type:${type}) failed', error: ${error}`)
+      })
+    },
+    /**
+     * IM群组邀请通话,被邀请方会收到的回调
+     * 如果当前处于通话中,可以继续调用该函数继续邀请他人进入通话,同时正在通话的用户会收到的回调
+     *
+     * @param userIDList 邀请列表
+     * @param type 1-语音通话,2-视频通话
+     * @param groupID IM群组ID
+     */
+    groupCall(params) {
+      this.tsignaling.inviteInGroup({
+        groupID: params.groupID,
+        inviteeList: params.userIDList,
+        timeout: 30,
+        data: JSON.stringify({
+          version: 0,
+          call_type: params.type,
+          room_id: Math.floor(Math.random() * 100000000 + 1),
+        }),
+      }).then(function(res) {
+        console.log(TAG_NAME, 'inviteInGroup OK', res)
+      }).catch(function(error) {
+        console.log(TAG_NAME, 'inviteInGroup failed', error)
+      })
+    },
+    /**
+     * 当您作为被邀请方收到 {@link TRTCCallingDelegate#onInvited } 的回调时,可以调用该函数接听来电
+     */
+    accept() {
+      // 拼接pusherURL进房
+      console.log(TAG_NAME, 'accept() inviteID: ', this.data.invitation.inviteID)
+      this.tsignaling.accept({
+        inviteID: this.data.invitation.inviteID,
+        data: JSON.stringify({
+          version: 0,
+          call_type: this.data.config.type,
+        }),
+      }).then( () => {
+        console.log('接受成功')
+      }).catch( () => {
+        console.error('接受失败')
+      })
+      this._getPushUrl(this.data.invitation.roomID)
+      this._enterTRTCRoom()
+    },
+    /**
+     * 当您作为被邀请方收到的回调时,可以调用该函数拒绝来电
+     */
+    reject() {
+      if (this.data.invitation.inviteID) {
+        this.tsignaling.reject({
+          inviteID: this.data.invitation.inviteID,
+          data: JSON.stringify({
+            version: 0,
+            call_type: this.data.config.type,
+          }),
+        }).then( (res) => {
+          console.log('demo reject OK', res)
+          this._reset()
+        }).catch( (error) => {
+          console.log('demo reject failed', error)
+        })
+      } else {
+        console.warn(`${TAG_NAME} 未收到邀请,无法拒绝`)
+        return
+      }
+    },
+    /**
+     * 当您处于通话中,可以调用该函数结束通话
+     */
+    hangup() {
+      const inviterFlag = !this.data.invitation.inviteID && this.data.invitationAccept.inviteID && true // 是否是邀请者
+      if (inviterFlag && !this.data.invitationAccept.acceptFlag && !this.data.invitationAccept.rejectFlag) {
+        console.log(TAG_NAME, 'cancel() inviteID: ', this.data.invitationAccept.inviteID)
+        this.tsignaling.cancel({
+          inviteID: this.data.invitationAccept.inviteID,
+        })
+      }
+      if (this.data.playerList.length === 0 && this.data.invitationAccept.acceptFlag) {
+        const currentTime = Date.parse(new Date())
+        // console.log('发送call_end信息', currentTime)
+        this.data._historyUserList.forEach( (user) => {
+          this.tsignaling.invite({
+            userID: user,
+            data: JSON.stringify({
+              version: 0,
+              call_type: this.data.config.type,
+              call_end: currentTime,
+            }),
+          })
+        })
+        this._emitter.emit(EVENT.CALL_END, {
+          inviteID: this.data.invitationAccept.inviteID,
+          call_end: currentTime,
+        })
+      }
+      this._reset().then( () => {
+        this._emitter.emit(EVENT.HANG_UP)
+        console.log(TAG_NAME, 'hangup() pusherConfig: ', this.data.pusherConfig)
+      })
+    },
+
+    _reset() {
+      return new Promise( (resolve, reject) => {
+        console.log(TAG_NAME, ' _reset()')
+        const result = this.userController.reset()
+        this.data.pusherConfig = {
+          pushUrl: '',
+          frontCamera: 'front',
+          enableMic: true,
+          enableCamera: true,
+          volume: 0,
+        },
+        // 清空状态
+        this.setData({
+          pusherConfig: this.data.pusherConfig,
+          soundMode: 'speaker',
+          invitation: {
+            inviteID: '',
+          },
+          playerList: result.userList,
+          streamList: result.streamList,
+          _historyUserList: [],
+          active: false,
+          callingFlag: false,
+          invitationAccept: {
+            inviteID: '',
+            acceptFlag: false,
+            rejectFlag: false,
+          },
+        }, () => {
+          resolve()
+        })
+      })
+    },
+    /**
+     *
+     * @param userID 远端用户id
+     */
+    startRemoteView(userID) {
+      this.data.streamList.forEach( (stream) => {
+        if (stream.userID === userID) {
+          stream.muteVideo = false
+          this.setData({
+            streamList: this.data.streamList,
+          }, () => {
+            console.log(`${TAG_NAME}, startRemoteView(${userID})`)
+          })
+          return
+        }
+      })
+    },
+    /**
+     * 当您收到 onUserVideoAvailable 回调为false时,可以停止渲染数据
+     *
+     * @param userID 远端用户id
+     */
+    stopRemoteView(userID) {
+      this.data.streamList.forEach( (stream) => {
+        if (stream.userID === userID) {
+          stream.muteVideo = true
+          this.setData({
+            streamList: this.data.streamList,
+          }, () => {
+            console.log(`${TAG_NAME}, stopRemoteView(${userID})`)
+          })
+          return
+        }
+      })
+    },
+    /**
+     * 您可以调用该函数开启摄像头
+     */
+    openCamera() {
+      this.data.pusherConfig.enableCamera = true
+      this.setData({
+        pusherConfig: this.data.pusherConfig,
+      }, () => {
+        console.log(`${TAG_NAME}, closeCamera() pusherConfig: ${this.data.pusherConfig}`)
+      })
+    },
+    /**
+     * 您可以调用该函数关闭摄像头
+     * 处于通话中的用户会收到回调
+     */
+    closeCamera() {
+      this.data.pusherConfig.enableCamera = false
+      this.setData({
+        pusherConfig: this.data.pusherConfig,
+      }, () => {
+        console.log(`${TAG_NAME}, closeCamera() pusherConfig: ${this.data.pusherConfig}`)
+      })
+    },
+    /**
+     * 是否静音mic
+     *
+     * @param isMute true:麦克风关闭 false:麦克风打开
+     */
+    // setMicMute(isMute) {
+    //   this.data.pusherConfig.enableMic = !isMute
+    //   this.setData({
+    //     pusherConfig: this.data.pusherConfig,
+    //   }, () => {
+    //     console.log(`${TAG_NAME}, setMicMute(${isMute}) enableMic: ${this.data.pusherConfig.enableMic}`)
+    //   })
+    // },
+    setMicMute(isMute) {
+      this.data.pusherConfig.enableMic = !isMute
+      this.setData({
+        pusherConfig: this.data.pusherConfig,
+      }, () => {
+        console.log(`${TAG_NAME}, setMicMute(${isMute}) enableMic: ${this.data.pusherConfig.enableMic}`)
+        wx.createLivePusherContext().setMICVolume({ volume: isMute ? 0 : 1 })
+      })
+    },
+
+    switchCamera(isFrontCamera) {
+      this.data.pusherConfig.frontCamera = isFrontCamera ? 'front' : 'back'
+      this.setData({
+        pusherConfig: this.data.pusherConfig,
+      }, () => {
+        console.log(`${TAG_NAME}, switchCamera(), frontCamera${this.data.pusherConfig.frontCamera}`)
+      })
+    },
+    setHandsFree(isHandsFree) {
+      this.data.soundMode = isHandsFree ? 'speaker' : 'ear'
+      this.setData({
+        soundMode: this.data.soundMode,
+      }, () => {
+        console.log(`${TAG_NAME}, setHandsFree() result: ${this.data.soundMode}`)
+      })
+    },
+
+    _toggleAudio() {
+      if (this.data.pusherConfig.enableMic) {
+        this.setMicMute(true)
+      } else {
+        this.setMicMute(false)
+      }
+    },
+
+    _toggleSoundMode() {
+      if (this.data.soundMode === 'speaker') {
+        this.setHandsFree(false)
+      } else {
+        this.setHandsFree(true)
+      }
+    },
+
+    _getPushUrl(roomId) {
+      // 拼接 puhser url rtmp 方案
+      console.log(TAG_NAME, '_getPushUrl', roomId)
+      // TODO: 解注释
+      if (ENV.IS_TRTC) {
+        // 版本高于7.0.8,基础库版本高于2.10.0 使用新的 url
+        return new Promise((resolve, reject) => {
+          this.setData({
+            active: true,
+          })
+          let roomID = ''
+          if (/^\d+$/.test(roomId)) {
+            // 数字房间号
+            roomID = '&roomid=' + roomId
+          } else {
+            // 字符串房间号
+            roomID = '&strroomid=' + roomId
+          }
+          setTimeout(()=> {
+            const pushUrl = 'room://cloud.tencent.com/rtc?sdkappid=' + this.data.config.sdkAppID +
+                            roomID +
+                            '&userid=' + this.data.config.userID +
+                            '&usersig=' + this.data.config.userSig +
+                            '&appscene=videocall' +
+                            '&cloudenv=PRO' // ios此参数必填
+            console.warn(TAG_NAME, 'getPushUrl result:', pushUrl)
+            this.data.pusherConfig.pushUrl = pushUrl
+            this.setData({
+              pusherConfig: this.data.pusherConfig,
+            })
+            resolve(pushUrl)
+          }, 0)
+        })
+      }
+      console.error(TAG_NAME, '组件仅支持微信 App iOS >=7.0.9, Android >= 7.0.8, 小程序基础库版 >= 2.10.0')
+      console.error(TAG_NAME, '需要真机运行,开发工具不支持实时音视频')
+    },
+
+    _enterTRTCRoom() {
+      // 开始推流
+      wx.createLivePusherContext().start()
+    },
+
+    _hangUp() {
+      this.hangup()
+    },
+
+    _pusherStateChangeHandler(event) {
+      const code = event.detail.code
+      const message = event.detail.message
+      const TAG_NAME = 'TRTCCalling pusherStateChange: '
+      switch (code) {
+        case 0: // 未知状态码,不做处理
+          console.log(TAG_NAME, message, code)
+          break
+        case 1001:
+          console.log(TAG_NAME, '已经连接推流服务器', code)
+          break
+        case 1002:
+          console.log(TAG_NAME, '已经与服务器握手完毕,开始推流', code)
+          break
+        case 1003:
+          console.log(TAG_NAME, '打开摄像头成功', code)
+          break
+        case 1005:
+          console.log(TAG_NAME, '推流动态调整分辨率', code)
+          break
+        case 1006:
+          console.log(TAG_NAME, '推流动态调整码率', code)
+          break
+        case 1007:
+          console.log(TAG_NAME, '首帧画面采集完成', code)
+          break
+        case 1008:
+          console.log(TAG_NAME, '编码器启动', code)
+          break
+        case 1018:
+          console.log(TAG_NAME, '进房成功', code)
+          break
+        case 1019:
+          console.log(TAG_NAME, '退出房间', code)
+          // 20200421 iOS 仍然没有1019事件通知退房,退房事件移动到 exitRoom 方法里,但不是后端通知的退房成功
+          // this._emitter.emit(EVENT.LOCAL_LEAVE, { userID: this.data.pusher.userID })
+          break
+        case 2003:
+          console.log(TAG_NAME, '渲染首帧视频', code)
+          break
+        case 1020:
+        case 1031:
+        case 1032:
+        case 1033:
+        case 1034:
+          // 通过 userController 处理 1020 1031 1032 1033 1034
+          this.userController.userEventHandler(event)
+          break
+        case -1301:
+          console.error(TAG_NAME, '打开摄像头失败: ', code)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case -1302:
+          console.error(TAG_NAME, '打开麦克风失败: ', code)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case -1303:
+          console.error(TAG_NAME, '视频编码失败: ', code)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case -1304:
+          console.error(TAG_NAME, '音频编码失败: ', code)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case -1307:
+          console.error(TAG_NAME, '推流连接断开: ', code)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case -100018:
+          console.error(TAG_NAME, '进房失败: userSig 校验失败,请检查 userSig 是否填写正确', code, message)
+          this._emitter.emit(EVENT.ERROR, { code, message })
+          break
+        case 5000:
+          console.log(TAG_NAME, '小程序被挂起: ', code)
+          // 20200421 iOS 微信点击胶囊圆点会触发该事件
+          // 触发 5000 后,底层SDK会退房,返回前台后会自动进房
+          break
+        case 5001:
+          // 20200421 仅有 Android 微信会触发该事件
+          console.log(TAG_NAME, '小程序悬浮窗被关闭: ', code)
+          break
+        case 1021:
+          console.log(TAG_NAME, '网络类型发生变化,需要重新进房', code)
+          break
+        case 2007:
+          console.log(TAG_NAME, '本地视频播放loading: ', code)
+          break
+        case 2004:
+          console.log(TAG_NAME, '本地视频播放开始: ', code)
+          break
+        default:
+          console.log(TAG_NAME, message, code)
+      }
+    },
+
+    _playerStateChange(event) {
+      // console.log(TAG_NAME, '_playerStateChange', event)
+      this._emitter.emit(EVENT.REMOTE_STATE_UPDATE, event)
+    },
+
+    _playerAudioVolumeNotify(event) {
+      const userID = event.target.dataset.userid
+      const volume = event.detail.volume
+      const stream = this.userController.getStream({
+        userID: userID,
+        streamType: 'main',
+      })
+      if (stream) {
+        stream.volume = volume
+      }
+      this.setData({
+        streamList: this.data.streamList,
+      }, () => {
+        this._emitter.emit(EVENT.USER_VOICE_VOLUME, {
+          userID: userID,
+          volume: volume,
+        })
+      })
+    },
+    _pusherAudioVolumeNotify(event) {
+      this.data.pusherConfig.volume = event.detail.volume
+      this._emitter.emit(EVENT.USER_VOICE_VOLUME, {
+        userID: this.data.config.userID,
+        volume: event.detail.volume,
+      })
+      this.setData({
+        pusherConfig: this.data.pusherConfig,
+      })
+    },
+  },
+
+  /**
+   * 生命周期方法
+   */
+  lifetimes: {
+    created: function() {
+      // 在组件实例刚刚被创建时执行
+      console.log(TAG_NAME, 'created', ENV)
+      this.tsignaling = new TSignaling({ SDKAppID: this.data.config.sdkAppID })
+      wx.setKeepScreenOn({
+        keepScreenOn: true,
+      })
+      MTA.App.init({
+        'appID': '500728728',
+        'eventID': '500730148',
+        'autoReport': true,
+        'statParam': true,
+        'ignoreParams': [],
+      })
+    },
+    attached: function() {
+      // 在组件实例进入页面节点树时执行
+      console.log(TAG_NAME, 'attached')
+      this.EVENT = EVENT
+      this._emitter = new EventEmitter()
+      this.userController = new UserController()
+      MTA.Page.stat()
+    },
+    ready: function() {
+      // 在组件在视图层布局完成后执行
+      console.log(TAG_NAME, 'ready')
+    },
+    detached: function() {
+      // 在组件实例被从页面节点树移除时执行
+      console.log(TAG_NAME, 'detached')
+      this._reset()
+    },
+    error: function(error) {
+      // 每当组件方法抛出错误时执行
+      console.log(TAG_NAME, 'error', error)
+    },
+  },
+  pageLifetimes: {
+    show: function() {
+    },
+    hide: function() {
+      // 组件所在的页面被隐藏时执行
+      console.log(TAG_NAME, 'hide')
+    },
+    resize: function(size) {
+      // 组件所在的页面尺寸变化时执行
+      console.log(TAG_NAME, 'resize', size)
+    },
+  },
+})

+ 8 - 0
components/TRTCCalling/TRTCCalling.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "TRTCCalling": "../../components/TRTCCalling/TRTCCalling"
+  },
+  "navigationStyle": "custom",
+  "disableScroll": true
+}

+ 17 - 0
components/TRTCCalling/TRTCCalling.wxml

@@ -0,0 +1,17 @@
+<import src="./template/audio-template/audio-template"></import>
+<import src="./template/video-template/video-template"></import>
+
+<view class="TRTCCaling-container">
+  <view wx:if="{{config.type === 1 && active}}" class="TRTCCalling-call-audio">
+    <!-- 语音通话 以下为语音通话模版 您可以根据您业务需求进行扩展,此处仅进行基础能力的展示 -->
+    <view style="width: 100%; height: 100%">
+      <template is='audio-template' data="{{streamList, pusherConfig, soundMode, _pusherStateChangeHandler, _pusherAudioVolumeNotify, _playerStateChange, _playerAudioVolumeNotify, _toggleAudio, _hangUp, _toggleSoundMode}}"></template>
+    </view>
+  </view>
+  <view wx:if="{{config.type === 2 && pusherConfig.pushUrl !== '' && active}}" class="TRTCCalling-call">
+    <!-- 视频通话 以下为视频通话模版 您可以根据您业务需求进行扩展,此处仅进行基础能力的展示-->
+    <view style="width: 100%; height: 100%">
+      <template is='video-template' data="{{streamList, pusherConfig, soundMode, _pusherStateChangeHandler, _pusherAudioVolumeNotify, _playerStateChange, _playerAudioVolumeNotify, _toggleAudio, _hangUp, _toggleSoundMode}}"></template>
+    </view>
+  </view>
+</view>

+ 10 - 0
components/TRTCCalling/TRTCCalling.wxss

@@ -0,0 +1,10 @@
+@import "./template/audio-template/audio-template.wxss";
+@import "./template/video-template/video-template.wxss";
+
+.TRTCCaling-container {
+	width: 100vw;
+	height: 100vh;
+	overflow: hidden;
+	background-image: url(https://mc.qcloudimg.com/static/img/7da57e0050d308e2e1b1e31afbc42929/bg.png);
+	margin: 0;
+}

+ 49 - 0
components/TRTCCalling/common/constants.js

@@ -0,0 +1,49 @@
+export const EVENT = {
+  INVITED: 'INVITED',
+  GROUP_CALL_INVITEE_LIST_UPDATE: 'GROUP_CALL_INVITEE_LIST_UPDATE',
+  USER_ENTER: 'USER_ENTER',
+  USER_LEAVE: 'USER_LEAVE',
+  REJECT: 'REJECT',
+  NO_RESP: 'NO_RESP',
+  LINE_BUSY: 'LINE_BUSY',
+  CALLING_CANCEL: 'CALLING_CANCEL',
+  CALLING_TIMEOUT: 'CALLING_TIMEOUT',
+  CALL_END: 'CALL_END',
+  USER_VIDEO_AVAILABLE: 'USER_VIDEO_AVAILABLE',
+  USER_AUDIO_AVAILABLE: 'USER_AUDIO_AVAILABLE',
+  USER_VOICE_VOLUME: 'USER_VOICE_VOLUME',
+
+  HANG_UP: 'HANG_UP',
+  ERROR: 'ERROR', // 组件内部抛出的错误
+}
+
+export const TRTC_EVENT = {
+  REMOTE_USER_JOIN: 'REMOTE_USER_JOIN', // 远端用户进房
+  REMOTE_USER_LEAVE: 'REMOTE_USER_LEAVE', // 远端用户退房
+  REMOTE_VIDEO_ADD: 'REMOTE_VIDEO_ADD', // 远端视频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_VIDEO_REMOVE: 'REMOTE_VIDEO_REMOVE', // 远端视频流移出事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_ADD: 'REMOTE_AUDIO_ADD', // 远端音频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_REMOVE: 'REMOTE_AUDIO_REMOVE', // 远端音频流移除事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_STATE_UPDATE: 'REMOTE_STATE_UPDATE', // 远端用户播放状态变更
+  LOCAL_NET_STATE_UPDATE: 'LOCAL_NET_STATE_UPDATE', // 本地推流网络状态变更
+  REMOTE_NET_STATE_UPDATE: 'REMOTE_NET_STATE_UPDATE', // 远端用户网络状态变更
+  LOCAL_AUDIO_VOLUME_UPDATE: 'LOCAL_AUDIO_VOLUME_UPDATE', // 本地音量变更
+  REMOTE_AUDIO_VOLUME_UPDATE: 'REMOTE_AUDIO_VOLUME_UPDATE', // 远端用户音量变更
+}
+
+export const DEFAULT_PLAYER_CONFIG = {
+  src: '',
+  mode: 'RTC',
+  autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
+  muteAudio: false, // 默认不拉取音频,需要手动订阅,如果要快速播放,需要设置false
+  muteVideo: false, // 默认不拉取视频,需要手动订阅,如果要快速播放,需要设置false
+  orientation: 'vertical', // 画面方向 vertical horizontal
+  objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
+  enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
+  minCache: 0.6, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
+  maxCache: 0.8, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
+  soundMode: 'speaker', // 声音输出方式 ear speaker
+  enableRecvMessage: 'false', // 是否接收SEI消息
+  autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
+  autoPauseIfOpenNative: true, // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
+}

+ 329 - 0
components/TRTCCalling/controller/user-controller.js

@@ -0,0 +1,329 @@
+import Event from '../utils/event.js'
+import User from '../model/user.js'
+import Stream from '../model/stream.js'
+import { TRTC_EVENT } from '../common/constants.js'
+
+const TAG_NAME = 'UserController'
+/**
+ * 通讯成员管理
+ */
+class UserController {
+  constructor(componentContext) {
+    // userMap 用于存储完整的数据结构
+    this.userMap = new Map()
+    // userList 用于存储简化的用户数据 Object,包括 {userID hasMainAudio hasMainVideo hasAuxAudio hasAuxVideo}
+    this.userList = []
+    // streamList 存储steam 对象列表,用于 trtc-room 渲染 player
+    this.streamList = []
+    this._emitter = new Event()
+    this.componentContext = componentContext
+  }
+  userEventHandler(event) {
+    const code = event.detail.code
+    let data
+    if (event.detail.message && typeof event.detail.message === 'string') {
+      try {
+        data = JSON.parse(event.detail.message)
+      } catch (exception) {
+        console.warn(TAG_NAME, 'userEventHandler 数据格式错误', exception)
+        return false
+      }
+    } else {
+      console.warn(TAG_NAME, 'userEventHandler 数据格式错误')
+      return false
+    }
+    switch (code) {
+      case 1031:
+        // console.log(TAG_NAME, '远端用户进房通知:', code)
+        // 1031 有新用户
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11"
+        //          }
+        //      ]
+        // }
+        this.addUser(data)
+        break
+      case 1032:
+        // console.log(TAG_NAME, '远端用户退房通知:', code)
+        // 1032 有用户退出
+        this.removeUser(data)
+        break
+      case 1033:
+        // console.log(TAG_NAME, '远端用户视频状态位变化通知:', code)
+        // 1033 用户视频状态变化,新增stream或者更新stream 状态
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "streamtype":"main",
+        //              "hasvideo":true
+        //          }
+        //      ]
+        // }
+        this.updateUserVideo(data)
+        break
+      case 1034:
+        // console.log(TAG_NAME, '远端用户音频状态位变化通知:', code)
+        // 1034 用户音频状态变化
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "hasaudio":false
+        //          }
+        //      ]
+        // }
+        this.updateUserAudio(data)
+        break
+    }
+  }
+  /**
+   * 处理用户进房事件
+   * @param {Object} data pusher 下发的数据
+   */
+  addUser(data) {
+    // console.log(TAG_NAME, 'addUser', data)
+    const incomingUserList = data.userlist
+    const userMap = this.userMap
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 已经在 map 中的用户
+        let user = this.getUser(userID)
+        if (!user) {
+          // 新增用户
+          user = new User({ userID: userID })
+          this.userList.push({
+            userID: userID,
+          })
+        }
+        userMap.set(userID, user)
+        this._emitter.emit(TRTC_EVENT.REMOTE_USER_JOIN, { userID: userID, userList: this.userList })
+        // console.log(TAG_NAME, 'addUser', item, userMap.get(userID), this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户退房事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  removeUser(data) {
+    // console.log(TAG_NAME, 'removeUser', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        let user = this.getUser(userID)
+        // 偶现SDK触发退房事件前没有触发进房事件
+        if (!user || !user.streams) {
+          return
+        }
+        // 从userList 里删除指定的用户和 stream
+        this._removeUserAndStream(userID)
+        // 重置
+        user.streams['main'] && user.streams['main'].reset()
+        user.streams['aux'] && user.streams['aux'].reset()
+        // 用户退出,释放引用,外部调用该 user 所有stream 的 playerContext.stop() 方法停止播放
+        // TODO 触发时机提前了,方便外部用户做出处理,时机仍需进一步验证
+        this._emitter.emit(TRTC_EVENT.REMOTE_USER_LEAVE, { userID: userID, userList: this.userList, streamList: this.streamList })
+        user = undefined
+        this.userMap.delete(userID)
+        // console.log(TAG_NAME, 'removeUser', this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户视频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserVideo(data) {
+    console.log(TAG_NAME, 'updateUserVideo', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        const streamType = item.streamtype
+        const streamID = userID + '_' + streamType
+        const hasVideo = item.hasvideo
+        const src = item.playurl
+        const user = this.getUser(userID)
+        // 更新指定用户的属性
+        if (user) {
+          // 查找对应的 stream
+          let stream = user.streams[streamType]
+          console.log(TAG_NAME, 'updateUserVideo start', user, streamType, stream)
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户(有音频或视频)的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasVideo, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasVideo })
+            // if (!hasVideo && !stream.hasAudio) {
+            //   this._removeStream(stream)
+            // }
+            // or
+            // if (hasVideo) {
+            //   stream.setProperty({ hasVideo })
+            // } else if (!stream.hasAudio) {
+            //   // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+          // 更新所属user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Video`] = hasVideo
+              return true
+            }
+          })
+          console.log(TAG_NAME, 'updateUserVideo end', user, streamType, stream)
+          const eventName = hasVideo ? TRTC_EVENT.REMOTE_VIDEO_ADD : TRTC_EVENT.REMOTE_VIDEO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserVideo', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   * 处理用户音频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserAudio(data) {
+    // console.log(TAG_NAME, 'updateUserAudio', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 音频只跟着 stream main ,这里只修改 main
+        const streamType = 'main'
+        const streamID = userID + '_' + streamType
+        const hasAudio = item.hasaudio
+        const src = item.playurl
+        const user = this.getUser(userID)
+        if (user) {
+          let stream = user.streams[streamType]
+          // if (!stream) {
+          //   user.streams[streamType] = stream = new Stream({ streamType: streamType })
+          //   this._addStream(stream)
+          // }
+
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasAudio, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasAudio })
+            // if (!hasAudio && !stream.hasVideo) {
+            //   this._removeStream(stream)
+            // }
+            // or
+            // if (hasAudio) {
+            //   stream.setProperty({ hasAudio })
+            // } else if (!stream.hasVideo) {
+            // // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+
+          // stream.userID = userID
+          // stream.streamID = userID + '_' + streamType
+          // stream.hasAudio = hasAudio
+          // stream.src = src
+          // 更新所属 user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Audio`] = hasAudio
+              return true
+            }
+          })
+          const eventName = hasAudio ? TRTC_EVENT.REMOTE_AUDIO_ADD : TRTC_EVENT.REMOTE_AUDIO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserAudio', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   *
+   * @param {String} userID 用户ID
+   * @returns {Object}
+   */
+  getUser(userID) {
+    return this.userMap.get(userID)
+  }
+  getStream({ userID, streamType }) {
+    const user = this.userMap.get(userID)
+    if (user) {
+      return user.streams[streamType]
+    }
+    return undefined
+  }
+  getUserList() {
+    return this.userList
+  }
+  getStreamList() {
+    return this.streamList
+  }
+  /**
+   * 重置所有user 和 steam
+   * @returns {Object}
+   */
+  reset() {
+    this.streamList.forEach((item)=>{
+      item.reset()
+    })
+    this.streamList = []
+    this.userList = []
+    this.userMap.clear()
+    return {
+      userList: this.userList,
+      streamList: this.streamList,
+    }
+  }
+  on(eventCode, handler, context) {
+    this._emitter.on(eventCode, handler, context)
+  }
+  off(eventCode, handler) {
+    this._emitter.off(eventCode, handler)
+  }
+  /**
+   * 删除用户和所有的 stream
+   * @param {String} userID 用户ID
+   */
+  _removeUserAndStream(userID) {
+    this.streamList = this.streamList.filter((item)=>{
+      return item.userID !== userID && item.userID !== ''
+    })
+    this.userList = this.userList.filter((item)=>{
+      return item.userID !== userID
+    })
+  }
+  _addStream(stream) {
+    if (!this.streamList.includes(stream)) {
+      this.streamList.push(stream)
+    }
+  }
+  _removeStream(stream) {
+    console.warn('==========', stream)
+    this.streamList = this.streamList.filter((item)=>{
+      if (item.userID === stream.userID && item.streamType === stream.streamType) {
+        return false
+      }
+      return true
+    })
+    const user = this.getUser(stream.userID)
+    user.streams[stream.streamType] = undefined
+  }
+}
+
+export default UserController

+ 14 - 0
components/TRTCCalling/libs/mta_analysis.js

@@ -0,0 +1,14 @@
+var MTA_CONFIG={app_id:"",event_id:"",api_base:"https://pingtas.qq.com/pingd",prefix:"_mta_",version:"1.3.10",stat_share_app:!1,stat_pull_down_fresh:!1,stat_reach_bottom:!1,stat_param:!0};function getNetworkType(a){wx.getNetworkType({success:function(b){a(b.networkType)}})}
+function getSystemInfo(){var a=wx.getSystemInfoSync();return{adt:encodeURIComponent(a.model),scl:a.pixelRatio,scr:a.windowWidth+"x"+a.windowHeight,lg:a.language,fl:a.version,jv:encodeURIComponent(a.system),tz:encodeURIComponent(a.platform)}}function getUID(){try{return wx.getStorageSync(MTA_CONFIG.prefix+"auid")}catch(a){}}function setUID(){try{var a=getRandom();wx.setStorageSync(MTA_CONFIG.prefix+"auid",a);return a}catch(b){}}
+function getSID(){try{return wx.getStorageSync(MTA_CONFIG.prefix+"ssid")}catch(a){}}function setSID(){try{var a="s"+getRandom();wx.setStorageSync(MTA_CONFIG.prefix+"ssid",a);return a}catch(b){}}function getRandom(a){for(var b=[0,1,2,3,4,5,6,7,8,9],c=10;1<c;c--){var d=Math.floor(10*Math.random()),f=b[d];b[d]=b[c-1];b[c-1]=f}for(c=d=0;5>c;c++)d=10*d+b[c];return(a||"")+(d+""+ +new Date)}
+function getPagePath(){try{var a=getCurrentPages(),b="/";0<a.length&&(b=a.pop().__route__);return b}catch(c){console.log("get current page path error:"+c)}}function getMainInfo(){var a={dm:"wechat.apps.xx",url:encodeURIComponent(getPagePath()+getQuery(MTA.Data.pageQuery)),pvi:"",si:"",ty:0};a.pvi=function(){var b=getUID();b||(b=setUID(),a.ty=1);return b}();a.si=function(){var a=getSID();a||(a=setSID());return a}();return a}
+function getBasicInfo(){var a=getSystemInfo();getNetworkType(function(a){try{wx.setStorageSync(MTA_CONFIG.prefix+"ntdata",a)}catch(c){}});a.ct=wx.getStorageSync(MTA_CONFIG.prefix+"ntdata")||"4g";return a}function getExtentInfo(){var a=MTA.Data.userInfo;var b=[],c;for(c in a)a.hasOwnProperty(c)&&b.push(c+"="+a[c]);a=b.join(";");return{r2:MTA_CONFIG.app_id,r4:"wx",ext:"v="+MTA_CONFIG.version+(null!==a&&""!==a?";ui="+encodeURIComponent(a):"")}}
+function getQuery(a){if(!MTA_CONFIG.stat_param||!a)return"";a=ignoreParams(a);var b=[],c;for(c in a)b.push(c+"="+a[c]);return 0<b.length?"?"+b.join("&"):""}function ignoreParams(a){if(1>MTA_CONFIG.ignore_params.length)return a;var b={},c;for(c in a)0<=MTA_CONFIG.ignore_params.indexOf(c)||(b[c]=a[c]);return b}
+function initOnload(){var a=Page;Page=function(b){var c=b.onLoad;b.onLoad=function(a){c&&c.call(this,a);MTA.Data.lastPageQuery=MTA.Data.pageQuery;MTA.Data.pageQuery=a;MTA.Data.lastPageUrl=MTA.Data.pageUrl;MTA.Data.pageUrl=getPagePath();MTA.Data.show=!1;MTA.Page.init()};a(b)}}
+var MTA={App:{init:function(a){"appID"in a&&(MTA_CONFIG.app_id=a.appID);"eventID"in a&&(MTA_CONFIG.event_id=a.eventID);"statShareApp"in a&&(MTA_CONFIG.stat_share_app=a.statShareApp);"statPullDownFresh"in a&&(MTA_CONFIG.stat_pull_down_fresh=a.statPullDownFresh);"statReachBottom"in a&&(MTA_CONFIG.stat_reach_bottom=a.statReachBottom);"ignoreParams"in a&&(MTA_CONFIG.ignore_params=a.ignoreParams);"statParam"in a&&(MTA_CONFIG.stat_param=a.statParam);setSID();try{"lauchOpts"in a&&(MTA.Data.lanchInfo=a.lauchOpts,
+  MTA.Data.lanchInfo.landing=1)}catch(b){}"autoReport"in a&&a.autoReport&&initOnload()}},Page:{init:function(){var a=getCurrentPages()[getCurrentPages().length-1];a.onShow&&!function(){var b=a.onShow;a.onShow=function(){if(!0===MTA.Data.show){var a=MTA.Data.lastPageQuery;MTA.Data.lastPageQuery=MTA.Data.pageQuery;MTA.Data.pageQuery=a;MTA.Data.lastPageUrl=MTA.Data.pageUrl;MTA.Data.pageUrl=getPagePath()}MTA.Data.show=!0;MTA.Page.stat();b.apply(this,arguments)}}();MTA_CONFIG.stat_pull_down_fresh&&a.onPullDownRefresh&&
+!function(){var b=a.onPullDownRefresh;a.onPullDownRefresh=function(){MTA.Event.stat(MTA_CONFIG.prefix+"pulldownfresh",{url:a.__route__});b.apply(this,arguments)}}();MTA_CONFIG.stat_reach_bottom&&a.onReachBottom&&!function(){var b=a.onReachBottom;a.onReachBottom=function(){MTA.Event.stat(MTA_CONFIG.prefix+"reachbottom",{url:a.__route__});b.apply(this,arguments)}}();MTA_CONFIG.stat_share_app&&a.onShareAppMessage&&!function(){var b=a.onShareAppMessage;a.onShareAppMessage=function(){MTA.Event.stat(MTA_CONFIG.prefix+
+  "shareapp",{url:a.__route__});return b.apply(this,arguments)}}()},multiStat:function(a,b){if(1==b)MTA.Page.stat(a),!0;else{var c=getCurrentPages()[getCurrentPages().length-1];c.onShow&&!function(){var b=c.onShow;c.onShow=function(){MTA.Page.stat(a);b.call(this,arguments)}}()}},stat:function(a){if(""!=MTA_CONFIG.app_id){var b=[],c=getExtentInfo();a&&(c.r2=a);a=[getMainInfo(),c,getBasicInfo()];if(MTA.Data.lanchInfo){a.push({ht:MTA.Data.lanchInfo.scene});MTA.Data.pageQuery&&MTA.Data.pageQuery._mta_ref_id&&
+a.push({rarg:MTA.Data.pageQuery._mta_ref_id});try{1==MTA.Data.lanchInfo.landing&&(c.ext+=";lp=1",MTA.Data.lanchInfo.landing=0)}catch(e){}}a.push({rdm:"/",rurl:0>=MTA.Data.lastPageUrl.length?MTA.Data.pageUrl+getQuery(MTA.Data.lastPageQuery):encodeURIComponent(MTA.Data.lastPageUrl+getQuery(MTA.Data.lastPageQuery))});a.push({rand:+new Date});c=0;for(var d=a.length;c<d;c++)for(var f in a[c])a[c].hasOwnProperty(f)&&b.push(f+"="+("undefined"==typeof a[c][f]?"":a[c][f]));wx.request({url:MTA_CONFIG.api_base+
+"?"+b.join("&").toLowerCase()})}}},Event:{stat:function(a,b){if(""!=MTA_CONFIG.event_id){var c=[],d=getMainInfo(),f=getExtentInfo();d.dm="wxapps.click";d.url=a;f.r2=MTA_CONFIG.event_id;var e="undefined"===typeof b?{}:b;var k=[],g;for(g in e)e.hasOwnProperty(g)&&k.push(encodeURIComponent(g)+"="+encodeURIComponent(e[g]));e=k.join(";");f.r5=e;e=0;d=[d,f,getBasicInfo(),{rand:+new Date}];for(f=d.length;e<f;e++)for(var h in d[e])d[e].hasOwnProperty(h)&&c.push(h+"="+("undefined"==typeof d[e][h]?"":d[e][h]));
+  wx.request({url:MTA_CONFIG.api_base+"?"+c.join("&").toLowerCase()})}}},Data:{userInfo:null,lanchInfo:null,pageQuery:null,lastPageQuery:null,pageUrl:"",lastPageUrl:"",show:!1}};module.exports=MTA;

+ 38 - 0
components/TRTCCalling/model/stream.js

@@ -0,0 +1,38 @@
+// 一个stream 对应一个 player
+import { DEFAULT_PLAYER_CONFIG } from '../common/constants.js'
+
+class Stream {
+  constructor(options) {
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '', // userID + '_' + streamType
+      isVisible: true, // 手Q初始化时不能隐藏 puser和player 否则黑屏。iOS 微信初始化时不能隐藏,否则同层渲染失败,player会置顶
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined, // playerContext 依赖component context来获取,目前只能在渲染后获取
+    }, options)
+  }
+  setProperty(options) {
+    Object.assign(this, options)
+  }
+  reset() {
+    if (this.playerContext) {
+      this.playerContext.stop()
+      this.playerContext = undefined
+    }
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '',
+      isVisible: true,
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined,
+    })
+  }
+}
+
+export default Stream

+ 17 - 0
components/TRTCCalling/model/user.js

@@ -0,0 +1,17 @@
+class User {
+  constructor(options) {
+    Object.assign(this, {
+      userID: '',
+      // hasMainStream: false, // 触发 1034 且stream type 为 main 即为true
+      // hasAuxStream: false, // 触发 1034 且stream type 为 aux 即为true
+      // hasSmallStream: false, // 触发 1034 且stream type 为 small 即为true
+      streams: {
+        // main: mainStream
+        // aux: auxStream
+      }, // 有0~2个Stream, 进房没有推流,main aux, small 特殊处理,small 和 main 同时只播放一路
+      // stream 是用于渲染 live-player 的数据源
+    }, options)
+  }
+}
+
+export default User

BIN
components/TRTCCalling/static/audio-active.png


BIN
components/TRTCCalling/static/audio-false.png


BIN
components/TRTCCalling/static/audio-true.png


BIN
components/TRTCCalling/static/avatar0_100.png


BIN
components/TRTCCalling/static/avatar1_100.png


BIN
components/TRTCCalling/static/avatar2_100.png


BIN
components/TRTCCalling/static/avatar3_100.png


BIN
components/TRTCCalling/static/avatar4_100.png


BIN
components/TRTCCalling/static/avatar5_100.png


BIN
components/TRTCCalling/static/hangup.png


BIN
components/TRTCCalling/static/micro-open.png


BIN
components/TRTCCalling/static/phone.png


BIN
components/TRTCCalling/static/speaker-true.png


+ 64 - 0
components/TRTCCalling/template/audio-template/audio-template.wxml

@@ -0,0 +1,64 @@
+<!-- 语音通话模版占位样式 您应根据自己的业务需要进行调整 -->
+<template name="audio-template">
+    <view  class="audio-place-holder">
+        <view class="{{'TRTCCalling-call-audio-img' + streamList.length}}">
+            <image src="./static/avatar1_100.png" class="img-place-holder">
+                <view class="audio-volume">
+                <image wx:if="{{pusherConfig.volume>10}}" class="image" src="./static/micro-open.png"></image>
+                </view>
+            </image>
+        </view>
+        <view class="{{'TRTCCalling-call-audio-img' + streamList.length}}" wx:for="{{streamList}}" wx:key="userID">
+            <image src="./static/avatar2_100.png" class="img-place-holder">
+                <view class="audio-volume">
+                <image wx:if="{{item.volume>10}}" class="image" src="./static/micro-open.png"></image>
+                </view>
+            </image>
+        </view>
+        <live-pusher
+        class="pusher-audio"
+        id="pusher"
+        mode="RTC"
+        autopush="{{true}}"
+        url="{{pusherConfig.pushUrl}}"
+        audio-volume-type="voicecall"
+        enable-camera="{{false}}"
+        enable-mic="{{true}}"
+        bindstatechange="_pusherStateChangeHandler"
+        bindaudiovolumenotify="_pusherAudioVolumeNotify"
+        />
+        <view wx:for="{{streamList}}" wx:key="streamID" class="view-container player-container player-audio">
+        <live-player
+            class="player-audio" 
+            id="{{item.streamID}}"
+            data-userid="{{item.userID}}"
+            data-streamid="{{item.streamID}}"
+            data-streamtype="{{item.streamType}}"
+            src= "{{item.src}}"
+            mode= "RTC"
+            object-fit="fillCrop"
+            autoplay= "{{true}}"
+            mute-video="{{true}}"
+            mute-audio="{{item.muteAudio}}"
+            min-cache= "0.2"
+            max-cache= "0.8"
+            sound-mode= "{{soundMode}}"
+            auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"
+            auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"
+            bindstatechange="_playerStateChange"
+            bindaudiovolumenotify="_playerAudioVolumeNotify"
+        />
+        </view>
+    </view>
+    <view class="handle-btns">
+        <view class="btn-normal" bindtap="_toggleAudio">
+            <image class="btn-image" src="{{pusherConfig.enableMic ? './static/audio-true.png': './static/audio-false.png'}} "></image>
+        </view>
+        <view class="btn-hangup" bindtap="_hangUp">
+            <image class="btn-image" src="./static/hangup.png"></image>
+        </view>
+        <view class="btn-normal" bindtap="_toggleSoundMode">
+            <image class="btn-image" src="{{soundMode === 'ear' ? './static/phone.png': './static/speaker-true.png'}} "></image>
+        </view>
+    </view>
+</template>

+ 92 - 0
components/TRTCCalling/template/audio-template/audio-template.wxss

@@ -0,0 +1,92 @@
+.audio-place-holder {
+    width: 100%;
+    height: 100%;
+}
+
+.audio-place-holder .TRTCCalling-call-audio-img0 {
+  margin: 30vw 30vw;
+  width: 40vw;
+  height: 40vw;
+}
+.audio-place-holder  .TRTCCalling-call-audio-img1 {
+  display: inline-block;
+  width: 40vw;
+  height: 40vw;
+  margin-left: 6.5vw;
+  margin-top: 40vw;
+}
+
+.audio-place-holder .pusher-audio {
+  width: 0;
+  height: 0;
+}
+
+.audio-place-holder .player-audio{
+  width: 0;
+  height: 0;
+}
+
+.audio-place-holder .audio-volume {
+  position: absolute;
+  bottom: 20rpx;
+  left: 20rpx;
+  width: 36rpx;
+  height: 36rpx;
+}
+
+.audio-place-holder .audio-active {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+}
+
+.audio-place-holder .audio-active .image{
+  position: absolute;
+  bottom: 0;
+}
+
+.audio-place-holder .img-place-holder {
+  position: absolute;
+  width: 40vw;
+  height: 40vw;
+}
+
+.audio-place-holder .handle-btns {
+    position: absolute;
+    z-index: 3;
+    bottom: 3vh;
+    width: 100vw;
+    z-index: 3;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-around;
+  }
+  
+  .audio-place-holder .btn-normal {
+    width: 8vh;
+    height: 8vh;
+    box-sizing: border-box;
+    display: flex;
+    background: white;
+    justify-content: center;
+    align-items: center;
+    border-radius: 50%;
+  }
+  .audio-place-holder .btn-image{
+    width: 4vh;
+    height: 4vh;
+  }
+  .audio-place-holder .btn-hangup  {
+    width: 8vh;
+    height: 8vh;
+    background: #f75c45;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 50%;
+  }
+  

+ 47 - 0
components/TRTCCalling/template/video-template/video-template.wxml

@@ -0,0 +1,47 @@
+<template name="video-template">
+	<live-pusher
+		class="pusher-video"
+		id="pusher"
+		autopush="{{true}}"
+		url="{{pusherConfig.pushUrl}}"
+		enable-camera="{{pusherConfig.enableCamera}}"
+		audio-volume-type="voicecall"
+		beauty="5"
+		enable-mic="{{true}}"
+		bindstatechange="_pusherStateChangeHandler"
+		bindaudiovolumenotify="_pusherAudioVolumeNotify"
+	/>
+	<view wx:for="{{streamList}}" wx:key="streamID" class="view-container player-container">
+		<live-player
+			class="{{'player'+streamList.length}}"
+			id="{{item.streamID}}"
+			data-userid="{{item.userID}}"
+			data-streamid="{{item.streamID}}"
+			data-streamtype="{{item.streamType}}"
+			src= "{{item.src}}"
+			mode= "RTC"
+			object-fit="fillCrop"
+			autoplay= "{{true}}"
+			mute-video="{{item.muteVideo}}"
+			mute-audio="{{item.muteAudio}}"
+			min-cache= "0.2"
+			max-cache= "0.8"
+			sound-mode= "{{soundMode}}"
+			auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"
+			auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"
+			bindstatechange="_playerStateChange"
+			bindaudiovolumenotify  ="_playerAudioVolumeNotify"
+		/>
+	</view>
+	<view class="handle-btns">
+		<view class="btn-normal" bindtap="_toggleAudio">
+			<image class="btn-image" src="{{pusherConfig.enableMic? './static/audio-true.png': './static/audio-false.png'}} "></image>
+		</view>
+		<view class="btn-hangup" bindtap="_hangUp">
+			<image class="btn-image" src="./static/hangup.png"></image>
+		</view>
+		<view class="btn-normal" bindtap="_toggleSoundMode">
+			<image class="btn-image" src="{{soundMode === 'ear' ? './static/phone.png': './static/speaker-true.png'}} "></image>
+		</view>
+	</view>
+</template>

+ 69 - 0
components/TRTCCalling/template/video-template/video-template.wxss

@@ -0,0 +1,69 @@
+.pusher-video {
+  position: absolute;
+  right: 2vw;
+  top: 2vw;
+  width: 160px;
+  height: 200px;
+  z-index: 3;
+}
+
+.player1 {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background-color: #f75c45;
+  z-index: 1;
+}
+
+.player2 {
+  width: 40vw;
+  height: 40vw;
+  background-color: #f75c45;
+  z-index: 1;
+}
+
+.handle-btns {
+  position: absolute;
+  z-index: 3;
+  bottom: 3vh;
+  width: 100vw;
+  z-index: 3;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+}
+
+.btn-normal {
+  width: 8vh;
+  height: 8vh;
+  box-sizing: border-box;
+  display: flex;
+  background: white;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+.btn-image{
+  width: 4vh;
+  height: 4vh;
+}
+.btn-hangup  {
+  width: 8vh;
+  height: 8vh;
+  background: #f75c45;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+
+.TRTCCalling-call-audio {
+  width: 100%;
+  height: 100%;
+}
+
+image {
+  width: 100%;
+  height: 100%;
+}

+ 21 - 0
components/TRTCCalling/utils/compare-version.js

@@ -0,0 +1,21 @@
+export default function compareVersion(v1, v2) {
+  v1 = v1.split('.')
+  v2 = v2.split('.')
+  const len = Math.max(v1.length, v2.length)
+  while (v1.length < len) {
+    v1.push('0')
+  }
+  while (v2.length < len) {
+    v2.push('0')
+  }
+  for (let i = 0; i < len; i++) {
+    const num1 = parseInt(v1[i])
+    const num2 = parseInt(v2[i])
+    if (num1 > num2) {
+      return 1
+    } if (num1 < num2) {
+      return -1
+    }
+  }
+  return 0
+}

+ 50 - 0
components/TRTCCalling/utils/environment.js

@@ -0,0 +1,50 @@
+import compareVersion from './compare-version.js'
+const TAG_NAME = 'TRTC-ROOM'
+
+const env = wx ? wx : qq
+if (!env) {
+  console.error(TAG_NAME, '不支持当前小程序环境')
+}
+const systemInfo = env.getSystemInfoSync()
+const safeArea = systemInfo.safeArea
+if (systemInfo.system === 'iOS 13.3' || (systemInfo.model === 'iPhoneX' && systemInfo.system === 'iOS 13.3.1') ) {
+  // audio-volume-type = media
+  console.log('use media audio volume type')
+}
+console.log(TAG_NAME, 'SystemInfo', systemInfo)
+let isNewVersion
+if (typeof qq !== 'undefined') {
+  isNewVersion = true
+} else if (typeof wx !== 'undefined') {
+  if (compareVersion(systemInfo.version, '7.0.8') >= 0 || // mobile pc
+  (compareVersion(systemInfo.version, '2.4.0') >= 0 && compareVersion(systemInfo.version, '6.0.0') < 0) && // mac os
+  compareVersion(systemInfo.SDKVersion, '2.10.0') >= 0) {
+    isNewVersion = true
+  } else {
+    isNewVersion = false
+  }
+}
+
+export const IS_TRTC = isNewVersion
+export const IS_QQ = typeof qq !== 'undefined'
+export const IS_WX = typeof wx !== 'undefined'
+export const IS_IOS = /iOS/i.test(systemInfo.system)
+export const IS_ANDROID = /Android/i.test(systemInfo.system)
+export const IS_MAC = /mac/i.test(systemInfo.system)
+export const APP_VERSION = systemInfo.version
+export const LIB_VERSION = (function() {
+  if (systemInfo.SDKBuild) {
+    return systemInfo.SDKVersion + '-' + systemInfo.SDKBuild
+  }
+  return systemInfo.SDKVersion
+})()
+
+let isFullscreenDevie = false
+if (systemInfo.screenHeight > safeArea.bottom) {
+// if (/iphone\s{0,}x/i.test(systemInfo.model)) {
+  isFullscreenDevie = true
+}
+
+export const IS_FULLSCREEN_DEVICE = isFullscreenDevie
+
+console.log(TAG_NAME, 'APP_VERSION:', APP_VERSION, ' LIB_VERSION:', LIB_VERSION, ' is new version:', IS_TRTC)

+ 62 - 0
components/TRTCCalling/utils/event.js

@@ -0,0 +1,62 @@
+class EventEmitter {
+  on(event, fn, ctx) {
+    if (typeof fn !== 'function') {
+      console.error('listener must be a function')
+      return
+    }
+
+    this._stores = this._stores || {};
+    (this._stores[event] = this._stores[event] || []).push({ cb: fn, ctx: ctx })
+  }
+
+  emit(event) {
+    this._stores = this._stores || {}
+    let store = this._stores[event]
+    let args
+
+    if (store) {
+      store = store.slice(0)
+      args = [].slice.call(arguments, 1),
+      args[0] = {
+        eventCode: event,
+        data: args[0],
+      }
+      for (let i = 0, len = store.length; i < len; i++) {
+        store[i].cb.apply(store[i].ctx, args)
+      }
+    }
+  }
+
+  off(event, fn) {
+    this._stores = this._stores || {}
+
+    // all
+    if (!arguments.length) {
+      this._stores = {}
+      return
+    }
+
+    // specific event
+    const store = this._stores[event]
+    if (!store) return
+
+    // remove all handlers
+    if (arguments.length === 1) {
+      delete this._stores[event]
+      return
+    }
+
+    // remove specific handler
+    let cb
+    for (let i = 0, len = store.length; i < len; i++) {
+      cb = store[i].cb
+      if (cb === fn) {
+        store.splice(i, 1)
+        break
+      }
+    }
+    return
+  }
+}
+
+module.exports = EventEmitter

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
components/TRTCCalling/utils/tsignaling-wx.js


+ 61 - 0
components/float-menu/index.js

@@ -0,0 +1,61 @@
+const App = getApp();
+
+Component({
+  options: {
+    addGlobalClass: true,
+  },
+  /**
+   * 组件的对外属性,是属性名到属性设置的映射表
+   */
+  properties: {
+    activePos: String,    
+  },
+
+  /**
+   * 组件的内部数据,和 properties 一同用于组件的模板渲染
+   */
+  data: {
+    isClose: true
+  },
+  // 组件数据字段监听器,用于监听 properties 和 data 的变化
+  observers: {
+
+  },
+  lifetimes: {
+    attached: function () {
+      this.setData({
+        
+      })
+    },
+    detached: function () {
+      // 在组件实例被从页面节点树移除时执行
+    },
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    //回退
+    open(){
+      this.setData({
+        isClose: false
+      })
+    },
+    close(){
+      this.setData({
+        isClose: true
+      })
+    },
+    navBack: function () {
+      wx.navigateBack({
+        delta: 1
+      })
+    },
+    //回主页
+    toIndex: function () {
+      wx.navigateTo({
+        url: '/pages/admin/home/index/index'
+      })
+    },
+  }
+})

+ 6 - 0
components/float-menu/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    
+  }
+}

+ 28 - 0
components/float-menu/index.wxml

@@ -0,0 +1,28 @@
+<view wx:if="{{isClose}}" class="float-menu-cover" bindtap="open">
+  <image class="arrows" src="/images/icon/go-l.svg"></image>
+</view>
+<view wx:else class="float-menu-cover float-menu-cover-open" bindtap="close">
+  <image class="arrows" src="/images/icon/go-r.svg"></image>
+</view>
+<view wx:if="{{!isClose}}" class="float-menu-items">
+  <navigator url="/pages/index/index" open-type="switchTab">
+    <view class="item">
+      <image src="/images/nav/home-off.png"></image>
+    </view>
+  </navigator>
+  <navigator url="/pages/category/category" open-type="switchTab">
+    <view class="item">
+      <image src="/images/nav/ic_catefory_normal.png"></image>
+    </view>
+  </navigator>
+  <navigator url="/pages/shop-cart/index" open-type="switchTab">
+    <view class="item">
+      <image src="/images/nav/cart-off.png"></image>
+    </view>
+  </navigator>
+  <navigator url="/pages/my/index" open-type="switchTab">
+    <view class="item">
+      <image src="/images/nav/my-off.png"></image>
+    </view>
+  </navigator>
+</view>

+ 52 - 0
components/float-menu/index.wxss

@@ -0,0 +1,52 @@
+.float-menu-cover {
+  position: fixed;
+  right: 0;
+  bottom: 200rpx;
+  display: flex;
+  align-items: center;
+  padding: 16rpx 10rpx 16rpx 10rpx;
+  background:rgba(0,0,0,0.7);
+  border-radius: 16rpx 0 0 16rpx;
+  z-index: 999;
+}
+.float-menu-cover-open {
+  right: 380rpx;
+}
+.float-menu-cover .arrows {
+  width: 34rpx;
+  height: 34rpx;
+}
+.float-menu-cover .r {
+  color: #fff;
+  display: flex;
+  flex-direction: column;
+  font-size: 10pt;
+  margin-left: 16rpx;
+}
+.float-menu-items {
+  width: 380rpx;
+  background: rgba(250,250,250,0.7);
+  position: fixed;
+  right: 0;
+  bottom: 192rpx;
+  border-radius: 16rpx 0 0 16rpx;
+  display: flex;
+  flex-wrap: wrap;
+  /* padding-bottom: 32rpx; */
+  z-index: 999;
+}
+.float-menu-items .item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 20rpx 16rpx 20rpx 32rpx;
+}
+.float-menu-items .item image {
+  width: 43rpx;
+  height: 43rpx;
+}
+.float-menu-items .item text {
+  margin-top: 8rpx;
+  color: #333;
+  font-size: 10pt;
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 0
components/parser.20200414.min/libs/CssHandler.js


Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 0
components/parser.20200414.min/libs/MpHtmlParser.js


+ 1 - 0
components/parser.20200414.min/libs/config.js

@@ -0,0 +1 @@
+"use strict";function makeMap(e){for(var a={},t=e.split(","),l=t.length;l--;)a[t[l]]=!0;return a}var canIUse=wx.canIUse("editor");module.exports={filter:null,highlight:null,onText:null,blankChar:makeMap(" , ,\t,\r,\n,\f"),blockTags:makeMap("address,article,aside,body,caption,center,cite,footer,header,html,nav,section"+(canIUse?"":",pre")),ignoreTags:makeMap("area,base,basefont,canvas,command,embed,frame,iframe,input,isindex,keygen,link,map,meta,param,script,source,style,svg,textarea,title,track,use,wbr"+(canIUse?",rp":"")),richOnlyTags:makeMap("a,colgroup,fieldset,legend,picture,table"+(canIUse?",bdi,bdo,rt,ruby":"")),selfClosingTags:makeMap("area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr"),trustAttrs:makeMap("align,alt,app-id,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,width,xmlns"),boolAttrs:makeMap("autoplay,controls,ignore,loop,muted"),trustTags:makeMap("a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video"+(canIUse?",bdi,bdo,caption,pre,rt,ruby":"")),userAgentStyles:{address:"font-style:italic",big:"display:inline;font-size:1.2em",blockquote:"background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px",caption:"display:table-caption;text-align:center",center:"text-align:center",cite:"font-style:italic",dd:"margin-left:40px",mark:"background-color:yellow",pre:"font-family:monospace;white-space:pre;overflow:scroll",s:"text-decoration:line-through",small:"display:inline;font-size:0.8em",u:"text-decoration:underline"}};

Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 0
components/parser.20200414.min/parser.js


+ 1 - 0
components/parser.20200414.min/parser.json

@@ -0,0 +1 @@
+{"component":true,"usingComponents":{"trees":"./trees/trees"}}

+ 1 - 0
components/parser.20200414.min/parser.wxml

@@ -0,0 +1 @@
+<slot wx:if="{{!html[0].name&&!html[0].type}}"/><trees class="top"style="{{selectable?'user-select:text;-webkit-user-select:text;':''}}{{showAm}}"animation="{{scaleAm}}"lazy-load="{{lazyLoad}}"nodes="{{html[0].name||html[0].type?html:[]}}"bindtap="_tap"bindtouchstart="_touchstart"bindtouchmove="_touchmove"/><image wx:for="{{imgs}}"wx:key="index"id="{{index}}"src="{{item}}"hidden bindload="_load"/>

+ 1 - 0
components/parser.20200414.min/parser.wxss

@@ -0,0 +1 @@
+:host{display:block;overflow:scroll;webkit-overflow-scrolling:touch}.top{display:inherit}@keyframes show{0%{opacity:0}100%{opacity:1}}

+ 1 - 0
components/parser.20200414.min/trees/handler.wxs

@@ -0,0 +1 @@
+"use strict";var inlineTags={abbr:1,b:1,big:1,code:1,del:1,em:1,i:1,ins:1,label:1,q:1,small:1,span:1,strong:1};module.exports={load:function(e){e.target.dataset.auto&&e.instance.setStyle({width:e.detail.width+"px"})},visited:function(e,i){e.instance.hasClass("_visited")||e.instance.addClass("_visited"),i.callMethod("linkpress",e)},useRichText:function(e){return!e.c&&!inlineTags[e.name]&&-1==(e.attrs.style||"").indexOf("display:inline")}};

Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 0
components/parser.20200414.min/trees/trees.js


+ 1 - 0
components/parser.20200414.min/trees/trees.json

@@ -0,0 +1 @@
+{"component":true,"usingComponents":{"trees":"./trees"}}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
components/parser.20200414.min/trees/trees.wxml


+ 1 - 0
components/parser.20200414.min/trees/trees.wxss

@@ -0,0 +1 @@
+._a{color:#366092;display:inline;padding:1.5px 0 1.5px 0;word-break:break-all}._hover{opacity:.7;text-decoration:underline}._visited{color:#551a8b}._img{height:50px;max-width:100%}:host{display:inline}._blockquote,._div,._p,._ul,._ol,._li{display:block}._b,._strong{font-weight:bold}._code{font-family:monospace}._del{text-decoration:line-through}._em,._i{font-style:italic}._h1{font-size:2em}._h2{font-size:1.5em}._h3{font-size:1.17em}._h5{font-size:.83em}._h6{font-size:.67em}._h1,._h2,._h3,._h4,._h5,._h6{display:block;font-weight:bold}._ins{text-decoration:underline}._li{flex:1;width:0}._ol-bef{margin-right:5px;text-align:right;width:36px}._ul-bef{line-height:normal;margin:0 12px 0 23px}._ol-bef,._ul_bef{flex:none;user-select:none}._ul-p1{display:inline-block;height:.3em;line-height:.3em;overflow:hidden;width:.3em}._ul-p2{border:.05em solid black;border-radius:50%;display:inline-block;height:.23em;width:.23em}._q::before{content:'"'}._q::after{content:'"'}._sub{font-size:smaller;vertical-align:sub}._sup{font-size:smaller;vertical-align:super}.__bdi,.__bdo,.__ruby,.__rt,._svg{display:inline-block}._video{background-color:black;display:inline-block;height:225px;position:relative;width:300px}._video::after{border-color:transparent transparent transparent white;border-style:solid;border-width:15px 0 15px 30px;content:'';left:50%;margin:-15px 0 0 -15px;position:absolute;top:50%}

+ 7 - 0
config.js

@@ -0,0 +1,7 @@
+module.exports = {
+  version: '10.5.1',
+  note: '瘦身代码包', // 这个为版本描述,无需修改
+  //subDomain: 'tz', // 此处改成你自己的专属域名。什么是专属域名?请看教程 https://www.it120.cc/help/qr6l4m.html
+  subDomain:'1a46f279c87062eab151b4707979c281',
+  sdkAppID: 1400450467, // 腾讯实时音视频应用编号,请看教程 https://www.it120.cc/help/nxoqsl.html
+}

BIN
images/face.png


BIN
images/fx.png


BIN
images/fxad.jpeg


BIN
images/gift.png


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
images/icon/car.svg


+ 1 - 0
images/icon/close.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1578190789782" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1886" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M810.666667 273.493333L750.506667 213.333333 512 451.84 273.493333 213.333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z" p-id="1887" fill="#999999"></path></svg>

+ 1 - 0
images/icon/close0.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1580811177884" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3261" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.390625" height="200"><defs><style type="text/css"></style></defs><path d="M512.023273 46.545455c256.651636 0 465.454545 208.802909 465.454545 465.454545s-208.802909 465.454545-465.454545 465.454545-465.454545-208.802909-465.454545-465.454545S255.371636 46.545455 512.023273 46.545455M512.023273 0c-282.763636 0-512 229.236364-512 512s229.236364 512 512 512 512-229.236364 512-512S794.810182 0 512.023273 0L512.023273 0z" p-id="3262" fill="#cdcdcd"></path><path d="M725.76 749.032727c-5.957818 0-11.915636-2.280727-16.453818-6.818909l-427.450182-427.52c-9.099636-9.099636-9.099636-23.831273 0-32.907636 9.099636-9.099636 23.808-9.099636 32.907636 0l427.450182 427.52c9.099636 9.099636 9.099636 23.831273 0 32.907636C737.675636 746.752 731.717818 749.032727 725.76 749.032727z" p-id="3263" fill="#cdcdcd"></path><path d="M298.263273 749.009455c-5.957818 0-11.915636-2.280727-16.453818-6.818909-9.099636-9.099636-9.099636-23.831273 0-32.907636L709.352727 281.809455c9.099636-9.099636 23.808-9.099636 32.907636 0s9.099636 23.831273 0 32.907636L314.717091 742.190545C310.178909 746.728727 304.221091 749.009455 298.263273 749.009455z" p-id="3264" fill="#cdcdcd"></path></svg>

Datei-Diff unterdrückt, da er zu groß ist
+ 12 - 0
images/icon/coupons-active.svg


Datei-Diff unterdrückt, da er zu groß ist
+ 12 - 0
images/icon/coupons-off.svg


+ 1 - 0
images/icon/delete.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1583769449736" class="icon" viewBox="0 0 1195 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3211" xmlns:xlink="http://www.w3.org/1999/xlink" width="233.3984375" height="200"><defs><style type="text/css"></style></defs><path d="M1174.514354 107.471458H20.367301a20.367301 20.367301 0 0 0 0 40.734602h100.410794a20.367301 20.367301 0 0 0 0 4.412915l55.602731 777.284095v1.833057a109.508188 109.508188 0 0 0 108.625605 92.195982h625.072466a109.508188 109.508188 0 0 0 108.625605-92.195982v-0.882583l55.670622-778.234569a20.367301 20.367301 0 0 0 0-4.412915h100.410794a20.367301 20.367301 0 0 0 0-40.734602z m-140.805941 42.228204l-55.53484 776.333621a68.909368 68.909368 0 0 1-67.891003 57.232115H284.870649a68.909368 68.909368 0 0 1-67.891003-57.232115L161.173241 149.699662v-1.493602H1033.979977c-0.067891 0.543128-0.271564 1.018365-0.271564 1.493602zM456.566996 40.734602h281.747663a20.367301 20.367301 0 0 0 0-40.734602h-281.747663a20.367301 20.367301 0 1 0 0 40.734602z" p-id="3212" fill="#FF3B30"></path><path d="M387.250282 871.245243a20.367301 20.367301 0 0 0 20.367301-20.367301V293.76437a20.367301 20.367301 0 0 0-40.734602 0v557.113572a20.367301 20.367301 0 0 0 20.367301 20.367301zM597.440827 871.245243a20.367301 20.367301 0 0 0 20.367301-20.367301V293.76437a20.367301 20.367301 0 0 0-40.734602 0v557.113572a20.367301 20.367301 0 0 0 20.367301 20.367301zM807.631373 871.245243a20.367301 20.367301 0 0 0 20.367301-20.367301V293.76437a20.367301 20.367301 0 0 0-40.734602 0v557.113572a20.367301 20.367301 0 0 0 20.367301 20.367301z" p-id="3213" fill="#FF3B30"></path></svg>

+ 1 - 0
images/icon/edit.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1580813641723" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4895" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><defs><style type="text/css"></style></defs><path d="M960 460.8c-19.2 0-38.4 12.8-38.4 32v38.4c0 19.2 19.2 32 38.4 32s38.4-12.8 38.4-32v-38.4c0-19.2-19.2-32-38.4-32zM1011.2 38.4c-12.8-19.2-38.4-19.2-57.6 0l-556.8 556.8c-19.2 12.8-19.2 38.4 0 57.6 12.8 19.2 38.4 19.2 57.6 0l556.8-556.8c19.2-12.8 19.2-38.4 0-57.6z" p-id="4896" fill="#cdcdcd"></path><path d="M960 620.8c-25.6 0-38.4 19.2-38.4 38.4v192c0 32-25.6 64-64 64h-684.8c-32 0-64-25.6-64-64v-684.8c0-32 25.6-64 64-64h416c19.2 0 38.4-19.2 38.4-38.4s-19.2-38.4-38.4-38.4h-416c-76.8 0-140.8 64-140.8 140.8v691.2c0 76.8 64 140.8 140.8 140.8h684.8c76.8 0 140.8-64 140.8-140.8v-198.4c0-25.6-19.2-38.4-38.4-38.4z" p-id="4897" fill="#cdcdcd"></path></svg>

+ 1 - 0
images/icon/fav0.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1583767131119" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2128" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><defs><style type="text/css"></style></defs><path d="M1022.955213 394.773333c-2.56-7.744-9.173333-13.376-17.258667-14.549333L677.78188 332.586667 531.15788 35.477333c-7.168-14.592-31.125333-14.592-38.314667 0L346.19788 332.586667l-327.893333 47.637333c-8.042667 1.173333-14.72 6.805333-17.258667 14.549333-2.517333 7.722667-0.405333 16.192 5.418667 21.888l237.269333 231.253333L187.712546 974.506667c-1.365333 8 1.92 16.085333 8.512 20.885333 6.570667 4.778667 15.296 5.44 22.485333 1.642667L512.000546 842.816l293.226667 154.218667c3.136 1.621333 6.570667 2.453333 9.984 2.453333 4.437333 0 8.810667-1.408 12.586667-4.096 6.549333-4.778667 9.834667-12.885333 8.490667-20.885333l-56-326.549333 237.290667-231.296C1023.424546 410.986667 1025.451213 402.496 1022.955213 394.773333zM742.400546 625.173333c-5.013333 4.906667-7.36 11.989333-6.144 18.922667l50.581333 294.954667-264.896-139.264C518.827213 798.122667 515.41388 797.312 512.000546 797.312s-6.848 0.810667-9.962667 2.474667L237.14188 939.093333l50.581333-294.997333c1.173333-6.912-1.109333-14.016-6.144-18.922667L67.28588 416.277333l296.192-43.029333c6.954667-1.002667 12.949333-5.397333 16.085333-11.690667L512.000546 93.184l132.458667 268.352c3.114667 6.314667 9.088 10.688 16.064 11.690667l296.192 43.029333L742.400546 625.173333z" p-id="2129" fill="#707070"></path></svg>

+ 1 - 0
images/icon/fav1.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1583768086854" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4234" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><defs><style type="text/css"></style></defs><path d="M1022.976546 394.773333c-2.581333-7.744-9.237333-13.376-17.28-14.549333L677.78188 332.586667 531.115213 35.477333c-7.168-14.592-31.104-14.592-38.272 0L346.19788 332.586667 18.283213 380.224c-8.021333 1.173333-14.72 6.805333-17.237333 14.549333-2.517333 7.722667-0.405333 16.192 5.418667 21.888l237.269333 231.253333L187.712546 974.506667c-1.365333 8 1.92 16.085333 8.512 20.885333 6.570667 4.778667 15.296 5.44 22.485333 1.642667L512.000546 842.816l293.248 154.218667c3.114667 1.621333 6.549333 2.453333 9.962667 2.453333 4.416 0 8.789333-1.408 12.544-4.096 6.592-4.778667 9.877333-12.864 8.533333-20.885333l-56-326.549333 237.248-231.296C1023.424546 410.986667 1025.451213 402.496 1022.976546 394.773333z" p-id="4235" fill="#e64340"></path></svg>

+ 1 - 0
images/icon/go-l.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577530571647" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2470" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M547.584 802.83648c20.89984 20.89984 20.89984 54.80448 0 75.70432-20.89984 20.91008-54.79424 20.91008-75.70432 0l-328.704-328.69376c-10.4448-10.45504-15.6672-24.14592-15.6672-37.84704s5.23264-27.40224 15.6672-37.84704l328.704-328.69376c20.91008-20.91008 54.80448-20.91008 75.70432 0 20.89984 20.89984 20.89984 54.79424 0 75.70432L256.74752 512 547.584 802.83648zM589.96736 512l290.83648-290.83648c20.89984-20.91008 20.89984-54.80448 0-75.70432-20.89984-20.91008-54.79424-20.91008-75.70432 0l-328.704 328.69376c-10.4448 10.45504-15.6672 24.14592-15.6672 37.84704s5.23264 27.40224 15.6672 37.84704l328.704 328.69376c20.91008 20.91008 54.80448 20.91008 75.70432 0 20.89984-20.89984 20.89984-54.80448 0-75.70432L589.96736 512z" p-id="2471" fill="#ffffff"></path></svg>

+ 1 - 0
images/icon/go-r.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577530650739" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4503" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M767.25248 512 476.40576 221.16352c-20.89984-20.91008-20.89984-54.80448 0-75.70432 20.89984-20.91008 54.79424-20.91008 75.70432 0l328.704 328.69376c10.4448 10.45504 15.6672 24.14592 15.6672 37.84704s-5.23264 27.40224-15.6672 37.84704l-328.704 328.69376c-20.91008 20.91008-54.80448 20.91008-75.70432 0-20.89984-20.89984-20.89984-54.80448 0-75.70432L767.25248 512zM143.18592 802.83648c-20.89984 20.89984-20.89984 54.80448 0 75.70432 20.89984 20.91008 54.784 20.91008 75.70432 0l328.704-328.69376c10.4448-10.45504 15.65696-24.14592 15.65696-37.84704s-5.2224-27.40224-15.65696-37.84704l-328.704-328.69376c-20.92032-20.91008-54.80448-20.91008-75.70432 0-20.89984 20.89984-20.89984 54.79424 0 75.70432L434.03264 512 143.18592 802.83648z" p-id="4504" fill="#ffffff"></path></svg>

+ 1 - 0
images/icon/kf.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1580810673475" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2413" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 563.2a153.6 153.6 0 1 0 0-307.2 153.6 153.6 0 0 0 0 307.2z m0 51.2a204.8 204.8 0 1 1 0-409.6 204.8 204.8 0 0 1 0 409.6zM484.9664 665.6a319.2832 319.2832 0 0 0-285.5936 176.4864l-28.2624 56.6272A51.2 51.2 0 0 0 216.8832 972.8h590.2336a51.2 51.2 0 0 0 45.7728-74.0864l-28.2624-56.6272A319.2832 319.2832 0 0 0 539.0336 665.6H484.9664z m0-51.2h54.0672A370.4832 370.4832 0 0 1 870.4 819.2l28.3136 56.6272A102.4 102.4 0 0 1 807.0656 1024H216.9344a102.4 102.4 0 0 1-91.5968-148.1728L153.6 819.2a370.4832 370.4832 0 0 1 331.3664-204.8z" p-id="2414" fill="#008000"></path><path d="M204.8 512v79.9232l-68.2496-19.2a128 128 0 0 1 0-241.408l27.9552-9.8816a358.5536 358.5536 0 0 1 694.9888 0l27.9552 9.8816a128 128 0 0 1 0 241.408L819.2 591.9232V409.6A307.2 307.2 0 1 0 204.8 409.6v102.4z m665.6-132.4032v144.896a76.8 76.8 0 0 0 0-144.896z m-716.8 0a76.8 76.8 0 0 0 0 144.896v-144.896z" p-id="2415" fill="#008000"></path></svg>

+ 1 - 0
images/icon/list1.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577539770172" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1253" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.7 633.9H82.6c-10.8 0-19.5 8.7-19.5 19.5v236.1c0 10.8 8.7 19.5 19.5 19.5h236.1c10.8 0 19.5-8.7 19.5-19.5V653.4c0-10.8-8.8-19.5-19.5-19.5z m-19.6 236h-197v-197h197v197zM941.4 668.1H446.1c-10.8 0-19.5 8.7-19.5 19.5s8.7 19.5 19.5 19.5h495.3c10.8 0 19.5-8.7 19.5-19.5s-8.7-19.5-19.5-19.5zM941.4 835.7H446.1c-10.8 0-19.5 8.7-19.5 19.5s8.7 19.5 19.5 19.5h495.3c10.8 0 19.5-8.7 19.5-19.5s-8.7-19.5-19.5-19.5zM318.7 115H82.6c-10.8 0-19.5 8.7-19.5 19.5v236.1c0 10.8 8.7 19.5 19.5 19.5h236.1c10.8 0 19.5-8.7 19.5-19.5V134.5c0-10.8-8.8-19.5-19.5-19.5z m-19.6 236.1h-197v-197h197v197zM446.1 188.3h495.3c10.8 0 19.5-8.7 19.5-19.5s-8.7-19.5-19.5-19.5H446.1c-10.8 0-19.5 8.7-19.5 19.5s8.7 19.5 19.5 19.5zM941.4 316.8H446.1c-10.8 0-19.5 8.7-19.5 19.5s8.7 19.5 19.5 19.5h495.3c10.8 0 19.5-8.7 19.5-19.5s-8.7-19.5-19.5-19.5z" fill="#333333" p-id="1254"></path></svg>

+ 1 - 0
images/icon/list2.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577539755632" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1012" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M473.1 95.8H114c-10.1 0-18.2 8.2-18.2 18.2v359.1c0 10.1 8.2 18.2 18.2 18.2h359.1c10.1 0 18.2-8.2 18.2-18.2V114c0-10.1-8.2-18.2-18.2-18.2z m-18.2 359.1H132.2V132.2h322.7v322.7zM910 95.8H550.9c-10.1 0-18.2 8.2-18.2 18.2v359.1c0 10.1 8.2 18.2 18.2 18.2H910c10.1 0 18.2-8.2 18.2-18.2V114c0-10.1-8.1-18.2-18.2-18.2z m-18.2 359.1H569.1V132.2h322.7v322.7zM473.1 532.7H114c-10.1 0-18.2 8.2-18.2 18.2V910c0 10.1 8.2 18.2 18.2 18.2h359.1c10.1 0 18.2-8.2 18.2-18.2V550.9c0-10-8.2-18.2-18.2-18.2z m-18.2 359.1H132.2V569.1h322.7v322.7zM910 532.7H550.9c-10.1 0-18.2 8.2-18.2 18.2V910c0 10.1 8.2 18.2 18.2 18.2H910c10.1 0 18.2-8.2 18.2-18.2V550.9c0-10-8.1-18.2-18.2-18.2z m-18.2 359.1H569.1V569.1h322.7v322.7z" fill="#333333" p-id="1013"></path></svg>

BIN
images/icon/next.png


+ 13 - 0
images/icon/next.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="7px" height="12px" viewBox="0 0 7 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
+    <title>下一步/灰</title>
+    <desc>Created with Sketch.</desc>
+    <g id="组件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="门店选择/未选中" transform="translate(-356.000000, -51.000000)" fill="#999999" fill-rule="nonzero">
+            <g id="下一步/灰" transform="translate(356.000000, 51.000000)">
+                <path d="M5.25089885,5.99999479 L0.198273835,10.7287972 C-0.0557102499,10.9662105 -0.0535692318,11.3485263 0.19880548,11.5846996 C0.4522714,11.8216502 0.874492204,11.8364112 1.12850363,11.5989986 L6.90199277,6.20100447 C6.9598436,6.14763335 6.99137823,6.07526723 6.99137823,5.99998525 C6.99137823,5.92469064 6.9598436,5.85236422 6.90199277,5.79895314 L1.12850363,0.400997641 C0.874492204,0.163571389 0.452285072,0.178372421 0.19880548,0.415296713 C-0.0535694232,0.651495777 -0.0557241132,1.03381282 0.198273835,1.27122619 L5.25089885,6.00000278 L5.25089885,6.00000278 L5.25089885,5.99999479 Z" id="路径"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
images/icon/pos-gray.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="10px" height="12px" viewBox="0 0 10 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
+    <title>定位-灰</title>
+    <desc>Created with Sketch.</desc>
+    <g id="组件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="门店选择/选中" transform="translate(-12.000000, -40.000000)" fill="#999999" fill-rule="nonzero">
+            <g id="定位-灰">
+                <g transform="translate(12.000000, 40.000000)">
+                    <path d="M8.20971207,7.81235432 L5.19109528,11.5391157 C4.95183719,11.8308567 4.56123127,11.831991 4.32316263,11.5391157 L1.29261027,7.81235432 C0.56310552,7.01094323 0.102567874,5.93542557 0.102567874,4.78108165 C0.102567874,2.27459983 2.19286328,0.241642776 4.77295417,0.241642776 C7.35255287,0.241642776 9.44286195,2.27459983 9.44286195,4.78108165 C9.44286195,5.96658225 8.97513289,7.00417565 8.20971207,7.81235432 Z M4.77295417,2.5062833 C3.47967532,2.5062833 2.43213504,3.52490167 2.43213504,4.78109454 C2.43213504,6.0377128 3.47967532,7.05589289 4.77295417,7.05589289 C6.06526231,7.05589289 7.11330845,6.0377128 7.11330845,4.78108165 C7.11330845,3.52490167 6.06526231,2.50627041 4.77295417,2.50627041 L4.77295417,2.5062833 Z" id="形状"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 1 - 0
images/icon/search.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577539568042" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2545" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M998.417847 968.923523 738.389131 712.967573c68.050929-73.967263 109.909294-171.800228 109.909294-279.479317 0-230.345352-189.746575-417.036095-423.778057-417.036095-234.030475 0-423.77705 186.690742-423.77705 417.036095 0 230.297023 189.746575 416.986758 423.77705 416.986758 101.131452 0 193.870691-34.971414 266.723355-93.126882l261.095991 256.924553c12.70864 12.514315 33.370499 12.514315 46.078132 0C1011.174816 1001.759377 1011.174816 981.436831 998.417847 968.923523L998.417847 968.923523 998.417847 968.923523zM424.520368 786.354882c-198.040115 0-358.5393-157.977996-358.5393-352.86562 0-194.88863 160.499184-352.866627 358.5393-352.866627C622.562497 80.622635 783.110011 238.600632 783.110011 433.489262 783.110011 628.376886 622.562497 786.354882 424.520368 786.354882L424.520368 786.354882 424.520368 786.354882z" p-id="2546" fill="#5b5b5b"></path></svg>

Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
images/icon/shop-on.svg


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 0
images/icon/shop.svg


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
images/icon/tel-gray.svg


+ 15 - 0
images/icon/time-gray.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="11px" height="11px" viewBox="0 0 11 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
+    <title>时间 -灰</title>
+    <desc>Created with Sketch.</desc>
+    <g id="组件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="门店选择/选中" transform="translate(-12.000000, -64.000000)" fill="#999999" fill-rule="nonzero">
+            <g id="时间--灰">
+                <g transform="translate(12.000000, 64.000000)">
+                    <path d="M5.58077801,0.148999644 C2.64371005,0.148999644 0.260002719,2.53270697 0.260002719,5.46977493 C0.260002719,8.40684289 2.64371005,10.7905502 5.58077801,10.7905502 C8.51784597,10.7905502 10.9015533,8.40684289 10.9015533,5.46977493 C10.9015533,2.53270697 8.51784597,0.148999644 5.58077801,0.148999644 Z M7.24694078,7.82915872 L7.07059509,7.99942353 C6.97330091,8.0967177 6.81519788,8.0967177 6.7179037,7.99942353 L5.1064689,6.42447404 C5.05174092,6.36974607 5.05782181,6.26029012 5.05782181,6.15083417 L5.05782181,3.06782495 C5.05782181,2.93404545 5.16727776,2.8245895 5.30713814,2.8245895 L5.55645446,2.8245895 C5.69631484,2.8245895 5.80577079,2.93404545 5.80577079,3.06782495 L5.80577079,6.08394442 L7.24694078,7.49470999 C7.34423496,7.58592328 7.34423496,7.73794543 7.24694078,7.82915872 Z" id="形状"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
images/live.jpg


BIN
images/live.png


BIN
images/nav/cart-off.png


BIN
images/nav/cart-on.png


BIN
images/nav/coupon-off.png


BIN
images/nav/coupon-on.png


BIN
images/nav/fl-off.png


BIN
images/nav/fl-on.png


BIN
images/nav/home-off.png


BIN
images/nav/home-on.png


BIN
images/nav/my-off.png


BIN
images/nav/my-on.png


BIN
images/nav/order-off.png


BIN
images/nav/order-on.png


BIN
images/no-order.png


BIN
images/nologin.png


BIN
images/nologin2.png


BIN
images/order-details/icon-address.png


BIN
images/order-details/icon-ddfh.png


BIN
images/order-details/icon-ddfk.png


BIN
images/order-details/icon-ddgb.png


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.