liyongli 3 éve
commit
00fc99a269
100 módosított fájl, 3187 hozzáadás és 0 törlés
  1. 14 0
      .gitignore
  2. 12 0
      README.md
  3. 7 0
      cloudfunctions/callback/config.json
  4. 25 0
      cloudfunctions/callback/index.js
  5. 14 0
      cloudfunctions/callback/package.json
  6. 5 0
      cloudfunctions/echo/config.json
  7. 8 0
      cloudfunctions/echo/index.js
  8. 14 0
      cloudfunctions/echo/package.json
  9. 5 0
      cloudfunctions/login/config.json
  10. 36 0
      cloudfunctions/login/index.js
  11. 14 0
      cloudfunctions/login/package.json
  12. 15 0
      cloudfunctions/openapi/config.json
  13. 86 0
      cloudfunctions/openapi/index.js
  14. 14 0
      cloudfunctions/openapi/package.json
  15. 19 0
      miniprogram/app.js
  16. 52 0
      miniprogram/app.json
  17. 160 0
      miniprogram/app.wxss
  18. 337 0
      miniprogram/components/chatroom/chatroom.js
  19. 4 0
      miniprogram/components/chatroom/chatroom.json
  20. 85 0
      miniprogram/components/chatroom/chatroom.wxml
  21. 161 0
      miniprogram/components/chatroom/chatroom.wxss
  22. BIN
      miniprogram/images/0.png
  23. BIN
      miniprogram/images/1.png
  24. BIN
      miniprogram/images/2.png
  25. BIN
      miniprogram/images/3.png
  26. BIN
      miniprogram/images/4.png
  27. BIN
      miniprogram/images/5.png
  28. BIN
      miniprogram/images/code-cloud-callback-config.png
  29. BIN
      miniprogram/images/code-db-inc-dec.png
  30. BIN
      miniprogram/images/code-db-onAdd.png
  31. BIN
      miniprogram/images/code-db-onQuery.png
  32. BIN
      miniprogram/images/code-db-onRemove.png
  33. BIN
      miniprogram/images/code-func-sum.png
  34. BIN
      miniprogram/images/console-entrance.png
  35. BIN
      miniprogram/images/create-collection.png
  36. BIN
      miniprogram/images/head.jpeg
  37. BIN
      miniprogram/images/tabbar_icon_home_active.png
  38. BIN
      miniprogram/images/tabbar_icon_home_default.png
  39. BIN
      miniprogram/images/tabbar_icon_setting_active.png
  40. BIN
      miniprogram/images/tabbar_icon_setting_default.png
  41. 60 0
      miniprogram/pages/addFunction/addFunction.js
  42. 4 0
      miniprogram/pages/addFunction/addFunction.json
  43. 29 0
      miniprogram/pages/addFunction/addFunction.wxml
  44. 3 0
      miniprogram/pages/addFunction/addFunction.wxss
  45. 66 0
      miniprogram/pages/chooseLib/chooseLib.js
  46. 4 0
      miniprogram/pages/chooseLib/chooseLib.json
  47. 14 0
      miniprogram/pages/chooseLib/chooseLib.wxml
  48. 7 0
      miniprogram/pages/chooseLib/chooseLib.wxss
  49. 193 0
      miniprogram/pages/databaseGuide/databaseGuide.js
  50. 4 0
      miniprogram/pages/databaseGuide/databaseGuide.json
  51. 134 0
      miniprogram/pages/databaseGuide/databaseGuide.wxml
  52. 10 0
      miniprogram/pages/databaseGuide/databaseGuide.wxss
  53. 66 0
      miniprogram/pages/deployFunctions/deployFunctions.js
  54. 4 0
      miniprogram/pages/deployFunctions/deployFunctions.json
  55. 21 0
      miniprogram/pages/deployFunctions/deployFunctions.wxml
  56. 7 0
      miniprogram/pages/deployFunctions/deployFunctions.wxss
  57. 7 0
      miniprogram/pages/detail/detail.js
  58. 3 0
      miniprogram/pages/detail/detail.json
  59. 4 0
      miniprogram/pages/detail/detail.wxml
  60. 1 0
      miniprogram/pages/detail/detail.wxss
  61. 79 0
      miniprogram/pages/home/index.js
  62. 5 0
      miniprogram/pages/home/index.json
  63. 78 0
      miniprogram/pages/home/index.wxml
  64. 99 0
      miniprogram/pages/home/index.wxss
  65. 18 0
      miniprogram/pages/im/im.js
  66. 4 0
      miniprogram/pages/im/im.json
  67. 21 0
      miniprogram/pages/im/im.wxml
  68. 10 0
      miniprogram/pages/im/im.wxss
  69. 84 0
      miniprogram/pages/im/room/room.js
  70. 5 0
      miniprogram/pages/im/room/room.json
  71. 12 0
      miniprogram/pages/im/room/room.wxml
  72. 10 0
      miniprogram/pages/im/room/room.wxss
  73. 124 0
      miniprogram/pages/index/index.js
  74. 3 0
      miniprogram/pages/index/index.json
  75. 77 0
      miniprogram/pages/index/index.wxml
  76. 161 0
      miniprogram/pages/index/index.wxss
  77. BIN
      miniprogram/pages/index/user-unlogin.png
  78. 79 0
      miniprogram/pages/interList/interList.js
  79. 4 0
      miniprogram/pages/interList/interList.json
  80. 12 0
      miniprogram/pages/interList/interList.wxml
  81. 49 0
      miniprogram/pages/interList/interList.wxss
  82. 15 0
      miniprogram/pages/openapi/callback/callback.js
  83. 3 0
      miniprogram/pages/openapi/callback/callback.json
  84. 16 0
      miniprogram/pages/openapi/callback/callback.wxml
  85. 3 0
      miniprogram/pages/openapi/callback/callback.wxss
  86. 59 0
      miniprogram/pages/openapi/cloudid/cloudid.js
  87. 3 0
      miniprogram/pages/openapi/cloudid/cloudid.json
  88. 41 0
      miniprogram/pages/openapi/cloudid/cloudid.wxml
  89. 1 0
      miniprogram/pages/openapi/cloudid/cloudid.wxss
  90. 5 0
      miniprogram/pages/openapi/openapi.js
  91. 3 0
      miniprogram/pages/openapi/openapi.json
  92. 22 0
      miniprogram/pages/openapi/openapi.wxml
  93. 7 0
      miniprogram/pages/openapi/openapi.wxss
  94. 201 0
      miniprogram/pages/openapi/serverapi/serverapi.js
  95. 3 0
      miniprogram/pages/openapi/serverapi/serverapi.json
  96. 63 0
      miniprogram/pages/openapi/serverapi/serverapi.wxml
  97. 7 0
      miniprogram/pages/openapi/serverapi/serverapi.wxss
  98. 65 0
      miniprogram/pages/ruins/index.js
  99. 4 0
      miniprogram/pages/ruins/index.json
  100. 4 0
      miniprogram/pages/ruins/index.wxml

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+# Windows
+[Dd]esktop.ini
+Thumbs.db
+$RECYCLE.BIN/
+
+# macOS
+.DS_Store
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+
+# Node.js
+node_modules/

+ 12 - 0
README.md

@@ -0,0 +1,12 @@
+# 云开发 quickstart
+
+这是云开发的快速启动指引,其中演示了如何上手使用云开发的三大基础能力:
+
+- 数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 文档型数据库
+- 文件存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理
+- 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写业务逻辑代码
+
+## 参考文档
+
+- [云开发文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)
+

+ 7 - 0
cloudfunctions/callback/config.json

@@ -0,0 +1,7 @@
+{
+  "permissions": {
+    "openapi": [
+      "customerServiceMessage.send"
+    ]
+  }
+}

+ 25 - 0
cloudfunctions/callback/index.js

@@ -0,0 +1,25 @@
+const cloud = require('wx-server-sdk')
+
+cloud.init({
+  // API 调用都保持和云函数当前所在环境一致
+  env: cloud.DYNAMIC_CURRENT_ENV
+})
+
+// 云函数入口函数
+exports.main = async (event, context) => {
+  console.log(event)
+
+  const { OPENID } = cloud.getWXContext()
+
+  const result = await cloud.openapi.customerServiceMessage.send({
+    touser: OPENID,
+    msgtype: 'text',
+    text: {
+      content: `收到:${event.Content}`,
+    }
+  })
+
+  console.log(result)
+
+  return result
+}

+ 14 - 0
cloudfunctions/callback/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "callback",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "wx-server-sdk": "~2.4.0"
+  }
+}

+ 5 - 0
cloudfunctions/echo/config.json

@@ -0,0 +1,5 @@
+{
+  "permissions": {
+    "openapi": []
+  }
+}

+ 8 - 0
cloudfunctions/echo/index.js

@@ -0,0 +1,8 @@
+const cloud = require('wx-server-sdk')
+
+exports.main = async (event, context) => {
+  // event.userInfo 是已废弃的保留字段,在此不做展示
+  // 获取 OPENID 等微信上下文请使用 cloud.getWXContext()
+  delete event.userInfo
+  return event
+}

+ 14 - 0
cloudfunctions/echo/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "echo",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "wx-server-sdk": "~2.4.0"
+  }
+}

+ 5 - 0
cloudfunctions/login/config.json

@@ -0,0 +1,5 @@
+{
+  "permissions": {
+    "openapi": []
+  }
+}

+ 36 - 0
cloudfunctions/login/index.js

@@ -0,0 +1,36 @@
+// 云函数模板
+// 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署”
+
+const cloud = require('wx-server-sdk')
+
+// 初始化 cloud
+cloud.init({
+  // API 调用都保持和云函数当前所在环境一致
+  env: cloud.DYNAMIC_CURRENT_ENV
+})
+
+/**
+ * 这个示例将经自动鉴权过的小程序用户 openid 返回给小程序端
+ * 
+ * event 参数包含小程序端调用传入的 data
+ * 
+ */
+exports.main = async (event, context) => {
+  console.log(event)
+  console.log(context)
+
+  // 可执行其他自定义逻辑
+  // console.log 的内容可以在云开发云函数调用日志查看
+
+  // 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)等信息
+  const wxContext = cloud.getWXContext()
+
+  return {
+    event,
+    openid: wxContext.OPENID,
+    appid: wxContext.APPID,
+    unionid: wxContext.UNIONID,
+    env: wxContext.ENV,
+  }
+}
+

+ 14 - 0
cloudfunctions/login/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "login",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "wx-server-sdk": "~2.4.0"
+  }
+}

+ 15 - 0
cloudfunctions/openapi/config.json

@@ -0,0 +1,15 @@
+{
+  "permissions": {
+    "openapi": [
+      "wxacode.get",
+      "subscribeMessage.send",
+      "subscribeMessage.addTemplate",
+      "templateMessage.send",
+      "templateMessage.addTemplate",
+      "templateMessage.deleteTemplate",
+      "templateMessage.getTemplateList",
+      "templateMessage.getTemplateLibraryById",
+      "templateMessage.getTemplateLibraryList"
+    ]
+  }
+}

+ 86 - 0
cloudfunctions/openapi/index.js

@@ -0,0 +1,86 @@
+// 云函数入口文件
+const cloud = require('wx-server-sdk')
+
+cloud.init()
+
+// 云函数入口函数
+exports.main = async (event, context) => {
+  console.log(event)
+  switch (event.action) {
+    case 'requestSubscribeMessage': {
+      return requestSubscribeMessage(event)
+    }
+    case 'sendSubscribeMessage': {
+      return sendSubscribeMessage(event)
+    }
+    case 'getWXACode': {
+      return getWXACode(event)
+    }
+    case 'getOpenData': {
+      return getOpenData(event)
+    }
+    default: {
+      return
+    }
+  }
+}
+
+async function requestSubscribeMessage(event) {
+  // 此处为模板 ID,开发者需要到小程序管理后台 - 订阅消息 - 公共模板库中添加模板,
+  // 然后在我的模板中找到对应模板的 ID,填入此处
+  return '请到管理后台申请模板 ID 然后在此替换' // 如 'N_J6F05_bjhqd6zh2h1LHJ9TAv9IpkCiAJEpSw0PrmQ'
+}
+
+async function sendSubscribeMessage(event) {
+  const { OPENID } = cloud.getWXContext()
+
+  const { templateId } = event
+
+  const sendResult = await cloud.openapi.subscribeMessage.send({
+    touser: OPENID,
+    templateId,
+    miniprogram_state: 'developer',
+    page: 'pages/openapi/openapi',
+    // 此处字段应修改为所申请模板所要求的字段
+    data: {
+      thing1: {
+        value: '咖啡',
+      },
+      time3: {
+        value: '2020-01-01 00:00',
+      },
+    }
+  })
+
+  return sendResult
+}
+
+async function getWXACode(event) {
+  // 此处将获取永久有效的小程序码,并将其保存在云文件存储中,最后返回云文件 ID 给前端使用
+
+  const wxacodeResult = await cloud.openapi.wxacode.get({
+    path: 'pages/openapi/openapi',
+  })
+
+  const fileExtensionMatches = wxacodeResult.contentType.match(/\/([^/]+)/)
+  const fileExtension = (fileExtensionMatches && fileExtensionMatches[1]) || 'jpg'
+
+  const uploadResult = await cloud.uploadFile({
+    // 云文件路径,此处为演示采用一个固定名称
+    cloudPath: `wxacode_default_openapi_page.${fileExtension}`,
+    // 要上传的文件内容可直接传入图片 Buffer
+    fileContent: wxacodeResult.buffer,
+  })
+
+  if (!uploadResult.fileID) {
+    throw new Error(`upload failed with empty fileID and storage server status code ${uploadResult.statusCode}`)
+  }
+
+  return uploadResult.fileID
+}
+
+async function getOpenData(event) {
+  return cloud.getOpenData({
+    list: event.openData.list,
+  })
+}

+ 14 - 0
cloudfunctions/openapi/package.json

@@ -0,0 +1,14 @@
+{
+  "name": "openapi",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "wx-server-sdk": "~2.4.0"
+  }
+}

+ 19 - 0
miniprogram/app.js

@@ -0,0 +1,19 @@
+//app.js
+App({
+  onLaunch: function () {
+    if (!wx.cloud) {
+      console.error('请使用 2.2.3 或以上的基础库以使用云能力')
+    } else {
+      wx.cloud.init({
+        // env 参数说明:
+        //   env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
+        //   此处请填入环境 ID, 环境 ID 可打开云控制台查看
+        //   如不填则使用默认环境(第一个创建的环境)
+        // env: 'my-env-id',
+        traceUser: true,
+      })
+    }
+
+    this.globalData = {}
+  }
+})

+ 52 - 0
miniprogram/app.json

@@ -0,0 +1,52 @@
+{
+  "pages": [
+    "pages/home/index",
+    "pages/index/index",
+    "pages/userConsole/userConsole",
+    "pages/storageConsole/storageConsole",
+    "pages/databaseGuide/databaseGuide",
+    "pages/addFunction/addFunction",
+    "pages/deployFunctions/deployFunctions",
+    "pages/chooseLib/chooseLib",
+    "pages/openapi/openapi",
+    "pages/openapi/serverapi/serverapi",
+    "pages/openapi/callback/callback",
+    "pages/openapi/cloudid/cloudid",
+    "pages/im/im",
+    "pages/im/room/room",
+    "pages/interList/interList",
+    "pages/detail/detail",
+    "pages/ruins/index"
+  ],
+  "window": {
+    "backgroundColor": "#F6F6F6",
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#F6F6F6",
+    "navigationBarTitleText": "寻找红色的记忆",
+    "navigationBarTextStyle": "black"
+  },
+  "tabBar": {
+    "color": "#333",
+    "selectedColor": "#07C160",
+    "backgroundColor": "#fff",
+    "list": [
+      {
+        "pagePath": "pages/home/index",
+        "text": "首页",
+        "iconPath": "/images/tabbar_icon_home_default.png",
+        "selectedIconPath": "/images/tabbar_icon_home_active.png"
+      },
+      {
+        "pagePath": "pages/index/index",
+        "text": "我的",
+        "iconPath": "/images/tabbar_icon_setting_default.png",
+        "selectedIconPath": "/images/tabbar_icon_setting_active.png"
+      }
+    ]
+  },
+  "sitemapLocation": "sitemap.json",
+  "style": "v2",
+  "useExtendedLib": {
+    "weui": true
+  }
+}

+ 160 - 0
miniprogram/app.wxss

@@ -0,0 +1,160 @@
+/**app.wxss**/
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-sizing: border-box;
+} 
+
+button {
+  background: initial;
+}
+
+button:focus{
+  outline: 0;
+}
+
+button::after{
+  border: none;
+}
+
+
+page {
+  background: #f6f6f6;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  font-weight: 400;
+  width: 750rpx;
+  height: 100vh;
+  font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,Helvetica,Segoe UI,Arial,Roboto,PingFang SC,miui,Hiragino Sans GB,Microsoft Yahei,sans-serif;
+}
+
+.userinfo, .uploader, .tunnel {
+  margin-top: 40rpx;
+  height: 140rpx;
+  width: 100%;
+  background: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  border-left: none;
+  border-right: none;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  transition: all 300ms ease;
+}
+
+.userinfo-avatar {
+  width: 100rpx;
+  height: 100rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+  background-size: cover;
+  background-color: white;
+}
+
+.userinfo-avatar:after {
+  border: none;
+}
+
+.userinfo-nickname {
+  font-size: 32rpx;
+  color: #007aff;
+  background-color: white;
+  background-size: cover;
+}
+
+.userinfo-nickname::after {
+  border: none;
+}
+
+.uploader, .tunnel {
+  height: auto;
+  padding: 0 0 0 40rpx;
+  flex-direction: column;
+  align-items: flex-start;
+  box-sizing: border-box;
+}
+
+.uploader-text, .tunnel-text {
+  width: 100%;
+  line-height: 52px;
+  font-size: 34rpx;
+  color: #007aff;
+}
+
+.uploader-container {
+  width: 100%;
+  height: 400rpx;
+  padding: 20rpx 20rpx 20rpx 0;
+  display: flex;
+  align-content: center;
+  justify-content: center;
+  box-sizing: border-box;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.uploader-image {
+  width: 100%;
+  height: 360rpx;
+}
+
+.tunnel {
+  padding: 0 0 0 40rpx;
+}
+
+.tunnel-text {
+  position: relative;
+  color: #222;
+  display: flex;
+  flex-direction: row;
+  align-content: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.tunnel-text:first-child {
+  border-top: none;
+}
+
+.tunnel-switch {
+  position: absolute;
+  right: 20rpx;
+  top: -2rpx;
+}
+
+.disable {
+  color: #888;
+}
+
+.service {
+  position: fixed;
+  right: 40rpx;
+  bottom: 40rpx;
+  width: 140rpx;
+  height: 140rpx;
+  border-radius: 50%;
+  background: linear-gradient(#007aff, #0063ce);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-content: center;
+  justify-content: center;
+  transition: all 300ms ease;
+}
+
+.service-button {
+  position: absolute;
+  top: 40rpx;
+}
+
+.service:active {
+  box-shadow: none;
+}
+
+.request-text {
+  padding: 20rpx 0;
+  font-size: 24rpx;
+  line-height: 36rpx;
+  word-break: break-all;
+}

+ 337 - 0
miniprogram/components/chatroom/chatroom.js

@@ -0,0 +1,337 @@
+const FATAL_REBUILD_TOLERANCE = 10
+const SETDATA_SCROLL_TO_BOTTOM = {
+  scrollTop: 100000,
+  scrollWithAnimation: true,
+}
+
+Component({
+  properties: {
+    envId: String,
+    collection: String,
+    groupId: String,
+    groupName: String,
+    userInfo: Object,
+    onGetUserInfo: {
+      type: Function,
+    },
+    getOpenID: {
+      type: Function,
+    },
+  },
+
+  data: {
+    chats: [],
+    textInputValue: '',
+    openId: '',
+    scrollTop: 0,
+    scrollToMessage: '',
+    hasKeyboard: false,
+  },
+
+  methods: {
+    onGetUserInfo(e) {
+      this.properties.onGetUserInfo(e)
+    },
+
+    getOpenID() { 
+      return this.properties.getOpenID() 
+    },
+
+    mergeCommonCriteria(criteria) {
+      return {
+        groupId: this.data.groupId,
+        ...criteria,
+      }
+    },
+
+    async initRoom() {
+      this.try(async () => {
+        await this.initOpenID()
+
+        const { envId, collection } = this.properties
+        this.db = wx.cloud.database({
+          env: envId,
+        })
+        const db = this.db
+        const _ = db.command
+
+        const { data: initList } = await db.collection(collection).where(this.mergeCommonCriteria()).orderBy('sendTimeTS', 'desc').get()
+
+        console.log('init query chats', initList)
+
+        this.setData({
+          chats: initList.reverse(),
+          scrollTop: 10000,
+        })
+
+        this.initWatch(initList.length ? {
+          sendTimeTS: _.gt(initList[initList.length - 1].sendTimeTS),
+        } : {})
+      }, '初始化失败')
+    },
+
+    async initOpenID() {
+      return this.try(async () => {
+        const openId = await this.getOpenID()
+
+        this.setData({
+          openId,
+        })
+      }, '初始化 openId 失败')
+    },
+
+    async initWatch(criteria) {
+      this.try(() => {
+        const { collection } = this.properties
+        const db = this.db
+        const _ = db.command
+
+        console.warn(`开始监听`, criteria)
+        this.messageListener = db.collection(collection).where(this.mergeCommonCriteria(criteria)).watch({
+          onChange: this.onRealtimeMessageSnapshot.bind(this),
+          onError: e => {
+            if (!this.inited || this.fatalRebuildCount >= FATAL_REBUILD_TOLERANCE) {
+              this.showError(this.inited ? '监听错误,已断开' : '初始化监听失败', e, '重连', () => {
+                this.initWatch(this.data.chats.length ? {
+                  sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
+                } : {})
+              })
+            } else {
+              this.initWatch(this.data.chats.length ? {
+                sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
+              } : {})
+            }
+          },
+        })
+      }, '初始化监听失败')
+    },
+
+    onRealtimeMessageSnapshot(snapshot) {
+      console.warn(`收到消息`, snapshot)
+
+      if (snapshot.type === 'init') {
+        this.setData({
+          chats: [
+            ...this.data.chats,
+            ...[...snapshot.docs].sort((x, y) => x.sendTimeTS - y.sendTimeTS),
+          ],
+        })
+        this.scrollToBottom()
+        this.inited = true
+      } else {
+        let hasNewMessage = false
+        let hasOthersMessage = false
+        const chats = [...this.data.chats]
+        for (const docChange of snapshot.docChanges) {
+          switch (docChange.queueType) {
+            case 'enqueue': {
+              hasOthersMessage = docChange.doc._openid !== this.data.openId
+              const ind = chats.findIndex(chat => chat._id === docChange.doc._id)
+              if (ind > -1) {
+                if (chats[ind].msgType === 'image' && chats[ind].tempFilePath) {
+                  chats.splice(ind, 1, {
+                    ...docChange.doc,
+                    tempFilePath: chats[ind].tempFilePath,
+                  })
+                } else chats.splice(ind, 1, docChange.doc)
+              } else {
+                hasNewMessage = true
+                chats.push(docChange.doc)
+              }
+              break
+            }
+          }
+        }
+        this.setData({
+          chats: chats.sort((x, y) => x.sendTimeTS - y.sendTimeTS),
+        })
+        if (hasOthersMessage || hasNewMessage) {
+          this.scrollToBottom()
+        }
+      }
+    },
+
+    async onConfirmSendText(e) {
+      this.try(async () => {
+        if (!e.detail.value) {
+          return
+        }
+
+        const { collection } = this.properties
+        const db = this.db
+        const _ = db.command
+
+        const doc = {
+          _id: `${Math.random()}_${Date.now()}`,
+          groupId: this.data.groupId,
+          avatar: this.data.userInfo.avatarUrl,
+          nickName: this.data.userInfo.nickName,
+          msgType: 'text',
+          textContent: e.detail.value,
+          sendTime: new Date(),
+          sendTimeTS: Date.now(), // fallback
+        }
+
+        this.setData({
+          textInputValue: '',
+          chats: [
+            ...this.data.chats,
+            {
+              ...doc,
+              _openid: this.data.openId,
+              writeStatus: 'pending',
+            },
+          ],
+        })
+        this.scrollToBottom(true)
+
+        await db.collection(collection).add({
+          data: doc,
+        })
+
+        this.setData({
+          chats: this.data.chats.map(chat => {
+            if (chat._id === doc._id) {
+              return {
+                ...chat,
+                writeStatus: 'written',
+              }
+            } else return chat
+          }),
+        })
+      }, '发送文字失败')
+    },
+
+    async onChooseImage(e) {
+      wx.chooseImage({
+        count: 1,
+        sourceType: ['album', 'camera'],
+        success: async res => {
+          const { envId, collection } = this.properties
+          const doc = {
+            _id: `${Math.random()}_${Date.now()}`,
+            groupId: this.data.groupId,
+            avatar: this.data.userInfo.avatarUrl,
+            nickName: this.data.userInfo.nickName,
+            msgType: 'image',
+            sendTime: new Date(),
+            sendTimeTS: Date.now(), // fallback
+          }
+
+          this.setData({
+            chats: [
+              ...this.data.chats,
+              {
+                ...doc,
+                _openid: this.data.openId,
+                tempFilePath: res.tempFilePaths[0],
+                writeStatus: 0,
+              },
+            ]
+          })
+          this.scrollToBottom(true)
+
+          const uploadTask = wx.cloud.uploadFile({
+            cloudPath: `${this.data.openId}/${Math.random()}_${Date.now()}.${res.tempFilePaths[0].match(/\.(\w+)$/)[1]}`,
+            filePath: res.tempFilePaths[0],
+            config: {
+              env: envId,
+            },
+            success: res => {
+              this.try(async () => {
+                await this.db.collection(collection).add({
+                  data: {
+                    ...doc,
+                    imgFileID: res.fileID,
+                  },
+                })
+              }, '发送图片失败')
+            },
+            fail: e => {
+              this.showError('发送图片失败', e)
+            },
+          })
+
+          uploadTask.onProgressUpdate(({ progress }) => {
+            this.setData({
+              chats: this.data.chats.map(chat => {
+                if (chat._id === doc._id) {
+                  return {
+                    ...chat,
+                    writeStatus: progress,
+                  }
+                } else return chat
+              })
+            })
+          })
+        },
+      })
+    },
+
+    onMessageImageTap(e) {
+      wx.previewImage({
+        urls: [e.target.dataset.fileid],
+      })
+    },
+
+    scrollToBottom(force) {
+      if (force) {
+        console.log('force scroll to bottom')
+        this.setData(SETDATA_SCROLL_TO_BOTTOM)
+        return
+      }
+
+      this.createSelectorQuery().select('.body').boundingClientRect(bodyRect => {
+        this.createSelectorQuery().select(`.body`).scrollOffset(scroll => {
+          if (scroll.scrollTop + bodyRect.height * 3 > scroll.scrollHeight) {
+            console.log('should scroll to bottom')
+            this.setData(SETDATA_SCROLL_TO_BOTTOM)
+          }
+        }).exec()
+      }).exec()
+    },
+
+    async onScrollToUpper() {
+      if (this.db && this.data.chats.length) {
+        const { collection } = this.properties
+        const _ = this.db.command
+        const { data } = await this.db.collection(collection).where(this.mergeCommonCriteria({
+          sendTimeTS: _.lt(this.data.chats[0].sendTimeTS),
+        })).orderBy('sendTimeTS', 'desc').get()
+        this.data.chats.unshift(...data.reverse())
+        this.setData({
+          chats: this.data.chats,
+          scrollToMessage: `item-${data.length}`,
+          scrollWithAnimation: false,
+        })
+      }
+    },
+
+    async try(fn, title) {
+      try {
+        await fn()
+      } catch (e) {
+        this.showError(title, e)
+      }
+    },
+
+    showError(title, content, confirmText, confirmCallback) {
+      console.error(title, content)
+      wx.showModal({
+        title,
+        content: content.toString(),
+        showCancel: confirmText ? true : false,
+        confirmText,
+        success: res => {
+          res.confirm && confirmCallback()
+        },
+      })
+    },
+  },
+
+  ready() {
+    global.chatroom = this
+    this.initRoom()
+    this.fatalRebuildCount = 0
+  },
+})

+ 4 - 0
miniprogram/components/chatroom/chatroom.json

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

+ 85 - 0
miniprogram/components/chatroom/chatroom.wxml

@@ -0,0 +1,85 @@
+<view class="chatroom">
+  <view class="header">
+    <!-- display number of people in the room -->
+    <view class="left"></view>
+    <!-- room name -->
+    <view class="middle">{{groupName}}</view>
+    <!-- reserved -->
+    <view class="right"></view>
+  </view>
+
+  <!-- chats -->
+  <scroll-view 
+    class="body" 
+    scroll-y 
+    scroll-with-animation="{{scrollWithAnimation}}"
+    scroll-top="{{scrollTop}}" 
+    scroll-into-view="{{scrollToMessage}}"
+    bindscrolltoupper="onScrollToUpper"
+  >
+    <view 
+      wx:for="{{chats}}"
+      wx:key="{{item._id}}"
+      id="item-{{index}}"
+      class="message {{openId == item._openid ? 'message__self' : ''}}"
+    >
+      <image 
+        class="avatar"
+        src="{{item.avatar}}"
+        mode="scaleToFill"
+      ></image> 
+      <view class="main">
+        <view class="nickname">{{item.nickName}}</view>
+        <block wx:if="{{item.msgType === 'image'}}">
+          <view class="image-wrapper">
+            <view class="loading" wx:if="{{item.writeStatus > -1}}">{{item.writeStatus}}%</view>
+            <image 
+              src="{{item.tempFilePath || item.imgFileID}}" 
+              data-fileid="{{item.tempFilePath || item.imgFileID}}" 
+              class="image-content" 
+              style="{{item.imgStyle}}"
+              mode="scallToFill" 
+              bindtap="onMessageImageTap"></image>
+          </view>
+        </block>
+        <block wx:else>
+          <view class="text-wrapper">
+            <view class="loading" wx:if="{{item.writeStatus === 'pending'}}">···</view>
+            <view class="text-content">{{item.textContent}}</view>
+          </view>
+        </block>
+      </view>
+    </view>
+  </scroll-view>
+
+  <!-- message sender -->
+  <view class="footer">
+    <view class="message-sender" wx:if="{{userInfo}}">
+      <input 
+        class="text-input"
+        type="text"
+        confirm-type="send"
+        bindconfirm="onConfirmSendText"
+        cursor-spacing="20"
+        value="{{textInputValue}}"
+      ></input>
+
+      <image 
+        src="./photo.png" 
+        class="btn-send-image" 
+        mode="scaleToFill"
+        bindtap="onChooseImage"
+      ></image>
+    </view>
+
+    <view class="message-sender" wx:if="{{!userInfo}}">
+      <button 
+        open-type="getUserInfo" 
+        bindgetuserinfo="onGetUserInfo"
+        class="userinfo"
+      >请先登录后参与聊天</button>
+    </view>
+  </view>
+
+</view>
+

+ 161 - 0
miniprogram/components/chatroom/chatroom.wxss

@@ -0,0 +1,161 @@
+.chatroom {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.chatroom .header {
+  flex-basis: fit-content;
+  display: flex;
+  flex-direction: row;
+  border-bottom: 1px solid #ddd;
+  padding: 20rpx 0 30rpx;
+  font-size: 30rpx;
+  /* background: rgb(34, 187, 47);
+  color: rgba(255, 255, 255, 1) */
+  /* font-family: 'Microsoft YaHei' */
+}
+
+.chatroom .header .left {
+  flex: 1;
+}
+
+.chatroom .header .middle {
+  flex: 2;
+  text-align: center;
+}
+
+.chatroom .header .right {
+  flex: 1;
+}
+
+.chatroom .body {
+  flex: 2;
+  display: flex;
+  flex-direction: column;
+  background: rgb(237,237,237);
+  padding-bottom: 16rpx;
+}
+
+.body .message {
+  display: flex;
+  flex-direction: row;
+  position: relative;
+  margin: 12rpx 0;
+}
+
+.body .message.message__self {
+  flex-direction: row-reverse;
+}
+
+.body .message .avatar {
+  position: relative;
+  top: 5rpx;
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 5rpx;
+  margin: 15rpx;
+}
+
+.body .message .main {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.body .message.message__self .main {
+  align-items: flex-end;
+}
+
+.body .message .nickname {
+  font-size: 24rpx;
+  color: #444;
+}
+
+.body .message .text-content {
+  border: 1px solid transparent;
+  border-radius: 3px;
+  background-color: #fff;
+  margin: 2px 0 0 0;
+  padding: 4px 10px;
+  font-size: 30rpx;
+  display: inline-block;
+}
+
+.body .message.message__self .text-content {
+  background-color: paleturquoise;
+}
+
+.body .message .text-wrapper {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  max-width: 80%;
+}
+
+.body .message.message__self .text-wrapper .loading{
+  font-size: 16rpx;
+  margin-right: 18rpx;
+}
+
+.body .message .image-wrapper {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.body .message .image-content {
+  max-width: 240rpx;
+  max-height: 240rpx;
+}
+
+.body .message.message__self .image-wrapper .loading {
+  font-size: 20rpx;
+  margin-right: 18rpx;
+}
+
+.chatroom .footer {
+  flex-basis: fit-content;
+  display: flex;
+  flex-direction: row;
+  border-top: 1px solid #ddd;
+  font-size: 10rpx;
+  padding: 20rpx 30rpx;
+  background: rgb(246,246,246);
+}
+
+.chatroom .footer .message-sender {
+  flex: 1;
+  display: flex;
+  flex-direction: row;
+}
+
+.message-sender .text-input {
+  flex: 1;
+  font-size: 16px;
+  border: 1px solid transparent;
+  border-radius: 5px;
+  padding: 3px 6px;
+  margin: 0 10px 0 5px;
+  background: #fff;
+}
+
+.message-sender .btn-send-image {
+  width: 50rpx;
+  height: 50rpx;
+  align-self: center;
+}
+
+button {
+  font-size: 30rpx;
+}
+
+button.userinfo {
+  background: darkturquoise;
+  color: aliceblue;
+  padding: 0 100rpx;
+  border: 1px solid #ddd;
+  border-radius: 20px;
+}

BIN
miniprogram/images/0.png


BIN
miniprogram/images/1.png


BIN
miniprogram/images/2.png


BIN
miniprogram/images/3.png


BIN
miniprogram/images/4.png


BIN
miniprogram/images/5.png


BIN
miniprogram/images/code-cloud-callback-config.png


BIN
miniprogram/images/code-db-inc-dec.png


BIN
miniprogram/images/code-db-onAdd.png


BIN
miniprogram/images/code-db-onQuery.png


BIN
miniprogram/images/code-db-onRemove.png


BIN
miniprogram/images/code-func-sum.png


BIN
miniprogram/images/console-entrance.png


BIN
miniprogram/images/create-collection.png


BIN
miniprogram/images/head.jpeg


BIN
miniprogram/images/tabbar_icon_home_active.png


BIN
miniprogram/images/tabbar_icon_home_default.png


BIN
miniprogram/images/tabbar_icon_setting_active.png


BIN
miniprogram/images/tabbar_icon_setting_default.png


+ 60 - 0
miniprogram/pages/addFunction/addFunction.js

@@ -0,0 +1,60 @@
+// pages/addFunction/addFunction.js
+
+const code = `// 云函数入口函数
+exports.main = async (event, context) => {
+  console.log(event)
+  console.log(context)
+  return {
+    sum: event.a + event.b
+  }
+}`
+
+Page({
+
+  data: {
+    result: '',
+    canIUseClipboard: wx.canIUse('setClipboardData'),
+  },
+
+  onLoad: function (options) {
+
+  },
+
+  copyCode: function() {
+    wx.setClipboardData({
+      data: code,
+      success: function () {
+        wx.showToast({
+          title: '复制成功',
+        })
+      }
+    })
+  },
+
+  testFunction() {
+    wx.cloud.callFunction({
+      name: 'sum',
+      data: {
+        a: 1,
+        b: 2
+      },
+      success: res => {
+        wx.showToast({
+          title: '调用成功',
+        })
+        this.setData({
+          result: JSON.stringify(res.result)
+        })
+      },
+      fail: err => {
+        wx.showToast({
+          icon: 'none',
+          title: '调用失败',
+        })
+        console.error('[云函数] [sum] 调用失败:', err)
+      }
+    })
+  },
+
+})
+

+ 4 - 0
miniprogram/pages/addFunction/addFunction.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "云函数指引",
+  "usingComponents": {}
+}

+ 29 - 0
miniprogram/pages/addFunction/addFunction.wxml

@@ -0,0 +1,29 @@
+<!--pages/addFunction/addFunction.wxml-->
+<view class="container">
+
+  <view class="list">
+    <view class="list-item" bindtap="testFunction">
+      <text>测试云函数</text>
+    </view>
+    <view class="list-item">
+      <text class="request-text">期望输出:{"sum":3}</text>
+    </view>
+    <view class="list-item" wx:if="{{result}}">
+      <text class="request-text">调用结果:{{result}}</text>
+    </view>
+  </view>
+
+  <view class="guide">
+    <text class="headline">新增云函数</text>
+    <text class="p">1. 在云函数根目录 cloudfunctions 上右键选择新建云函数,命名为 sum</text>
+    <text class="p">2. 在创建的 cloudfunctions/sum/index.js 文件中添加如下代码</text>
+    <image class="image1" src="../../images/code-func-sum.png" mode="aspectFit"></image>
+    <button class="copyBtn" wx:if="{{canIUseClipboard}}" bindtap="copyCode">复制代码</button>
+    <text class="p">3. 在 cloudfunctions/sum 目录上右键上传并部署</text>
+    <text class="p">4. 点击测试云函数测试</text>
+    <text class="p">5. 打开云开发云函数管理页,选择 sum 云函数</text>
+    <text class="p">6. 查看 sum 的调用日志</text>
+    <text class="p">进阶:可在云函数中使用 wx-server-sdk 操作数据库,文件存储和调用其他云函数,详见文档</text>
+  </view>
+
+</view>

+ 3 - 0
miniprogram/pages/addFunction/addFunction.wxss

@@ -0,0 +1,3 @@
+/* pages/addFunction/addFunction.wxss */
+
+@import "../../style/guide.wxss";

+ 66 - 0
miniprogram/pages/chooseLib/chooseLib.js

@@ -0,0 +1,66 @@
+// pages/chooseLib/chooseLib.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+})

+ 4 - 0
miniprogram/pages/chooseLib/chooseLib.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "选择基础库",
+  "usingComponents": {}
+}

+ 14 - 0
miniprogram/pages/chooseLib/chooseLib.wxml

@@ -0,0 +1,14 @@
+<!--pages/chooseLib/chooseLib.wxml-->
+<view class="container">
+
+  <view class="list">
+    <view class="list-item">
+      <text class="black">初始化失败</text>
+    </view>
+    <view class="list-item">
+      <text class="request-text">请使用 2.2.3 或以上的基础库以使用云能力</text>
+    </view>
+  </view>
+
+</view>
+

+ 7 - 0
miniprogram/pages/chooseLib/chooseLib.wxss

@@ -0,0 +1,7 @@
+/* pages/chooseLib/chooseLib.wxss */
+
+@import "../../style/guide.wxss";
+
+.black {
+  color: black;
+}

+ 193 - 0
miniprogram/pages/databaseGuide/databaseGuide.js

@@ -0,0 +1,193 @@
+// pages/databaseGuide/databaseGuide.js
+
+const app = getApp()
+
+Page({
+
+  data: {
+    step: 1,
+    counterId: '',
+    openid: '',
+    count: null,
+    queryResult: '',
+  },
+
+  onLoad: function (options) {
+    if (app.globalData.openid) {
+      this.setData({
+        openid: app.globalData.openid
+      })
+    }
+  },
+
+  onAdd: function () {
+    // const db = wx.cloud.database()
+    // db.collection('counters').add({
+    //   data: {
+    //     count: 1
+    //   },
+    //   success: res => {
+    //     // 在返回结果中会包含新创建的记录的 _id
+    //     this.setData({
+    //       counterId: res._id,
+    //       count: 1
+    //     })
+    //     wx.showToast({
+    //       title: '新增记录成功',
+    //     })
+    //     console.log('[数据库] [新增记录] 成功,记录 _id: ', res._id)
+    //   },
+    //   fail: err => {
+    //     wx.showToast({
+    //       icon: 'none',
+    //       title: '新增记录失败'
+    //     })
+    //     console.error('[数据库] [新增记录] 失败:', err)
+    //   }
+    // })
+  },
+
+  onQuery: function() {
+    // const db = wx.cloud.database()
+    // // 查询当前用户所有的 counters
+    // db.collection('counters').where({
+    //   _openid: this.data.openid
+    // }).get({
+    //   success: res => {
+    //     this.setData({
+    //       queryResult: JSON.stringify(res.data, null, 2)
+    //     })
+    //     console.log('[数据库] [查询记录] 成功: ', res)
+    //   },
+    //   fail: err => {
+    //     wx.showToast({
+    //       icon: 'none',
+    //       title: '查询记录失败'
+    //     })
+    //     console.error('[数据库] [查询记录] 失败:', err)
+    //   }
+    // })
+  },
+
+  onCounterInc: function() {
+    // const db = wx.cloud.database()
+    // const newCount = this.data.count + 1
+    // db.collection('counters').doc(this.data.counterId).update({
+    //   data: {
+    //     count: newCount
+    //   },
+    //   success: res => {
+    //     this.setData({
+    //       count: newCount
+    //     })
+    //   },
+    //   fail: err => {
+    //     icon: 'none',
+    //     console.error('[数据库] [更新记录] 失败:', err)
+    //   }
+    // })
+  },
+
+  onCounterDec: function() {
+    // const db = wx.cloud.database()
+    // const newCount = this.data.count - 1
+    // db.collection('counters').doc(this.data.counterId).update({
+    //   data: {
+    //     count: newCount
+    //   },
+    //   success: res => {
+    //     this.setData({
+    //       count: newCount
+    //     })
+    //   },
+    //   fail: err => {
+    //     icon: 'none',
+    //     console.error('[数据库] [更新记录] 失败:', err)
+    //   }
+    // })
+  },
+
+  onRemove: function() {
+    // if (this.data.counterId) {
+    //   const db = wx.cloud.database()
+    //   db.collection('counters').doc(this.data.counterId).remove({
+    //     success: res => {
+    //       wx.showToast({
+    //         title: '删除成功',
+    //       })
+    //       this.setData({
+    //         counterId: '',
+    //         count: null,
+    //       })
+    //     },
+    //     fail: err => {
+    //       wx.showToast({
+    //         icon: 'none',
+    //         title: '删除失败',
+    //       })
+    //       console.error('[数据库] [删除记录] 失败:', err)
+    //     }
+    //   })
+    // } else {
+    //   wx.showToast({
+    //     title: '无记录可删,请见创建一个记录',
+    //   })
+    // }
+  },
+
+  nextStep: function () {
+    // 在第一步,需检查是否有 openid,如无需获取
+    if (this.data.step === 1 && !this.data.openid) {
+      wx.cloud.callFunction({
+        name: 'login',
+        data: {},
+        success: res => {
+          app.globalData.openid = res.result.openid
+          this.setData({
+            step: 2,
+            openid: res.result.openid
+          })
+        },
+        fail: err => {
+          wx.showToast({
+            icon: 'none',
+            title: '获取 openid 失败,请检查是否有部署 login 云函数',
+          })
+          console.log('[云函数] [login] 获取 openid 失败,请检查是否有部署云函数,错误信息:', err)
+        }
+      })
+    } else {
+      const callback = this.data.step !== 6 ? function() {} : function() {
+        console.group('数据库文档')
+        console.log('https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database.html')
+        console.groupEnd()
+      }
+
+      this.setData({
+        step: this.data.step + 1
+      }, callback)
+    }
+  },
+
+  prevStep: function () {
+    this.setData({
+      step: this.data.step - 1
+    })
+  },
+
+  goHome: function() {
+    const pages = getCurrentPages()
+    if (pages.length === 2) {
+      wx.navigateBack()
+    } else if (pages.length === 1) {
+      wx.redirectTo({
+        url: '../index/index',
+      })
+    } else {
+      wx.reLaunch({
+        url: '../index/index',
+      })
+    }
+  }
+
+})

+ 4 - 0
miniprogram/pages/databaseGuide/databaseGuide.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "数据库指引",
+  "usingComponents": {}
+}

+ 134 - 0
miniprogram/pages/databaseGuide/databaseGuide.wxml

@@ -0,0 +1,134 @@
+<!--pages/databaseGuide/databaseGuide.wxml-->
+<view class="container">
+
+  <!-- 导航 -->
+  <view class="list">
+    <view class="list-item">
+      <text class="request-text">数据库指引</text>
+    </view>
+    <view class="list-item">
+      <text class="request-text" wx:for="{{7}}" style="color: {{step === index + 1 ? 'red': 'black'}}">{{index + 1}}</text>
+    </view>
+    <view class="list-item" wx:if="{{openid}}">
+      <text class="request-text">openid:{{openid}}</text>
+    </view>
+    <view class="list-item" wx:if="{{counterId}}">
+      <text class="request-text">当前记录 ID:{{counterId}}</text>
+    </view>
+  </view>
+
+  <!-- 快速操作数据库指引 -->
+
+  <!-- 简介 -->
+  <view class="guide" wx:if="{{step === 1}}">
+    <text class="headline">示例介绍</text>
+    <text class="p">1. 以计数器为例,在此演示如何操作数据库</text>
+    <text class="p">2. 数据库操作大多需要用户 openid,需先配置好 login 云函数,如已配置好,点击下一步,获取用户 openid 并开始我们的指引</text>
+    <div class="nav">
+      <button class="next" size="mini" type="default" bindtap="nextStep">下一步</button>
+    </div>
+  </view>
+
+  <!-- 新建集合 -->
+  <view class="guide" wx:if="{{step === 2}}">
+    <text class="headline">新建集合</text>
+    <text class="p">1. 打开云开发控制台,进入到数据库管理页</text>
+    <image class="image1" src="../../images/console-entrance.png" mode="aspectFit"></image>
+    <text class="p">2. 选择添加集合,集合名为 counters</text>
+    <image class="flat-image" src="../../images/create-collection.png" mode="aspectFit"></image>
+    <text class="p">3. 可以看到 counters 集合出现在左侧集合列表中</text>
+    <text class="p">注:集合必须在云开发控制台中创建</text>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="nextStep">下一步</button>
+    </div>
+  </view>
+
+  <!-- 新增记录 -->
+  <view class="guide" wx:if="{{step === 3}}">
+    <text class="headline">新增记录</text>
+    <text class="p">1. 打开 pages/databaseGuide/databaseGuide.js 文件,定位到 onAdd 方法</text>
+    <text class="p">2. 把注释掉的代码解除注释</text>
+    <image class="code-image" src="../../images/code-db-onAdd.png" mode="aspectFit"></image>
+    <text class="p">3. onAdd 方法会往 counters 集合新增一个记录,新增如下格式的一个 JSON 记录</text>
+    <text class="code">
+    {
+      "_id": "数据库自动生成记录 ID 字段",
+      "_openid": "数据库自动插入记录创建者的 openid",
+      "count": 1
+    }
+    </text>
+    <text class="p">4. 点击按钮</text>
+    <button size="mini" type="default" bindtap="onAdd">新增记录</button>
+    <text class="p" wx:if="{{counterId}}">新增的记录 _id 为:{{counterId}}</text>
+    <text class="p">5. 在云开发 -> 数据库 -> counters 集合中可以看到新增的记录</text>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="nextStep" wx:if="{{counterId}}">下一步</button>
+    </div>
+  </view>
+
+  <!-- 查询记录 -->
+  <view class="guide" wx:if="{{step === 4}}">
+    <text class="headline">查询记录</text>
+    <text class="p">1. 打开 pages/databaseGuide/databaseGuide.js 文件,定位到 onQuery 方法</text>
+    <text class="p">2. 把注释掉的代码解除注释,onQuery 方法会在下方按钮被点击时触发</text>
+    <image class="code-image" src="../../images/code-db-onQuery.png" mode="aspectFit"></image>
+    <text class="p">3. 点击按钮</text>
+    <button size="mini" type="default" bindtap="onQuery">查询记录</button>
+    <text class="code" wx:if="{{queryResult}}">{{queryResult}}</text>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="nextStep">下一步</button>
+    </div>
+  </view>
+
+  <!-- 更新记录 -->
+  <view class="guide" wx:if="{{step === 5}}">
+    <text class="headline">更新记录</text>
+    <text class="p">1. 打开 pages/databaseGuide/databaseGuide.js 文件,定位到 onCounterInc 和 onCounterDec 方法</text>
+    <text class="p">2. 把注释掉的代码解除注释</text>
+    <image class="code-image" src="../../images/code-db-inc-dec.png" mode="aspectFit"></image>
+    <text class="p">3. 点击下方按钮更新计数器</text>
+    <div class="counter">
+      <button class="minus" size="mini" type="default" bindtap="onCounterDec">-</button>
+      <text class="p">{{count}}</text>
+      <button class="plus" size="mini" type="default" bindtap="onCounterInc">+</button>
+    </div>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="nextStep">下一步</button>
+    </div>
+  </view>
+
+  <!-- 删除记录 -->
+  <view class="guide" wx:if="{{step === 6}}">
+    <text class="headline">删除记录</text>
+    <text class="p">1. 打开 pages/databaseGuide/databaseGuide.js 文件,定位到 onRemove 方法</text>
+    <text class="p">2. 把注释掉的代码解除注释</text>
+    <image class="code-image" src="../../images/code-db-onRemove.png" mode="aspectFit"></image>
+    <text class="p">3. 点击下方按钮删除计数器</text>
+    <button size="mini" type="default" bindtap="onRemove">删除记录</button>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep" wx:if="{{counterId}}">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="nextStep">下一步</button>
+    </div>
+  </view>
+
+  <!-- 结语 -->
+  <view class="guide" wx:if="{{step === 7}}">
+    <text class="headline">完成指引 !</text>
+    <text class="p">恭喜你,至此已完成数据库操作入门基础,可以点击调试器中的链接,查看详尽的数据库文档</text>
+
+    <div class="nav">
+      <button class="prev" size="mini" type="default" bindtap="prevStep">上一步</button>
+      <button class="next" size="mini" type="default" bindtap="goHome">回到首页</button>
+    </div>
+  </view>
+
+</view>

+ 10 - 0
miniprogram/pages/databaseGuide/databaseGuide.wxss

@@ -0,0 +1,10 @@
+/* pages/databaseGuide/databaseGuide.wxss */
+
+@import "../../style/guide.wxss";
+
+.guide .counter {
+  margin-top: 50rpx;
+  display: flex;
+  flex-direction: row;
+  align-content: space-between;
+}

+ 66 - 0
miniprogram/pages/deployFunctions/deployFunctions.js

@@ -0,0 +1,66 @@
+// pages/deployFunctions/deployFunctions.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+})

+ 4 - 0
miniprogram/pages/deployFunctions/deployFunctions.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "部署云函数",
+  "usingComponents": {}
+}

+ 21 - 0
miniprogram/pages/deployFunctions/deployFunctions.wxml

@@ -0,0 +1,21 @@
+<!--pages/deployFunctions/deployFunctions.wxml-->
+<view class="container">
+
+  <view class="list">
+    <view class="list-item">
+      <text class="black">调用失败</text>
+    </view>
+    <view class="list-item">
+      <text class="request-text">请检查 login 云函数是否已部署</text>
+    </view>
+  </view>
+
+  <view class="guide">
+    <text class="headline">部署 login 云函数</text>
+    <text class="p">1. 确保已通过工具栏云开发入口开通云开发</text>
+    <text class="p">2. 在 cloudfunctions/login 目录上右键上传并部署</text>
+    <text class="p">3. 回到首页,重新点击获取 openid</text>
+  </view>
+
+</view>
+

+ 7 - 0
miniprogram/pages/deployFunctions/deployFunctions.wxss

@@ -0,0 +1,7 @@
+/* pages/deployFunctions/deployFunctions.wxss */
+
+@import "../../style/guide.wxss";
+
+.black {
+  color: black;
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 7 - 0
miniprogram/pages/detail/detail.js


+ 3 - 0
miniprogram/pages/detail/detail.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 4 - 0
miniprogram/pages/detail/detail.wxml

@@ -0,0 +1,4 @@
+<!--miniprogram/pages/detail/detail.wxml-->
+<view>
+  <rich-text nodes="{{detail}}"></rich-text>
+</view>

+ 1 - 0
miniprogram/pages/detail/detail.wxss

@@ -0,0 +1 @@
+/* miniprogram/pages/detail/detail.wxss */

+ 79 - 0
miniprogram/pages/home/index.js

@@ -0,0 +1,79 @@
+// miniprogram/pages/home/index.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  },
+
+  toDetail:function(e){
+    console.log(e.currentTarget.dataset);
+    let title = e.currentTarget.dataset.title, id = e.currentTarget.dataset.id;
+    wx.navigateTo({
+      url: '/pages/detail/detail?title=' +title + "&id=" + id,
+    })
+  },
+  toRuins:function(e){
+    wx.navigateTo({
+      url: '/pages/ruins/index',
+    })
+  }
+
+})

+ 5 - 0
miniprogram/pages/home/index.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "mp-cell": "weui-miniprogram/cell/cell"
+  }
+}

+ 78 - 0
miniprogram/pages/home/index.wxml

@@ -0,0 +1,78 @@
+<!--miniprogram/pages/home/index.wxml-->
+<view class="home">
+  <image src="../../images/head.jpeg"></image>
+  <view class="title">
+    畅行中国•庆祝建党100周年“寻找红色记忆”主题——“红色文物会说话、红色遗址会发声”融媒传播发布仪式
+  </view>
+  <view class="subTitle">
+    2021年5月15日至16日
+  </view>
+
+  <view class="icon_container">
+    <view class="icon_item_cell">
+      <view class="icon_content" data-title="活动简介" data-id="1" bindtap="toDetail">
+        <image class="img" src="../../images/0.png"></image>
+        <view class="icon_title">
+          活动简介
+        </view>
+      </view>
+    </view>
+    <view class="icon_item_cell">
+      <view class="icon_content" data-title="日程会务" data-id="1" bindtap="toDetail">
+        <image class="img" src="../../images/1.png"></image>
+        <view class="icon_title">
+          日程会务
+        </view>
+      </view>
+    </view>
+    <view class="icon_item_cell">
+      <view class="icon_content" data-title="疫情防控" data-id="1" bindtap="toDetail">
+        <image class="img" src="../../images/2.png"></image>
+        <view class="icon_title">
+          疫情防控
+        </view>
+      </view>
+    </view>
+    <view class="icon_item_cell">
+      <view class="icon_content">
+        <image class="img" src="../../images/3.png"></image>
+        <view class="icon_title">
+          精彩瞬间
+        </view>
+      </view>
+    </view>
+    <view class="icon_item_cell">
+      <view class="icon_content">
+        <image class="img" src="../../images/4.png"></image>
+        <view class="icon_title">
+          红色文物会说话
+        </view>
+      </view>
+    </view>
+    <view class="icon_item_cell">
+      <view class="icon_content" bindtap="toRuins">
+        <image class="img" src="../../images/5.png"></image>
+        <view class="icon_title">
+          红色遗址会发声
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <mp-cell link hover value="新闻动态" footer="查看更多" url="/pages/interList/interList?type=news">
+  </mp-cell>
+  <view class="newList" data-id="1" data-title="新华云直播:“红色文物会说话、红色遗址会发声”融媒传播发布仪式暨“党性教育基地”揭牌仪式" bindtap="toDetail">
+    <image class="newsImg" src="https://xzhsjy.hdd.jxrtv.com/imgs/hddlogo.png"></image>
+    <view class="newsTitle">
+      <text class="top">新华云直播:“红色文物会说话、红色遗址会发声”融媒传播发布仪式暨“党性教育基地”揭牌仪式</text>
+      <text class="subTitle">发布日期 2021-05-15 11:25:00</text>
+    </view>
+  </view>
+  <view class="br"></view>
+  <mp-cell link hover value="视频动态" footer="查看更多" url="/pages/interList/interList?type=vadeo">
+  </mp-cell>
+  <view class="videoList">
+    <view class="title">红色文物会说话(片头)</view>
+    <video class="videoEle" src="https://xzhsjy.hdd.jxrtv.com/jxrtv.mp4"></video>
+  </view>
+</view>

+ 99 - 0
miniprogram/pages/home/index.wxss

@@ -0,0 +1,99 @@
+/* miniprogram/pages/home/index.wxss */
+.home {
+  padding: 10rpx;
+}
+
+.br {
+  height: 1em;
+}
+
+.home image {
+  width: 100%;
+  height: 250rpx;
+  border-radius: 22.5rpx;
+}
+
+.home .title {
+  font-size: 16px;
+}
+
+.home .subTitle {
+  font-size: 12px;
+  margin: 10rpx 0;
+  color: #999;
+}
+
+.icon_container {
+  width: 750rpx;
+  margin-left: -10rpx;
+  font-size: 12px;
+  color: #666;
+}
+
+.icon_container .icon_item_cell {
+  box-sizing: border-box;
+  display: inline-block;
+  width: 250rpx;
+  padding: 5rpx;
+}
+
+.icon_container .icon_content {
+  text-align: center;
+  border-radius: 10rpx;
+  padding: 46rpx 0 20rpx 0;
+  box-shadow: 3rpx 3rpx 20rpx rgba(0, 0, 0, .1);
+}
+
+.icon_container .icon_title {
+  padding: 15rpx 0;
+}
+
+.icon_container .img {
+  width: 4em;
+  height: 4em;
+}
+
+.videoList {
+  margin-bottom: 20rpx;
+}
+
+.videoList .title {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.newList {
+  width: 100%;
+  font-size: 0;
+  height: 240rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  border-radius: 22.5rpx;
+  box-shadow: 3rpx 3rpx 20rpx rgba(0, 0, 0, .1);
+}
+
+.newList .newsImg {
+  width: 240rpx;
+  height: 240rpx;
+}
+
+.newList .newsTitle {
+  width: 100%;
+  height: 240rpx;
+  font-size: 14px;
+  margin-top: -240rpx;
+  padding: 20rpx 10rpx 20rpx 250rpx;
+  box-sizing: border-box;
+}
+
+.newList .top {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  overflow: hidden;
+}
+
+.videoEle {
+  width: 100%;
+}

+ 18 - 0
miniprogram/pages/im/im.js

@@ -0,0 +1,18 @@
+const app = getApp()
+
+Page({
+  data: {
+  },
+
+  onLoad: function (options) {
+    if (app.globalData.openid) {
+      this.setData({
+        openid: app.globalData.openid
+      })
+    }
+
+    console.group('数据库"实时数据推送"文档')
+    console.log('https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/realtime.html')
+    console.groupEnd()
+  },
+})

+ 4 - 0
miniprogram/pages/im/im.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "数据库指引",
+  "usingComponents": {}
+}

+ 21 - 0
miniprogram/pages/im/im.wxml

@@ -0,0 +1,21 @@
+<view class="container">
+  <!-- 简介 -->
+  <view class="guide">
+    <text class="headline">即时通信 demo 介绍</text>
+    <text class="p">本 demo 以《聊天室》为例,在此演示如何使用数据库《实时数据推送》能力</text>
+    <text class="p">1. 确保正在使用基础库 2.8.1</text>
+    <text class="p">2. 打开云开发控制台,进入到数据库管理页</text>
+    <text class="p">3. 选择添加集合,集合名设置为 chatroom</text>
+    <text class="p">4. 将集合设置为所有用户可读、仅创建者可写</text>
+    <text class="p">5. 确保IDE增强编译已开启(如无,到工具详情页中开启)</text>
+    <text class="p">6. 点击下方按钮进入聊天室!</text>
+    <text class="p">注1:可使用(菜单栏-工具)多账号调试的功能在工具中模拟多账号登录调试</text>
+    <text class="p">注2:实时数据推送的文档链接已在调试器中打印,可打开查看</text>
+  </view>
+
+  <view class="uploader">
+    <navigator url="./room/room" open-type="navigate" class="uploader-text">
+      <text>进入聊天室</text>
+    </navigator>
+  </view>
+</view>

+ 10 - 0
miniprogram/pages/im/im.wxss

@@ -0,0 +1,10 @@
+/* pages/databaseGuide/databaseGuide.wxss */
+
+@import "../../style/guide.wxss";
+
+.guide .counter {
+  margin-top: 50rpx;
+  display: flex;
+  flex-direction: row;
+  align-content: space-between;
+}

+ 84 - 0
miniprogram/pages/im/room/room.js

@@ -0,0 +1,84 @@
+const app = getApp()
+
+Page({
+  data: {
+    avatarUrl: './user-unlogin.png',
+    userInfo: null,
+    logged: false,
+    takeSession: false,
+    requestResult: '',
+    // chatRoomEnvId: 'release-f8415a',
+    chatRoomCollection: 'chatroom',
+    chatRoomGroupId: 'demo',
+    chatRoomGroupName: '聊天室',
+
+    // functions for used in chatroom components
+    onGetUserInfo: null,
+    getOpenID: null,
+  },
+
+  onLoad: function() {
+    // 获取用户信息
+    wx.getSetting({
+      success: res => {
+        if (res.authSetting['scope.userInfo']) {
+          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
+          wx.getUserInfo({
+            success: res => {
+              this.setData({
+                avatarUrl: res.userInfo.avatarUrl,
+                userInfo: res.userInfo
+              })
+            }
+          })
+        }
+      }
+    })
+
+    this.setData({
+      onGetUserInfo: this.onGetUserInfo,
+      getOpenID: this.getOpenID,
+    })
+
+    wx.getSystemInfo({
+      success: res => {
+        console.log('system info', res)
+        if (res.safeArea) {
+          const { top, bottom } = res.safeArea
+          this.setData({
+            containerStyle: `padding-top: ${(/ios/i.test(res.system) ? 10 : 20) + top}px; padding-bottom: ${20 + res.windowHeight - bottom}px`,
+          })
+        }
+      },
+    })
+  },
+
+  getOpenID: async function() {
+    if (this.openid) {
+      return this.openid
+    }
+
+    const { result } = await wx.cloud.callFunction({
+      name: 'login',
+    })
+
+    return result.openid
+  },
+
+  onGetUserInfo: function(e) {
+    if (!this.logged && e.detail.userInfo) {
+      this.setData({
+        logged: true,
+        avatarUrl: e.detail.userInfo.avatarUrl,
+        userInfo: e.detail.userInfo
+      })
+    }
+  },
+
+  onShareAppMessage() {
+    return {
+      title: '即时通信 Demo',
+      path: '/pages/im/room/room',
+    }
+  },
+})

+ 5 - 0
miniprogram/pages/im/room/room.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "chatroom": "/components/chatroom/chatroom"
+  }
+}

+ 12 - 0
miniprogram/pages/im/room/room.wxml

@@ -0,0 +1,12 @@
+<view class="container" style="{{containerStyle}}">
+  <chatroom
+    style="width: 100%; height: 100%"
+    envId="{{chatRoomEnvId}}"
+    collection="{{chatRoomCollection}}"
+    groupId="{{chatRoomGroupId}}"
+    groupName="{{chatRoomGroupName}}"
+    userInfo="{{userInfo}}"
+    onGetUserInfo="{{onGetUserInfo}}"
+    getOpenID="{{getOpenID}}"
+  ></chatroom>
+</view>

+ 10 - 0
miniprogram/pages/im/room/room.wxss

@@ -0,0 +1,10 @@
+.container {
+  height: 100%;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding-top: 80rpx;
+  padding-bottom: 30rpx;
+}

+ 124 - 0
miniprogram/pages/index/index.js

@@ -0,0 +1,124 @@
+//index.js
+const app = getApp()
+
+Page({
+  data: {
+    avatarUrl: './user-unlogin.png',
+    userInfo: {},
+    hasUserInfo: false,
+    logged: false,
+    takeSession: false,
+    requestResult: '',
+    canIUseGetUserProfile: false,
+    canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') // 如需尝试获取用户信息可改为false
+  },
+
+  onLoad: function() {
+    if (!wx.cloud) {
+      wx.redirectTo({
+        url: '../chooseLib/chooseLib',
+      })
+      return
+    }
+    if (wx.getUserProfile) {
+      this.setData({
+        canIUseGetUserProfile: true,
+      })
+    }
+  },
+
+  getUserProfile() {
+    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
+    wx.getUserProfile({
+      desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+      success: (res) => {
+        this.setData({
+          avatarUrl: res.userInfo.avatarUrl,
+          userInfo: res.userInfo,
+          hasUserInfo: true,
+        })
+      }
+    })
+  },
+
+  onGetUserInfo: function(e) {
+    if (!this.data.logged && e.detail.userInfo) {
+      this.setData({
+        logged: true,
+        avatarUrl: e.detail.userInfo.avatarUrl,
+        userInfo: e.detail.userInfo,
+        hasUserInfo: true,
+      })
+    }
+  },
+
+  onGetOpenid: function() {
+    // 调用云函数
+    wx.cloud.callFunction({
+      name: 'login',
+      data: {},
+      success: res => {
+        console.log('[云函数] [login] user openid: ', res.result.openid)
+        app.globalData.openid = res.result.openid
+        wx.navigateTo({
+          url: '../userConsole/userConsole',
+        })
+      },
+      fail: err => {
+        console.error('[云函数] [login] 调用失败', err)
+        wx.navigateTo({
+          url: '../deployFunctions/deployFunctions',
+        })
+      }
+    })
+  },
+
+  // 上传图片
+  doUpload: function () {
+    // 选择图片
+    wx.chooseImage({
+      count: 1,
+      sizeType: ['compressed'],
+      sourceType: ['album', 'camera'],
+      success: function (res) {
+        wx.showLoading({
+          title: '上传中',
+        })
+
+        const filePath = res.tempFilePaths[0]
+        
+        // 上传图片
+        const cloudPath = `my-image${filePath.match(/\.[^.]+?$/)[0]}`
+        wx.cloud.uploadFile({
+          cloudPath,
+          filePath,
+          success: res => {
+            console.log('[上传文件] 成功:', res)
+
+            app.globalData.fileID = res.fileID
+            app.globalData.cloudPath = cloudPath
+            app.globalData.imagePath = filePath
+            
+            wx.navigateTo({
+              url: '../storageConsole/storageConsole'
+            })
+          },
+          fail: e => {
+            console.error('[上传文件] 失败:', e)
+            wx.showToast({
+              icon: 'none',
+              title: '上传失败',
+            })
+          },
+          complete: () => {
+            wx.hideLoading()
+          }
+        })
+      },
+      fail: e => {
+        console.error(e)
+      }
+    })
+  },
+
+})

+ 3 - 0
miniprogram/pages/index/index.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 77 - 0
miniprogram/pages/index/index.wxml

@@ -0,0 +1,77 @@
+<!--index.wxml-->
+<view class="container">
+
+  <!-- 用户 openid -->
+  <view class="userinfo">
+    <block wx:if="{{canIUseOpenData}}" class="userinfo-opendata">
+      <view class="userinfo-block-avatar" bindtap="bindViewTap">
+        <open-data type="userAvatarUrl"></open-data>
+      </view>
+    </block>
+    <block wx:elif="{{!hasUserInfo}}">
+      <button 
+        wx:if="{{canIUseGetUserProfile}}" 
+        bindtap="getUserProfile"
+        class="userinfo-avatar"
+        style="background-image: url({{avatarUrl}})"
+        size="default"
+      ></button>
+      <button
+        wx:else
+        open-type="getUserInfo" 
+        bindgetuserinfo="onGetUserInfo"
+        class="userinfo-avatar"
+        style="background-image: url({{avatarUrl}})"
+        size="default"
+      ></button>
+    </block>
+    <block wx:else>
+      <image bindtap="bindViewTap" class="userinfo-block-avatar" src="{{avatarUrl}}" mode="cover"></image>
+    </block>
+    <view class="userinfo-nickname-wrapper">
+      <button class="userinfo-nickname" bindtap="onGetOpenid">点击获取 openid</button>
+    </view>
+  </view>
+
+
+  <!-- 上传图片 -->
+  <view class="uploader">
+    <view class="uploader-text" bindtap="doUpload">
+      <text>上传图片</text>
+    </view>
+    <view class="uploader-container" wx:if="{{imgUrl}}">
+      <image class="uploader-image" src="{{imgUrl}}" mode="aspectFit" bindtap="previewImg"></image>
+    </view>
+  </view>
+
+
+  <!-- 操作数据库 -->
+  <view class="uploader">
+    <navigator url="../databaseGuide/databaseGuide" open-type="navigate" class="uploader-text">
+      <text>前端操作数据库</text>
+    </navigator>
+  </view>
+
+  <!-- 即时通信 -->
+  <view class="uploader">
+    <navigator url="../im/im" open-type="navigate" class="uploader-text">
+      <text>即时通信 Demo</text>
+    </navigator>
+  </view>
+
+  <!-- 新建云函数 -->
+  <view class="uploader">
+    <navigator url="../addFunction/addFunction" open-type="navigate" class="uploader-text">
+      <text>快速新建云函数</text>
+    </navigator>
+  </view>
+
+  <!-- 云调用 -->
+  <view class="uploader">
+    <navigator url="../openapi/openapi" open-type="navigate" class="uploader-text">
+      <text>云调用</text>
+    </navigator>
+  </view>
+
+
+</view>

+ 161 - 0
miniprogram/pages/index/index.wxss

@@ -0,0 +1,161 @@
+/**index.wxss**/
+
+page {
+  background: #f6f6f6;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+}
+
+.userinfo, .uploader, .tunnel {
+  margin-top: 40rpx;
+  height: 140rpx;
+  width: 100%;
+  background: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  border-left: none;
+  border-right: none;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  transition: all 300ms ease;
+}
+
+.userinfo {
+  padding-left: 120rpx;
+}
+
+.userinfo-avatar {
+  width: 100rpx;
+  height: 100rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+  background-size: cover;
+  background-color: white;
+}
+
+.userinfo-block-avatar {
+  width: 100rpx;
+  height: 100rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+  overflow: hidden;
+}
+
+.userinfo-avatar[size] {
+  width: 100rpx;
+}
+
+
+.userinfo-avatar:after {
+  border: none;
+}
+
+.userinfo-nickname {
+  font-size: 32rpx;
+  color: #007aff;
+  background-color: white;
+  background-size: cover;
+  text-align: left;
+  padding-left: 0;
+  margin-left: 10px;
+}
+
+.userinfo-nickname::after {
+  border: none;
+}
+
+.userinfo-nickname-wrapper {
+  flex: 1;
+}
+
+.uploader, .tunnel {
+  height: auto;
+  padding: 0 0 0 40rpx;
+  flex-direction: column;
+  align-items: flex-start;
+  box-sizing: border-box;
+}
+
+.uploader-text, .tunnel-text {
+  width: 100%;
+  line-height: 52px;
+  font-size: 34rpx;
+  color: #007aff;
+}
+
+.uploader-container {
+  width: 100%;
+  height: 400rpx;
+  padding: 20rpx 20rpx 20rpx 0;
+  display: flex;
+  align-content: center;
+  justify-content: center;
+  box-sizing: border-box;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.uploader-image {
+  width: 100%;
+  height: 360rpx;
+}
+
+.tunnel {
+  padding: 0 0 0 40rpx;
+}
+
+.tunnel-text {
+  position: relative;
+  color: #222;
+  display: flex;
+  flex-direction: row;
+  align-content: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.tunnel-text:first-child {
+  border-top: none;
+}
+
+.tunnel-switch {
+  position: absolute;
+  right: 20rpx;
+  top: -2rpx;
+}
+
+.disable {
+  color: #888;
+}
+
+.service {
+  position: fixed;
+  right: 40rpx;
+  bottom: 40rpx;
+  width: 140rpx;
+  height: 140rpx;
+  border-radius: 50%;
+  background: linear-gradient(#007aff, #0063ce);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-content: center;
+  justify-content: center;
+  transition: all 300ms ease;
+}
+
+.service-button {
+  position: absolute;
+  top: 40rpx;
+}
+
+.service:active {
+  box-shadow: none;
+}
+
+.request-text {
+  padding: 20rpx 0;
+  font-size: 24rpx;
+  line-height: 36rpx;
+  word-break: break-all;
+}

BIN
miniprogram/pages/index/user-unlogin.png


+ 79 - 0
miniprogram/pages/interList/interList.js

@@ -0,0 +1,79 @@
+// miniprogram/pages/interList/interList.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+    console.log(options.type)
+    if(options.type == "news"){
+      wx.setNavigationBarTitle({
+        title: '新闻列表' 
+      })
+    }else{
+      wx.setNavigationBarTitle({
+        title: '视频列表' 
+      })
+    }
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  },
+
+  tolist:function(e){
+    console.log(e)
+  }
+})

+ 4 - 0
miniprogram/pages/interList/interList.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "列表"
+}

+ 12 - 0
miniprogram/pages/interList/interList.wxml

@@ -0,0 +1,12 @@
+<!--miniprogram/pages/interList/interList.wxml-->
+<view class="interList">
+  <mp-cell link hover value="新闻动态" footer="查看更多">
+  </mp-cell>
+  <view class="newList">
+    <image class="newsImg" src="https://xzhsjy.hdd.jxrtv.com/imgs/hddlogo.png"></image>
+    <view class="newsTitle">
+      <text class="top">新华云直播:“红色文物会说话、红色遗址会发声”融媒传播发布仪式暨“党性教育基地”揭牌仪式</text>
+      <text class="subTitle">发布日期 2021-05-15 11:25:00</text>
+    </view>
+  </view>
+</view>

+ 49 - 0
miniprogram/pages/interList/interList.wxss

@@ -0,0 +1,49 @@
+/* miniprogram/pages/interList/interList.wxss */
+.interList {
+  padding: 10rpx;
+}
+
+.videoList {
+  margin-bottom: 20rpx;
+}
+
+.videoList .title {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.newList {
+  width: 100%;
+  font-size: 0;
+  height: 240rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  border-radius: 22.5rpx;
+  box-shadow: 3rpx 3rpx 20rpx rgba(0, 0, 0, .1);
+}
+
+.newList .newsImg {
+  width: 240rpx;
+  height: 240rpx;
+}
+
+.newList .newsTitle {
+  width: 100%;
+  height: 240rpx;
+  font-size: 14px;
+  margin-top: -240rpx;
+  padding: 20rpx 10rpx 20rpx 250rpx;
+  box-sizing: border-box;
+}
+
+.newList .top {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  overflow: hidden;
+}
+
+.videoEle {
+  width: 100%;
+}

+ 15 - 0
miniprogram/pages/openapi/callback/callback.js

@@ -0,0 +1,15 @@
+// miniprogram/pages/openapi/callback/callback.js
+Page({
+
+  data: {
+
+  },
+
+  onLoad: function (options) {
+
+  },
+
+  onCustomerServiceButtonClick(e) {
+    console.log(e)
+  },
+})

+ 3 - 0
miniprogram/pages/openapi/callback/callback.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 16 - 0
miniprogram/pages/openapi/callback/callback.wxml

@@ -0,0 +1,16 @@
+<view class="container">
+  <view class="list">
+    <button open-type="contact" bindcontact="onCustomerServiceButtonClick">
+      进入客服消息
+    </button>
+  </view>
+
+  <view class="guide">
+    <text class="headline">测试须知</text>
+    <text class="p">1. 进入云开发控制台“设置-全局设置”,选择添加消息推送配置</text>
+    <text class="p">2. 消息类型选择 text(文本客户消息)</text>
+    <text class="p">3. 事件类型选择空</text>
+    <text class="p">4. 选择需要推送到哪个环境的哪个云函数</text>
+    <text class="p">5. 在手机上测试</text>
+  </view>
+</view>

+ 3 - 0
miniprogram/pages/openapi/callback/callback.wxss

@@ -0,0 +1,3 @@
+/* miniprogram/pages/openapi/callback/callback.wxss */
+
+@import "../../../style/guide.wxss";

+ 59 - 0
miniprogram/pages/openapi/cloudid/cloudid.js

@@ -0,0 +1,59 @@
+// miniprogram/pages/openapi/cloudid/cloudid.js
+Page({
+
+  data: {
+    weRunResult: '',
+    userInfoResult: '',
+  },
+
+  onGetWeRunData() {
+    wx.getWeRunData({
+      success: res => {
+        wx.cloud.callFunction({
+          name: 'echo',
+          data: {
+            // info 字段在云函数 event 对象中会被自动替换为相应的敏感数据
+            info: wx.cloud.CloudID(res.cloudID),
+          },
+        }).then(res => {
+          console.log('[onGetWeRunData] 收到 echo 回包:', res)
+
+          this.setData({
+            weRunResult: JSON.stringify(res.result),
+          })
+
+          wx.showToast({
+            title: '敏感数据获取成功',
+          })
+        }).catch(err => {
+          console.log('[onGetWeRunData] 失败:', err)
+        })
+      }
+    })
+  },
+
+  onGetUserInfo(e) {
+    console.log(e)
+    wx.cloud.callFunction({
+      name: 'openapi',
+      data: {
+        action: 'getOpenData',
+        openData: {
+          list: [
+            e.detail.cloudID,
+          ]
+        }
+      }
+    }).then(res => {
+      console.log('[onGetUserInfo] 调用成功:', res)
+
+      this.setData({
+        userInfoResult: JSON.stringify(res.result),
+      })
+
+      wx.showToast({
+        title: '敏感数据获取成功',
+      })
+    })
+  }
+})

+ 3 - 0
miniprogram/pages/openapi/cloudid/cloudid.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 41 - 0
miniprogram/pages/openapi/cloudid/cloudid.wxml

@@ -0,0 +1,41 @@
+<!--index.wxml-->
+<view class="container">
+
+  <view class="guide">
+    <text class="headline">开放数据调用</text>
+    <text class="p">通过 cloudID 获取敏感开放数据有以下两种方式</text>
+    <text class="p">1. 小程序端 callFunction 自动获取</text>
+    <text class="p">2. 通过 wx-server-sdk 获取</text>
+    <text class="p">以下分别先后展示这两种获取方式</text>
+  </view>
+
+  <view class="uploader">
+    <button class="uploader-text" bindtap="onGetWeRunData">getWeRunData 敏感数据获取</button>
+  </view>
+
+  <view class="guide">
+    <text class="headline">测试须知</text>
+    <text class="p">1. 公共库版本需大于 2.7.0</text>
+    <text class="p">2. 请确保 echo 函数已上传</text>
+  </view>
+
+  <view class="guide" style="word-break: break-all">
+    {{weRunResult}}
+  </view>
+
+
+  <view class="uploader">
+    <button class="uploader-text" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">getUserInfo 敏感数据获取</button>
+  </view>
+
+  <view class="guide">
+    <text class="headline">测试须知</text>
+    <text class="p">1. 公共库版本需大于 2.7.0</text>
+    <text class="p">2. 请确保 openapi 函数已上传</text>
+  </view>
+
+  <view class="guide" style="word-break: break-all">
+    {{userInfoResult}}
+  </view>
+
+</view>

+ 1 - 0
miniprogram/pages/openapi/cloudid/cloudid.wxss

@@ -0,0 +1 @@
+@import "../../../style/guide.wxss";

+ 5 - 0
miniprogram/pages/openapi/openapi.js

@@ -0,0 +1,5 @@
+Page({
+  data: {
+  },
+})
+

+ 3 - 0
miniprogram/pages/openapi/openapi.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 22 - 0
miniprogram/pages/openapi/openapi.wxml

@@ -0,0 +1,22 @@
+<!--index.wxml-->
+<view class="container">
+
+  <view class="uploader">
+    <navigator url="./serverapi/serverapi" open-type="navigate" class="uploader-text">
+      <text>服务端调用</text>
+    </navigator>
+  </view>
+
+  <view class="uploader">
+    <navigator url="./cloudid/cloudid" open-type="navigate" class="uploader-text">
+      <text>开放数据调用</text>
+    </navigator>
+  </view>
+
+  <view class="uploader">
+    <navigator url="./callback/callback" open-type="navigate" class="uploader-text">
+      <text>消息推送</text>
+    </navigator>
+  </view>
+
+</view>

+ 7 - 0
miniprogram/pages/openapi/openapi.wxss

@@ -0,0 +1,7 @@
+/* miniprogram/pages/openapi/openapi.wxss */
+
+@import "../../style/guide.wxss";
+
+.black {
+  color: black;
+}

+ 201 - 0
miniprogram/pages/openapi/serverapi/serverapi.js

@@ -0,0 +1,201 @@
+Page({
+
+  data: {
+    templateId: '',
+    subscribeMessageResult: '',
+    requestSubscribeMessageResult: '',
+    wxacodeSrc: '',
+    wxacodeResult: '',
+    showClearWXACodeCache: false,
+  },
+
+  async getSubscribeMessageTemplate() {
+    try {
+      const { result } = await wx.cloud.callFunction({
+        name: 'openapi',
+        data: {
+          action: 'requestSubscribeMessage',
+        },
+      })
+
+      const templateId = result
+
+      console.warn('[云函数] [openapi] 获取订阅消息模板 调用成功:', templateId)
+      this.setData({
+        templateId,
+      })
+    } catch (err) {
+      wx.showToast({
+        icon: 'none',
+        title: '调用失败',
+      })
+      console.error('[云函数] [openapi] 获取订阅消息模板 调用失败:', err)
+    }
+  },
+
+  async requestSubscribeMessage() {
+    const templateId = this.data.templateId
+
+    if (!templateId) {
+      wx.showModal({
+        title: '发送失败',
+        content: '请先获取模板 ID',
+        showCancel: false,
+      })
+    }
+
+    wx.requestSubscribeMessage({
+      tmplIds: [templateId],
+      success: (res) => {
+        if (res[templateId] === 'accept') {
+          this.setData({
+            requestSubscribeMessageResult: '成功',
+          })
+        } else {
+          this.setData({
+            requestSubscribeMessageResult: `失败(${res[templateId]})`,
+          })
+        }
+      },
+      fail: (err) => {
+        this.setData({
+          requestSubscribeMessageResult: `失败(${JSON.stringify(err)})`,
+        })
+      },
+    })
+  },
+
+  sendSubscribeMessage(e) {
+    this.setData({
+      subscribeMessageResult: '',
+    })
+
+    wx.cloud.callFunction({
+      name: 'openapi',
+      data: {
+        action: 'sendSubscribeMessage',
+        templateId: this.data.templateId,
+      },
+      success: res => {
+        console.warn('[云函数] [openapi] subscribeMessage.send 调用成功:', res)
+        wx.showModal({
+          title: '发送成功',
+          content: '请返回微信主界面查看',
+          showCancel: false,
+        })
+        wx.showToast({
+          title: '发送成功,请返回微信主界面查看',
+        })
+        this.setData({
+          subscribeMessageResult: JSON.stringify(res.result)
+        })
+      },
+      fail: err => {
+        wx.showToast({
+          icon: 'none',
+          title: '调用失败',
+        })
+        console.error('[云函数] [openapi] subscribeMessage.send 调用失败:', err)
+      }
+    })
+  },
+
+  submitSubscribeMessageForm(e) {
+    this.setData({
+      subscribeMessageResult: '',
+    })
+
+    wx.cloud.callFunction({
+      name: 'openapi',
+      data: {
+        action: 'sendSubscribeMessage',
+        formId: e.detail.formId,
+      },
+      success: res => {
+        console.warn('[云函数] [openapi] subscribeMessage.send 调用成功:', res)
+        wx.showModal({
+          title: '发送成功',
+          content: '请返回微信主界面查看',
+          showCancel: false,
+        })
+        wx.showToast({
+          title: '发送成功,请返回微信主界面查看',
+        })
+        this.setData({
+          templateMessageResult: JSON.stringify(res.result)
+        })
+      },
+      fail: err => {
+        wx.showToast({
+          icon: 'none',
+          title: '调用失败',
+        })
+        console.error('[云函数] [openapi] templateMessage.send 调用失败:', err)
+      }
+    })
+  },
+
+  onGetWXACode() {
+    this.setData({
+      wxacodeSrc: '',
+      wxacodeResult: '',
+      showClearWXACodeCache: false,
+    })
+
+    // 此处为演示,将使用 localStorage 缓存,正常开发中文件 ID 应存在数据库中
+    const fileID = wx.getStorageSync('wxacodeCloudID')
+
+    if (fileID) {
+      // 有云文件 ID 缓存,直接使用该 ID
+      // 如需清除缓存,选择菜单栏中的 “工具 -> 清除缓存 -> 清除数据缓存”,或在 Storage 面板中删掉相应的 key
+      this.setData({
+        wxacodeSrc: fileID,
+        wxacodeResult: `从本地缓存中取得了小程序码的云文件 ID`,
+        showClearWXACodeCache: true,
+      })
+      console.log(`从本地缓存中取得了小程序码的云文件 ID:${fileID}`)
+    } else {
+      wx.cloud.callFunction({
+        name: 'openapi',
+        data: {
+          action: 'getWXACode',
+        },
+        success: res => {
+          console.warn('[云函数] [openapi] wxacode.get 调用成功:', res)
+          wx.showToast({
+            title: '调用成功',
+          })
+          this.setData({
+            wxacodeSrc: res.result,
+            wxacodeResult: `云函数获取二维码成功`,
+            showClearWXACodeCache: true,
+          })
+          wx.setStorageSync('wxacodeCloudID', res.result)
+        },
+        fail: err => {
+          wx.showToast({
+            icon: 'none',
+            title: '调用失败',
+          })
+          console.error('[云函数] [openapi] wxacode.get 调用失败:', err)
+        }
+      })
+    }
+  },
+
+  clearWXACodeCache() {
+    wx.removeStorageSync('wxacodeCloudID')
+
+    this.setData({
+      wxacodeSrc: '',
+      wxacodeResult: '',
+      showClearWXACodeCache: false,
+    })
+
+    wx.showToast({
+      title: '清除成功',
+    })
+  },
+
+})
+

+ 3 - 0
miniprogram/pages/openapi/serverapi/serverapi.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 63 - 0
miniprogram/pages/openapi/serverapi/serverapi.wxml

@@ -0,0 +1,63 @@
+<view class="container">
+
+  <view class="list">
+    <view class="list-item" bindtap="getSubscribeMessageTemplate">
+      <text>获取订阅消息模板 ID</text>
+    </view>
+    <view class="list-item" wx:if="{{templateId}}">
+      <text class="request-text">模板 ID:{{templateId}}</text>
+    </view>
+  </view>
+
+  <view class="list">
+    <view class="list-item" bindtap="requestSubscribeMessage">
+      <text>获取下发权限</text>
+    </view>
+    <view class="list-item" wx:if="{{requestSubscribeMessageResult}}">
+      <text class="request-text">获取权限结果:{{requestSubscribeMessageResult}}</text>
+    </view>
+  </view>
+
+  <view class="list">
+    <view class="list-item" bindtap="sendSubscribeMessage">
+      <text>发送订阅消息</text>
+    </view>
+    <view class="list-item" wx:if="{{subscribeMessageResult}}">
+      <text class="request-text">调用结果:{{subscribeMessageResult}}</text>
+    </view>
+  </view>
+
+  <view class="guide">
+    <text class="headline">测试须知</text>
+    <text class="p">1. 需先到小程序管理后台,进入订阅消息管理</text>
+    <text class="p">2. 在订阅消息管理、公共模板库中添加一个模板</text>
+    <text class="p">3. 添加完成后在我的模板中点开模板详情</text>
+    <text class="p">4. 根据模板详情修改 openapi 云函数 index.js 中的相应位置</text>
+    <text class="p">5. 上传 cloudfunctions 目录下的 openapi 云函数</text>
+    <text class="p">6. 需在手机上预览测试,工具中无效</text>
+    <text class="p">7. 依次点击获取模板、获取下发权限、发送订阅消息</text>
+    <text class="p">8. 调用成功后返回到微信主界面查看收到的模板消息</text>
+  </view>
+
+  <view class="list">
+    <view class="list-item" bindtap="onGetWXACode">
+      <text>获取小程序码</text>
+    </view>
+    <view class="list-item" wx:if="{{wxacodeResult}}">
+      <text class="request-text">{{wxacodeResult}}</text>
+      <text class="request-text" wx:if="{{showClearWXACodeCache}}" bindtap="clearWXACodeCache">清除缓存</text>
+    </view>
+  </view>
+
+  <view class="guide">
+    <text class="headline">测试须知</text>
+    <text class="p">1. 需上传 cloudfunctions 目录下的 openapi 云函数</text>
+    <text class="p">2. 云函数中获取图片后会上传至存储空间并返回至小程序使用和缓存</text>
+    <text class="p">3. 云存储需设置为公有读</text>
+  </view>
+
+  <view class="guide">
+    <image src="{{wxacodeSrc}}" mode="aspectFit"></image>
+  </view>
+
+</view>

+ 7 - 0
miniprogram/pages/openapi/serverapi/serverapi.wxss

@@ -0,0 +1,7 @@
+/* miniprogram/pages/openapi/openapi.wxss */
+
+@import "../../../style/guide.wxss";
+
+.black {
+  color: black;
+}

+ 65 - 0
miniprogram/pages/ruins/index.js

@@ -0,0 +1,65 @@
+// miniprogram/pages/ruins/index.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+})

+ 4 - 0
miniprogram/pages/ruins/index.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "红色遗址会发声"
+}

+ 4 - 0
miniprogram/pages/ruins/index.wxml

@@ -0,0 +1,4 @@
+<!--miniprogram/pages/ruins/index.wxml-->
+<view class="ruins">
+
+</view>

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott