├── .gitignore
├── README.md
├── cloudfunctions
├── benchmark
│ ├── index.js
│ └── package.json
├── cancelFollowing
│ ├── index.js
│ └── package.json
├── cancelLiked
│ ├── index.js
│ └── package.json
├── deletePoster
│ ├── index.js
│ └── package.json
├── getFollowers
│ ├── index.js
│ └── package.json
├── getFollowings
│ ├── index.js
│ └── package.json
├── getIfFollow
│ ├── index.js
│ └── package.json
├── getMainPageData
│ ├── index.js
│ └── package.json
├── getMePageData
│ ├── index.js
│ └── package.json
├── getPosterData
│ ├── index.js
│ └── package.json
├── getPosterLikes
│ ├── index.js
│ └── package.json
├── helper_test
│ ├── config.json
│ ├── index.js
│ └── package.json
├── login
│ ├── index.js
│ └── package.json
├── openapi
│ └── index.js
└── searchUsers
│ ├── index.js
│ └── package.json
├── extra
└── 小程序云开发实战-迷你微博.md
├── miniprogram
├── app.js
├── app.json
├── app.wxss
├── component
│ └── image-wrapper
│ │ ├── image-wrapper.js
│ │ ├── image-wrapper.json
│ │ ├── image-wrapper.wxml
│ │ └── image-wrapper.wxss
├── images
│ └── error.png
├── pages
│ ├── circle
│ │ ├── add-poster
│ │ │ ├── add-poster.js
│ │ │ ├── add-poster.json
│ │ │ ├── add-poster.wxml
│ │ │ └── add-poster.wxss
│ │ ├── circle.js
│ │ ├── circle.json
│ │ ├── circle.wxml
│ │ ├── circle.wxss
│ │ ├── component
│ │ │ ├── abstract-load
│ │ │ │ ├── abstract-load.js
│ │ │ │ ├── abstract-load.json
│ │ │ │ ├── abstract-load.wxml
│ │ │ │ └── abstract-load.wxss
│ │ │ ├── post-item
│ │ │ │ ├── post-item.js
│ │ │ │ ├── post-item.json
│ │ │ │ ├── post-item.wxml
│ │ │ │ └── post-item.wxss
│ │ │ └── user-info
│ │ │ │ ├── user-info.js
│ │ │ │ ├── user-info.json
│ │ │ │ ├── user-info.wxml
│ │ │ │ └── user-info.wxss
│ │ ├── poster-detail
│ │ │ ├── poster-detail.js
│ │ │ ├── poster-detail.json
│ │ │ ├── poster-detail.wxml
│ │ │ └── poster-detail.wxss
│ │ ├── search-user
│ │ │ ├── search-user.js
│ │ │ ├── search-user.json
│ │ │ ├── search-user.wxml
│ │ │ └── search-user.wxss
│ │ ├── user-data
│ │ │ ├── user-data.js
│ │ │ ├── user-data.json
│ │ │ ├── user-data.wxml
│ │ │ └── user-data.wxss
│ │ ├── user-follower
│ │ │ ├── user-follower.js
│ │ │ ├── user-follower.json
│ │ │ ├── user-follower.wxml
│ │ │ └── user-follower.wxss
│ │ ├── user-following
│ │ │ ├── user-following.js
│ │ │ ├── user-following.json
│ │ │ ├── user-following.wxml
│ │ │ └── user-following.wxss
│ │ └── user-poster
│ │ │ ├── user-poster.js
│ │ │ ├── user-poster.json
│ │ │ ├── user-poster.wxml
│ │ │ └── user-poster.wxss
│ └── index
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── user-unlogin.png
├── sitemap.json
└── style
│ └── guide.wxss
└── project.config.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows
2 | [Dd]esktop.ini
3 | Thumbs.db
4 | $RECYCLE.BIN/
5 |
6 | # macOS
7 | .DS_Store
8 | .fseventsd
9 | .Spotlight-V100
10 | .TemporaryItems
11 | .Trashes
12 |
13 | # Node.js
14 | node_modules/
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### KuthorX-Helper
2 |
3 | 教你手把手写小程序X云开发!
4 |
5 | [小程序云开发实战-迷你微博](./extra/小程序云开发实战-迷你微博.md)
6 |
--------------------------------------------------------------------------------
/cloudfunctions/benchmark/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async (event, context) => {
8 | const test = await db.collection("poster").where({
9 | authorId: "93db88df603dbeccac52f65c68dfc21c86ff2cbb"
10 | }).get()
11 | return test
12 | }
--------------------------------------------------------------------------------
/cloudfunctions/benchmark/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "benchmark",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/cancelFollowing/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async(event, context) => {
8 | const result = await db.collection("poster_user_follows")
9 | .where({
10 | followerId: event.followerId,
11 | followingId: event.followingId,
12 | })
13 | .remove()
14 | return result
15 | }
--------------------------------------------------------------------------------
/cloudfunctions/cancelFollowing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cancelFollowing",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/cancelLiked/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async (event, context) => {
8 | const result = await db.collection("poster_likes")
9 | .where({
10 | posterId: event.posterId,
11 | likeId: event.likeId,
12 | })
13 | .remove()
14 | return result
15 | }
--------------------------------------------------------------------------------
/cloudfunctions/cancelLiked/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cancelLiked",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/deletePoster/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async(event, context) => {
8 | const result = await db.collection("poster").doc(event._id)
9 | .remove()
10 | return result
11 | }
--------------------------------------------------------------------------------
/cloudfunctions/deletePoster/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deletePoster",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getFollowers/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async (event, context) => {
8 | const userId = event.userId
9 | const result = await db.collection("poster_user_follows").where({
10 | followingId: userId
11 | }).get()
12 | const followerIds = result.data.map(v => {
13 | return v.followerId
14 | })
15 | const followerInfos = await db.collection("poster_users").where({
16 | userId: db.command.in(followerIds)
17 | }).get()
18 | return followerInfos
19 | }
--------------------------------------------------------------------------------
/cloudfunctions/getFollowers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getFollowers",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getFollowings/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async (event, context) => {
8 | const userId = event.userId
9 | const result = await db.collection("poster_user_follows").where({
10 | followerId: userId
11 | }).get()
12 | const followingIds = result.data.map(v => {
13 | return v.followingId
14 | })
15 | const followingInfos = await db.collection("poster_users").where({
16 | userId: db.command.in(followingIds)
17 | }).get()
18 | return followingInfos
19 | }
--------------------------------------------------------------------------------
/cloudfunctions/getFollowings/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getFollowings",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getIfFollow/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | // 云函数入口函数
7 | exports.main = async(event, context) => {
8 | const followingResult = await db.collection("poster_user_follows")
9 | .where({
10 | followingId: event.followingId,
11 | followerId: event.followerId
12 | }).get()
13 | return followingResult
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getIfFollow/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getIfFollow",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getMainPageData/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require("wx-server-sdk")
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | const options = {
7 | year: "numeric",
8 | month: "numeric",
9 | day: "numeric",
10 | hour: "numeric",
11 | minute: "numeric",
12 | second: "numeric",
13 | hourCycle: "h23"
14 | }
15 |
16 | const MAX_LIMIT = 100
17 |
18 | async function getDbData(dbName, whereObj) {
19 | const totalCountsData = await db
20 | .collection(dbName)
21 | .where(whereObj)
22 | .count()
23 | const total = totalCountsData.total
24 | console.log(total)
25 | const batch = Math.ceil(total / 100)
26 | const tasks = []
27 | for (let i = 0; i < batch; i++) {
28 | const promise = db
29 | .collection(dbName)
30 | .where(whereObj)
31 | .skip(i * MAX_LIMIT)
32 | .limit(MAX_LIMIT)
33 | .get()
34 | tasks.push(promise)
35 | }
36 | const rrr = await Promise.all(tasks)
37 | if (rrr.length !== 0) {
38 | return rrr.reduce((acc, cur) => {
39 | console.log(acc)
40 | return {
41 | data: acc.data.concat(cur.data),
42 | errMsg: acc.errMsg
43 | }
44 | })
45 | } else {
46 | return {
47 | data: [],
48 | errMsg: "empty"
49 | }
50 | }
51 | }
52 |
53 | // 云函数入口函数
54 | exports.main = async (event, context, cb) => {
55 | const userId = event.userId
56 | const isEveryOne = event.isEveryOne
57 | const isOnlyMe = event.isOnlyMe
58 | let followingResult
59 | let users
60 | let idNameMap = {}
61 | let followingIds = []
62 | if (isEveryOne) {
63 | // followingResult = await getDbData("poster_users", {})
64 | followingResult = await db.collection("poster_users").get()
65 | users = followingResult.data
66 | followingIds = users.map(u => {
67 | return u.userId
68 | })
69 | } else if (isOnlyMe) {
70 | // followingResult = await getDbData("poster_users", {
71 | // userId: userId
72 | // })
73 | followingResult = await db
74 | .collection("poster_users")
75 | .where({
76 | userId: userId
77 | })
78 | .get()
79 | users = followingResult.data
80 | followingIds = users.map(u => {
81 | return u.userId
82 | })
83 | } else {
84 | // followingResult = await getDbData("poster_user_follows", {
85 | // followerId: userId
86 | // })
87 | followingResult = await db
88 | .collection("poster_user_follows")
89 | .where({
90 | followerId: userId
91 | })
92 | .get()
93 | const following = followingResult.data
94 | followingIds = following.map(f => {
95 | return f.followingId
96 | })
97 | followingIds.push(userId)
98 | // const userData = await getDbData("poster_users", {
99 | // userId: db.command.in(followingIds)
100 | // })
101 | userData = await db
102 | .collection("poster_users")
103 | .where({
104 | userId: db.command.in(followingIds)
105 | })
106 | .get()
107 | users = userData.data
108 | }
109 | users.map(u => {
110 | idNameMap[u.userId] = u.name
111 | })
112 | console.log(idNameMap)
113 | // 获取动态
114 | // const postResult = await getDbData("poster", {
115 | // authorId: db.command.in(followingIds)
116 | // })
117 | const postResult = await db
118 | .collection("poster")
119 | .orderBy("date", "desc")
120 | .where({
121 | authorId: db.command.in(followingIds)
122 | })
123 | .get()
124 | const postData = postResult.data
125 | console.log(postData)
126 | postData.map(p => {
127 | p.author = idNameMap[p.authorId]
128 | p.formatDate = new Date(p.date).toLocaleDateString("zh-Hans", options)
129 | })
130 | console.log(postData)
131 | return postData
132 | }
133 |
--------------------------------------------------------------------------------
/cloudfunctions/getMainPageData/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getMainPageData",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getMePageData/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require("wx-server-sdk")
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | async function getPosterCount(userId) {
7 | return {
8 | value: (await db.collection("poster").where({
9 | authorId: userId
10 | }).count()).total,
11 | key: "posterCount"
12 | }
13 | }
14 |
15 | async function getFollowingCount(userId) {
16 | return {
17 | value: (await db.collection("poster_user_follows").where({
18 | followerId: userId
19 | }).count()).total,
20 | key: "followingCount"
21 | }
22 | }
23 |
24 | async function getFollowerCount(userId) {
25 | return {
26 | value: (await db.collection("poster_user_follows").where({
27 | followingId: userId
28 | }).count()).total,
29 | key: "followerCount"
30 | }
31 | }
32 |
33 |
34 | async function getUserName(userId) {
35 | return {
36 | value: (await db.collection("poster_users").where({
37 | userId: userId
38 | }).get()).data[0].name,
39 | key: "userName"
40 | }
41 | }
42 |
43 | // 云函数入口函数
44 | exports.main = async (event, context) => {
45 | const userId = event.userId
46 | // const userName = await db.collection("poster_users").where({
47 | // userId: userId
48 | // })
49 | // const posterCount = await db.collection("poster").where({
50 | // authorId: userId
51 | // }).count()
52 | // const followingCount = await db.collection("poster_user_follows").where({
53 | // followerId: userId
54 | // }).count()
55 | // const followerCount = await db.collection("poster_user_follows").where({
56 | // followingId: userId
57 | // }).count()
58 |
59 | const tasks = []
60 | tasks.push(getPosterCount(userId))
61 | tasks.push(getFollowerCount(userId))
62 | tasks.push(getFollowingCount(userId))
63 | tasks.push(getUserName(userId))
64 |
65 | const allData = await Promise.all(tasks)
66 | const finalData = {}
67 | allData.map(d => {
68 | finalData[d.key] = d.value
69 | })
70 |
71 | return finalData
72 | }
--------------------------------------------------------------------------------
/cloudfunctions/getMePageData/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getMePageData",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getPosterData/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require("wx-server-sdk")
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | const options = {
7 | year: "numeric",
8 | month: "numeric",
9 | day: "numeric",
10 | hour: "numeric",
11 | minute: "numeric",
12 | second: "numeric",
13 | hourCycle: "h23"
14 | }
15 |
16 | // 云函数入口函数
17 | exports.main = async (event, context) => {
18 | const posterData = await db.collection("poster").doc(event.posterId).get()
19 | const posterInner = posterData.data
20 | const authorId = posterInner.authorId
21 | const authorInfo = await db.collection("poster_users").where({
22 | userId: authorId
23 | }).get()
24 | const name = authorInfo.data[0].name
25 | posterInner.author = name
26 | posterInner.formatDate = new Date(posterInner.date).toLocaleDateString("zh-Hans", options)
27 | return posterInner
28 | }
--------------------------------------------------------------------------------
/cloudfunctions/getPosterData/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getPosterData",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/getPosterLikes/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require("wx-server-sdk")
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | const options = {
7 | year: "numeric",
8 | month: "numeric",
9 | day: "numeric",
10 | hour: "numeric",
11 | minute: "numeric",
12 | second: "numeric",
13 | hourCycle: "h23"
14 | }
15 |
16 | // 云函数入口函数
17 | exports.main = async (event, context) => {
18 | const likesData = await db.collection("poster_likes").where({
19 | posterId: event.posterId
20 | }).get()
21 | const likesInner = likesData.data
22 | const likeUserIds = likesInner.map(l => {
23 | return l.likeId
24 | })
25 | const userInfos = await db.collection("poster_users").where({
26 | userId: db.command.in(likeUserIds)
27 | }).get()
28 | const userInner = userInfos.data
29 | const idNameMap = {}
30 | userInner.map(u => {
31 | idNameMap[u.userId] = u.name
32 | })
33 | likesInner.map(l => {
34 | l.name = idNameMap[l.likeId]
35 | })
36 | return likesInner
37 | }
--------------------------------------------------------------------------------
/cloudfunctions/getPosterLikes/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getPosterLikes",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/helper_test/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "permissions": {
3 | "openapi": [
4 | "customerServiceMessage.send"
5 | ]
6 | }
7 | }
--------------------------------------------------------------------------------
/cloudfunctions/helper_test/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 |
4 | cloud.init()
5 |
6 | function getRandomInt(max) {
7 | return Math.floor(Math.random() * Math.floor(max));
8 | }
9 |
10 |
11 | // 云函数入口函数
12 | exports.main = async(event, context) => {
13 | const wxContext = cloud.getWXContext()
14 | console.log(event)
15 |
16 | const content = event.Content
17 | if (content === "推理小说") {
18 | const db = cloud.database()
19 | const countResult = await db.collection('books').count()
20 | const id = getRandomInt(countResult.total) + 1
21 | const result = await db.collection('books').where({
22 | id: id
23 | })
24 | .get()
25 | const data = result.data[0]
26 | const resultString = `中文基本信息:\n${data.ch}\n英文基本信息:\n${data.en}\n基本介绍:\n${data.intro}`
27 | await cloud.openapi.customerServiceMessage.send({
28 | touser: wxContext.OPENID,
29 | msgtype: 'text',
30 | text: {
31 | content: resultString,
32 | },
33 | })
34 |
35 | return 'success'
36 | }
37 |
38 | const test = {
39 | "ToUserName": "toUser",
40 | "FromUserName": "fromUser",
41 | "CreateTime": 1482048670,
42 | "MsgType": "text",
43 | "Content": "this is a test",
44 | "MsgId": 1234567890123456
45 | }
46 |
47 | await cloud.openapi.customerServiceMessage.send({
48 | touser: wxContext.OPENID,
49 | msgtype: 'text',
50 | text: {
51 | content: '不识别的信息哦,请输入其他信息吧!\n目前客服支持:\n1. 推理小说推荐:发送“推理小说”即可随机获得一本推理小说信息\n即将上线:\n1. 淘金记',
52 | },
53 | })
54 |
55 | // return 'success'
56 | }
--------------------------------------------------------------------------------
/cloudfunctions/helper_test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "helper_test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/cloudfunctions/login/index.js:
--------------------------------------------------------------------------------
1 | // 云函数模板
2 | // 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署”
3 |
4 | const cloud = require('wx-server-sdk')
5 |
6 | // 初始化 cloud
7 | cloud.init()
8 |
9 | /**
10 | * 这个示例将经自动鉴权过的小程序用户 openid 返回给小程序端
11 | *
12 | * event 参数包含小程序端调用传入的 data
13 | *
14 | */
15 | exports.main = (event, context) => {
16 | console.log(event)
17 | console.log(context)
18 |
19 | // 可执行其他自定义逻辑
20 | // console.log 的内容可以在云开发云函数调用日志查看
21 |
22 | // 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)
23 | const wxContext = cloud.getWXContext()
24 |
25 | return {
26 | event,
27 | openid: wxContext.OPENID,
28 | appid: wxContext.APPID,
29 | unionid: wxContext.UNIONID,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/cloudfunctions/login/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "login",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cloudfunctions/openapi/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KuthorX/KuthorX-Helper/03ac0aba45ac1b44b6f485a0eb2d8e340c1ae49d/cloudfunctions/openapi/index.js
--------------------------------------------------------------------------------
/cloudfunctions/searchUsers/index.js:
--------------------------------------------------------------------------------
1 | // 云函数入口文件
2 | const cloud = require('wx-server-sdk')
3 | cloud.init()
4 | const db = cloud.database()
5 |
6 | const MAX_LIMIT = 100
7 |
8 | async function getDbData(dbName, whereObj) {
9 | const totalCountsData = await db.collection(dbName).where(whereObj).count()
10 | const total = totalCountsData.total
11 | const batch = Math.ceil(total / 100)
12 | const tasks = []
13 | for (let i = 0; i < batch; i++) {
14 | const promise = db
15 | .collection(dbName)
16 | .where(whereObj)
17 | .skip(i * MAX_LIMIT)
18 | .limit(MAX_LIMIT)
19 | .get()
20 | tasks.push(promise)
21 | }
22 | const rrr = await Promise.all(tasks)
23 | if (rrr.length !== 0) {
24 | return rrr.reduce((acc, cur) => {
25 | return {
26 | data: acc.data.concat(cur.data),
27 | errMsg: acc.errMsg
28 | }
29 | })
30 | } else {
31 | return {
32 | data: [],
33 | errMsg: "empty"
34 | }
35 | }
36 | }
37 |
38 | // 云函数入口函数
39 | exports.main = async (event, context) => {
40 | const text = event.text
41 | const data = await getDbData("poster_users", {
42 | name: {
43 | $regex: text
44 | }
45 | })
46 | return data
47 | }
--------------------------------------------------------------------------------
/cloudfunctions/searchUsers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "searchUsers",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "wx-server-sdk": "latest"
13 | }
14 | }
--------------------------------------------------------------------------------
/extra/小程序云开发实战-迷你微博.md:
--------------------------------------------------------------------------------
1 | ### 0. 前言
2 |
3 | 本文将手把手教你如何写出迷你版微博的一行行代码,迷你版微博包含以下功能:
4 |
5 | - Feed 流:关注动态、所有动态
6 | - 发送图文动态
7 | - 搜索用户
8 | - 关注系统
9 | - 点赞动态
10 | - 个人主页
11 |
12 | 使用到的云开发能力:
13 |
14 | - 云数据库
15 | - 云存储
16 | - 云函数
17 | - 云调用
18 |
19 | 没错,几乎是所有的云开发能力。也就是说,读完这篇实战,你就相当于完全入门了云开发!
20 |
21 | 咳咳,当然,实际上这里只是介绍核心逻辑和重点代码片段,完整代码建议下载查看。
22 |
23 | ### 1. 取得授权
24 |
25 | 作为一个社交平台,首先要做的肯定是经过用户授权,获取用户信息,小程序提供了很方便的接口:
26 |
27 | ```html
28 |
31 | ```
32 |
33 | 这个 `button` 有个 `open-type` 属性,这个属性是专门用来使用小程序的开放能力的,而 `getUserInfo` 则表示 **获取用户信息,可以从`bindgetuserinfo`回调中获取到用户信息**。
34 |
35 | 于是我们可以在 wxml 里放入这个 `button` 后,在相应的 js 里写如下代码:
36 |
37 | ```js
38 | Page({
39 | ...
40 |
41 | getUserInfo: function(e) {
42 | wx.navigateTo({
43 | url: "/pages/circle/circle"
44 | })
45 | },
46 |
47 | ...
48 | })
49 | ```
50 |
51 | 这样在成功获取到用户信息后,我们就能跳转到迷你微博页面了。
52 |
53 | 需要注意,不能使用 `wx.authorize({scope: "scope.userInfo"})` 来获取读取用户信息的权限,因为它不会跳出授权弹窗。目前只能使用上面所述的方式实现。
54 |
55 | ### 2. 主页设计
56 |
57 | 社交平台的主页大同小异,主要由三个部分组成:
58 |
59 | - Feed 流
60 | - 消息
61 | - 个人信息
62 |
63 | 那么很容易就能想到这样的布局(注意新建一个 Page 哦,路径:`pages/circle/circle.wxml`):
64 |
65 | ```html
66 |
67 |
71 |
72 |
73 |
77 |
78 |
79 |
83 |
84 |
85 |
114 |
115 | ```
116 |
117 | 很好理解,画面主要被分为上下两个部分:上面的部分是主要内容,下面的部分是三个 Tab 组成的 Footer。重点 WXSS 实现(完整的 WXSS 可以下载源码查看):
118 |
119 | ```css
120 | .footer {
121 | box-shadow: 0 0 15rpx #ccc;
122 | display: flex;
123 | position: fixed;
124 | height: 120rpx;
125 | bottom: 0;
126 | width: 100%;
127 | flex-direction: row;
128 | justify-content: center;
129 | z-index: 100;
130 | background: #fff;
131 | }
132 |
133 | .footer-item {
134 | display: flex;
135 | justify-content: center;
136 | align-items: center;
137 | height: 100%;
138 | width: 33.33%;
139 | color: #333;
140 | }
141 |
142 | .footer-item:nth-child(2) {
143 | border-left: 3rpx solid #aaa;
144 | border-right: 3rpx solid #aaa;
145 | flex-grow: 1;
146 | }
147 |
148 | .footer-btn {
149 | width: 100%;
150 | height: 100%;
151 | display: flex;
152 | justify-content: center;
153 | align-items: center;
154 | border-radius: 0;
155 | font-size: 30rpx;
156 | }
157 | ```
158 |
159 | 核心逻辑是通过 `position: fixed` 来让 Footer 一直在下方。
160 |
161 | 读者会发现有一个 `currentPage` 的 data ,这个 data 的作用其实很直观:通过判断它的值是 `main`/`msg`/`me` 中的哪一个来决定主要内容。同时,为了让首次使用的用户知道自己在哪个 Tab,Footer 中相应的 `button` 也会从白底黑字黑底白字,与另外两个 Tab 形成对比。
162 |
163 | 现在我们来看看 `main` 部分的代码(在上面代码的基础上扩充):
164 |
165 | ```html
166 | ...
167 |
171 |
172 |
178 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
191 |
192 |
198 |
199 |
200 | 无数据
203 |
204 |
212 |
213 | ...
214 | ```
215 |
216 | 这里用到了 [列表渲染](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html) 和 [条件渲染](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/conditional.html),还不清楚的可以点击进去学习一下。
217 |
218 | 可以看到,相比之前的代码,我添加一个 header,同时 `main-area` 的内部也新增了一个 `scroll-view`(用于展示 Feed 流) 和一个 `button`(用于编辑新迷你微博)。header 的功能很简单:左侧区域是一个 `picker`,可以选择查看的动态类型(目前有 _关注动态_ 和 _所有动态_ 两种);右侧区域是一个按钮,点击后可以跳转到搜索页面,这两个功能我们先放一下,先继续看 `main-area` 的新增内容。
219 |
220 | `main-area` 里的 `scroll-view` 是一个可监听滚动事件的列表,其中监听事件的实现:
221 |
222 | ```js
223 | data: {
224 | ...
225 | addPosterBtnBottom: "190rpx",
226 | mainHeaderMaxHeight: "80rpx",
227 | mainAreaHeight: "calc(100vh - 200rpx)",
228 | mainAreaMarginTop: "80rpx",
229 | },
230 | onMainPageScroll: function(e) {
231 | if (e.detail.deltaY < 0) {
232 | this.setData({
233 | addPosterBtnBottom: "-190rpx",
234 | mainHeaderMaxHeight: "0",
235 | mainAreaHeight: "calc(100vh - 120rpx)",
236 | mainAreaMarginTop: "0rpx"
237 | })
238 | } else {
239 | this.setData({
240 | addPosterBtnBottom: "190rpx",
241 | mainHeaderMaxHeight: "80rpx",
242 | mainAreaHeight: "calc(100vh - 200rpx)",
243 | mainAreaMarginTop: "80rpx"
244 | })
245 | }
246 | },
247 | ...
248 | ```
249 |
250 | 结合 wxml 可以知道,当页面向下滑动 (deltaY < 0) 时,header 和 `button` 会 “突然消失”,反之它们则会 “突然出现”。为了视觉上有更好地过渡,我们可以在 WXSS 中使用 `transition` :
251 |
252 | ```css
253 | ...
254 | .main-area {
255 | position: relative;
256 | flex-grow: 1;
257 | overflow: auto;
258 | z-index: 1;
259 | transition: height 0.3s, margin-top 0.3s;
260 | }
261 | .main-header {
262 | position: fixed;
263 | width: 100%;
264 | height: 80rpx;
265 | background: #fff;
266 | top: 0;
267 | left: 0;
268 | display: flex;
269 | justify-content: space-around;
270 | align-items: center;
271 | z-index: 100;
272 | border-bottom: 3rpx solid #aaa;
273 | transition: max-height 0.3s;
274 | overflow: hidden;
275 | }
276 | .add-poster-btn {
277 | position: fixed;
278 | right: 60rpx;
279 | box-shadow: 5rpx 5rpx 10rpx #aaa;
280 | display: flex;
281 | justify-content: center;
282 | align-items: center;
283 | color: #333;
284 | padding-bottom: 10rpx;
285 | text-align: center;
286 | border-radius: 50%;
287 | font-size: 60rpx;
288 | width: 100rpx;
289 | height: 100rpx;
290 | transition: bottom 0.3s;
291 | background: #fff;
292 | z-index: 1;
293 | }
294 | ...
295 | ```
296 |
297 | ### 3. Feed 流
298 |
299 | #### 3.1 post-item
300 |
301 | 前面提到,`scroll-view` 的内容是 Feed 流,那么首先就要想到使用 [列表渲染](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html)。而且,为了方便在个人主页复用,列表渲染中的每一个 item 都要抽象出来。这时就要使用小程序中的 [Custom-Component](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/) 功能了。
302 |
303 | 新建一个名为 `post-item` 的 `Component`,其中 wxml 的实现(路径:`pages/circle/component/post-item/post-item.js`):
304 |
305 | ```html
306 |
312 |
313 | {{data.author}}
316 | {{data.formatDate}}
317 |
318 |
319 | {{data.msg}}
320 |
321 |
322 |
323 |
324 |
325 | ```
326 |
327 | 可见,一个 `poster-item` 最主要有以下信息:
328 |
329 | - 作者名
330 | - 发送时间
331 | - 文本内容
332 | - 图片内容
333 |
334 | 其中,图片内容因为是可选的,所以使用了 [条件渲染](https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/conditional.html),这会在没有图片信息时不让图片显示区域占用屏幕空间。另外,图片内容主要是由 `image-wrapper` 组成,它也是一个 `Custom-Component`,主要功能是:
335 |
336 | - 强制长宽 1:1 裁剪显示图片
337 | - 点击查看大图
338 | - 未加载完成时显示 加载中
339 |
340 | 具体代码这里就不展示了,比较简单,读者可以在 `component/image-wrapper` 里找到。
341 |
342 | 回过头看 `main-area` 的其他新增部分,细心的读者会发现有这么一句:
343 |
344 | ```html
345 | 无数据
348 | ```
349 |
350 | 这会在 Feed 流暂时没有获取到数据时给用户一个提示。
351 |
352 | #### 3.2 collections: poster、poster_users
353 |
354 | 展示 Feed 流的部分已经编写完毕,现在就差实际数据了。根据上一小节 `poster-item` 的主要信息,我们可以初步推断出一条迷你微博在 [云数据库](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html#%E6%95%B0%E6%8D%AE%E5%BA%93) 的 collection `poster` 里是这样存储的:
355 |
356 | ```json
357 | {
358 | "username": "Tester",
359 | "date": "2019-07-22 12:00:00",
360 | "text": "Ceshiwenben",
361 | "photo": "xxx"
362 | }
363 | ```
364 |
365 | 先来看 `username`。由于社交平台一般不会限制用户的昵称,所以如果每条迷你微博都存储昵称,那将来每次用户修改一次昵称,就要遍历数据库把所有迷你微博项都改一遍,相当耗费时间,所以我们不如存储一个 `userId`,并另外把 id 和 昵称 的对应关系存在另一个叫 `poster_users` 的 collection 里。
366 |
367 | ```json
368 | {
369 | "userId": "xxx",
370 | "name": "Tester",
371 | ...(其他用户信息)
372 | }
373 | ```
374 |
375 | `userId` 从哪里拿呢?当然是通过之前已经授权的获取用户信息接口拿到了,详细操作之后会说到。
376 |
377 | 接下来是 `date`,这里最好是服务器时间(因为客户端传过来的时间可能会有误差),而云开发文档里也有提供相应的接口:[serverDate](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/database/db.serverDate.html#db.serverDate)。这个数据可以直接被 `new Date()` 使用,可以理解为一个 UTC 时间。
378 |
379 | `text` 即文本信息,直接存储即可。
380 |
381 | `photo` 则表示附图数据,但是限于小程序 `image` 元素的实现,想要显示一张图片,要么提供该图片的 url,要么提供该图片在 [云存储](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html#%E5%AD%98%E5%82%A8) 的 id,所以这里最佳的实践是:先把图片上传到云存储里,然后把回调里的文件 id 作为数据存储。
382 |
383 | 综上所述,最后 `poster` 每一项的数据结构如下:
384 |
385 | ```json
386 | {
387 | "authorId": "xxx",
388 | "date": "utc-format-date",
389 | "text": "Ceshiwenben",
390 | "photoId": "yyy"
391 | }
392 | ```
393 |
394 | 确定数据结构后,我们就可以开始往 collection 添加数据了。但是,在此之前,我们还缺少一个重要步骤。
395 |
396 | #### 3.3 用户信息录入 与 云数据库
397 |
398 | 没错,我们还没有在 `poster_users` 里添加一条新用户的信息。这个步骤一般在 `pages/circle/circle` 页面首次加载时判断即可:
399 |
400 | ```js
401 | getUserId: function(cb) {
402 | let that = this
403 | var value = this.data.userId || wx.getStorageSync("userId")
404 | if (value) {
405 | if (cb) {
406 | cb(value)
407 | }
408 | return value
409 | }
410 | wx.getSetting({
411 | success(res) {
412 | if (res.authSetting["scope.userInfo"]) {
413 | wx.getUserInfo({
414 | withCredentials: true,
415 | success: function(userData) {
416 | wx.setStorageSync("userId", userData.signature)
417 | that.setData({
418 | userId: userData.signature
419 | })
420 | db.collection("poster_users")
421 | .where({
422 | userId: userData.signature
423 | })
424 | .get()
425 | .then(searchResult => {
426 | if (searchResult.data.length === 0) {
427 | wx.showToast({
428 | title: "新用户录入中"
429 | })
430 | db.collection("poster_users")
431 | .add({
432 | data: {
433 | userId: userData.signature,
434 | date: db.serverDate(),
435 | name: userData.userInfo.nickName,
436 | gender: userData.userInfo.gender
437 | }
438 | })
439 | .then(res => {
440 | console.log(res)
441 | if (res.errMsg === "collection.add:ok") {
442 | wx.showToast({
443 | title: "录入完成"
444 | })
445 | if (cb) cb()
446 | }
447 | })
448 | .catch(err => {
449 | wx.showToast({
450 | title: "录入失败,请稍后重试",
451 | image: "/images/error.png"
452 | })
453 | wx.navigateTo({
454 | url: "/pages/index/index"
455 | })
456 | })
457 | } else {
458 | if (cb) cb()
459 | }
460 | })
461 | }
462 | })
463 | } else {
464 | wx.showToast({
465 | title: "登陆失效,请重新授权登陆",
466 | image: "/images/error.png"
467 | })
468 | wx.navigateTo({
469 | url: "/pages/index/index"
470 | })
471 | }
472 | }
473 | })
474 | }
475 | ```
476 |
477 | 代码实现比较复杂,整体思路是这样的:
478 |
479 | 1. 判断是否已存储了 `userId`,如果有直接返回并调用回调函数,如果没有继续 2
480 | 2. 通过 `wx.getSetting` 获取当前设置信息
481 | 3. 如果返回里有 `res.authSetting["scope.userInfo"]` 说明已经授权读取用户信息,继续 3,没有授权的话就跳转回首页重新授权
482 | 4. 调用 `wx.getUserInfo` 获取用户信息,成功后提取出 `signature`(这是每个微信用户的唯一签名),并调用 `wx.setStorageSync` 将其缓存
483 | 5. 调用 `db.collection().where().get()` ,判断返回的数据是否是空数组,如果不是说明该用户已经录入(注意 `where()` 中的筛选条件),如果是说明该用户是新用户,继续 5
484 | 6. 提示新用户录入中,同时调用 `db.collection().add()` 来添加用户信息,最后通过回调判断是否录入成功,并提示用户
485 |
486 | 不知不觉我们就使用了云开发中的 云数据库 功能,紧接着我们就要开始使用 云存储 和 云函数了!
487 |
488 | #### 3.4 addPoster 与 云存储
489 |
490 | 发送新的迷你微博,需要一个编辑新迷你微博的界面,路径我定为 `pages/circle/add-poster/add-poster`:
491 |
492 | ```html
493 |
494 |
495 |
496 |
497 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
510 |
511 | ```
512 |
513 | wxml 的代码很好理解:`textarea` 显示编辑文本,`image-wrapper` 显示需要上传的图片,最下面是一个发送的 `button`。其中,图片编辑区域的 `bindtap` 事件实现:
514 |
515 | ```js
516 | onImageTap: function() {
517 | let that = this
518 | wx.chooseImage({
519 | count: 1,
520 | success: function(res) {
521 | const tempFilePaths = res.tempFilePaths
522 | that.setData({
523 | imageSrc: tempFilePaths[0]
524 | })
525 | }
526 | })
527 | }
528 | ```
529 |
530 | 直接通过 `wx.chooseImage` 官方 API 获取本地图片的临时路径即可。而当发送按钮点击后,会有如下代码被执行:
531 |
532 | ```js
533 | onSendTap: function() {
534 | if (this.data.text === "" && this.data.imageSrc === "") {
535 | wx.showModal({
536 | title: "错误",
537 | content: "不能发送空内容",
538 | showCancel: false,
539 | confirmText: "好的"
540 | })
541 | return
542 | }
543 | const that = this
544 | wx.showLoading({
545 | title: "发送中",
546 | mask: true
547 | })
548 | const imageSrc = this.data.imageSrc
549 | if (imageSrc !== "") {
550 | const finalPath = imageSrc.replace("//", "/").replace(":", "")
551 | wx.cloud
552 | .uploadFile({
553 | cloudPath: finalPath,
554 | filePath: imageSrc // 文件路径
555 | })
556 | .then(res => {
557 | that.sendToDb(res.fileID)
558 | })
559 | .catch(error => {
560 | that.onSendFail()
561 | })
562 | } else {
563 | that.sendToDb()
564 | }
565 | },
566 | sendToDb: function(fileId = "") {
567 | const that = this
568 | const posterData = {
569 | authorId: that.data.userId,
570 | msg: that.data.text,
571 | photoId: fileId,
572 | date: db.serverDate()
573 | }
574 | db.collection("poster")
575 | .add({
576 | data: {
577 | ...posterData
578 | }
579 | })
580 | .then(res => {
581 | wx.showToast({
582 | title: "发送成功"
583 | })
584 | wx.navigateBack({
585 | delta: 1
586 | })
587 | })
588 | .catch(error => {
589 | that.onSendFail()
590 | })
591 | .finally(wx.hideLoading())
592 | }
593 | ```
594 |
595 | 1. 首先判断文本和图片内容是否都为空,如果是则不执行发送,如果不是继续 2
596 | 2. 提示发送中,上传图片到云存储,注意需要将图片中的临时 url 的一些特殊字符组合替换一下,原因见 [文件名命名限制](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/naming.html)
597 | 3. 上传成功后,调用 `db.collection().add()`,发送成功后退回上一页(即首页),如果失败则执行 `onSendFail` 函数,后者见源码,逻辑较简单这里不赘述
598 |
599 | 于是,我们就这样创建了第一条迷你微博。接下来就让它在 Feed 流中显示吧!
600 |
601 | #### 3.5 云函数 getMainPageData
602 |
603 | 这个函数的主要作用如前所述,就是通过处理云数据库中的数据,将最终数据返回给客户端,后者将数据可视化给用户。我们先做一个初步版本,因为现在 `poster_users` 中只有一条数据,所以仅先展示自己的迷你微博。`getMainPageData` 云函数代码如下:
604 |
605 | ```js
606 | // 云函数入口文件
607 | const cloud = require("wx-server-sdk")
608 | cloud.init()
609 | const db = cloud.database()
610 |
611 | // 云函数入口函数
612 | exports.main = async (event, context, cb) => {
613 | // 通过 event 获取入参
614 | const userId = event.userId
615 | let followingResult
616 | let users
617 | // idNameMap 负责存储 userId 和 name 的映射关系
618 | let idNameMap = {}
619 | let followingIds = []
620 | // 获取用户信息
621 | followingResult = await db
622 | .collection("poster_users")
623 | .where({
624 | userId: userId
625 | })
626 | .get()
627 | users = followingResult.data
628 | followingIds = users.map(u => {
629 | return u.userId
630 | })
631 | users.map(u => {
632 | idNameMap[u.userId] = u.name
633 | })
634 | // 获取动态
635 | const postResult = await db
636 | .collection("poster")
637 | .orderBy("date", "desc")
638 | .where({
639 | // 通过高级筛选功能筛选出符合条件的 userId
640 | authorId: db.command.in(followingIds)
641 | })
642 | .get()
643 | const postData = postResult.data
644 | // 向返回的数据添加 存储用户昵称的 author 属性、存储格式化后的时间的 formatDate 属性
645 | postData.map(p => {
646 | p.author = idNameMap[p.authorId]
647 | p.formatDate = new Date(p.date).toLocaleDateString("zh-Hans", options)
648 | })
649 | return postData
650 | }
651 | ```
652 |
653 | 最后在 `pages/circle/circle.js` 里补充云调用:
654 |
655 | ```js
656 | getMainPageData: function(userId) {
657 | const that = this
658 | wx.cloud
659 | .callFunction({
660 | name: "getMainPageData",
661 | data: {
662 | userId: userId,
663 | isEveryOne: that.data.groupArrayIndex === 0 ? false : true
664 | }
665 | })
666 | .then(res => {
667 | that.setData({
668 | pageMainData: res.result,
669 | pageMainLoaded: true
670 | })
671 | })
672 | .catch(err => {
673 | wx.showToast({
674 | title: "获取动态失败",
675 | image: "/images/error.png"
676 | })
677 | wx.hideLoading()
678 | })
679 | }
680 | ```
681 |
682 | 即可展示 Feed 流数据给用户。
683 |
684 | 之后,`getMainPageData` 还会根据使用场景的不同,新增了查询所有用户动态、查询关注用户动态的功能,但是原理是一样的,看源码可以轻易理解,后续就不再说明。
685 |
686 | ### 4. 关注系统
687 |
688 | 上一节中我们一口气把云开发中的大部分主要功能:云数据库、云存储、云函数、云调用都用了一遍,接下来其他功能的实现也基本都依赖它们。
689 |
690 | #### 4.1 poster_user_follows
691 |
692 | 首先我们需要建一个新的 collection `poster_user_follows`,其中的每一项数据的数据结构如下:
693 |
694 | ```json
695 | {
696 | "followerId": "xxx",
697 | "followingId": "xxx"
698 | }
699 | ```
700 |
701 | 很简单,`followerId` 表示关注人,`followingId` 表示被关注人。
702 |
703 | #### 4.2 user-data 页面
704 |
705 | 关注或者取消关注需要进入他人的个人主页操作,我们在 `pages/circle/user-data/user-data.wxml` 中放一个 `user-info` 的自定义组件,然后新建该组件编辑:
706 |
707 | ```html
708 |
709 | 用户名: {{userName}}
710 | 动态数: {{posterCount}}
711 | 关注数: {{followingCount}}
712 | 粉丝数: {{followerCount}}
713 |
714 |
715 | ```
716 |
717 | 这里注意条件渲染的 `button`:如果当前访问个人主页的用户 id (originId) 和 被访问的用户 id (userId)的值是相等的话,这个按钮就不会被渲染(自己不能关注/取消关注自己)。
718 |
719 | 我们重点看下 `onFollowTap` 的实现:
720 |
721 | ```js
722 | onFollowTap: function() {
723 | const that = this
724 | // 判断当前关注状态
725 | if (this.data.isFollow) {
726 | wx.showLoading({
727 | title: "操作中",
728 | mask: true
729 | })
730 | wx.cloud
731 | .callFunction({
732 | name: "cancelFollowing",
733 | data: {
734 | followerId: this.properties.originId,
735 | followingId: this.properties.userId
736 | }
737 | })
738 | .then(res => {
739 | wx.showToast({
740 | title: "取消关注成功"
741 | })
742 | that.setData({
743 | isFollow: false,
744 | followText: "关注"
745 | })
746 | })
747 | .catch(error => {
748 | wx.showToast({
749 | title: "取消关注失败",
750 | image: "/images/error.png"
751 | })
752 | })
753 | .finally(wx.hideLoading())
754 | } else if (this.data.isFollow !== undefined) {
755 | wx.showLoading({
756 | title: "操作中",
757 | mask: true
758 | })
759 | const data = {
760 | followerId: this.properties.originId,
761 | followingId: this.properties.userId
762 | }
763 | db.collection("poster_user_follows")
764 | .add({
765 | data: {
766 | ...data
767 | }
768 | })
769 | .then(res => {
770 | wx.showToast({
771 | title: "关注成功"
772 | })
773 | that.setData({
774 | isFollow: true,
775 | followText: "取消关注"
776 | })
777 | })
778 | .catch(error => {
779 | wx.showToast({
780 | title: "关注失败",
781 | image: "/images/error.png"
782 | })
783 | })
784 | .finally(wx.hideLoading())
785 | }
786 | }
787 | }
788 | ```
789 |
790 | 这里读者可能会有疑问:为什么关注的时候直接调用 `db.collection().add()` 即可,而取消关注却要调用云函数呢?这里涉及到云数据库的设计问题:删除多个数据的操作,或者说删除使用 `where` 筛选的数据,只能在服务端执行。如果确实想在客户端删除,则在查询用户关系时,将唯一标识数据的 `_id` 用 `setData` 存下来,之后再使用 `db.collection().doc(_id).delete()` 删除即可。这两种实现方式读者可自行选择。当然,还有一种实现是不实际删除数据,只是加个 `isDelete` 字段标记一下。
791 |
792 | 查询用户关系的实现很简单,云函数的实现方式如下:
793 |
794 | ```js
795 | // 云函数入口文件
796 | const cloud = require('wx-server-sdk')
797 | cloud.init()
798 | const db = cloud.database()
799 |
800 | // 云函数入口函数
801 | exports.main = async(event, context) => {
802 | const followingResult = await db.collection("poster_user_follows")
803 | .where({
804 | followingId: event.followingId,
805 | followerId: event.followerId
806 | }).get()
807 | return followingResult
808 | }
809 | ```
810 |
811 | 客户端只要检查返回的数据长度是否大于 0 即可。
812 |
813 | 另外附上 `user-data` 页面其他数据的获取云函数实现:
814 |
815 | ```js
816 | // 云函数入口文件
817 | const cloud = require("wx-server-sdk")
818 | cloud.init()
819 | const db = cloud.database()
820 |
821 | async function getPosterCount(userId) {
822 | return {
823 | value: (await db.collection("poster").where({
824 | authorId: userId
825 | }).count()).total,
826 | key: "posterCount"
827 | }
828 | }
829 |
830 | async function getFollowingCount(userId) {
831 | return {
832 | value: (await db.collection("poster_user_follows").where({
833 | followerId: userId
834 | }).count()).total,
835 | key: "followingCount"
836 | }
837 | }
838 |
839 | async function getFollowerCount(userId) {
840 | return {
841 | value: (await db.collection("poster_user_follows").where({
842 | followingId: userId
843 | }).count()).total,
844 | key: "followerCount"
845 | }
846 | }
847 |
848 |
849 | async function getUserName(userId) {
850 | return {
851 | value: (await db.collection("poster_users").where({
852 | userId: userId
853 | }).get()).data[0].name,
854 | key: "userName"
855 | }
856 | }
857 |
858 | // 云函数入口函数
859 | exports.main = async (event, context) => {
860 | const userId = event.userId
861 | const tasks = []
862 | tasks.push(getPosterCount(userId))
863 | tasks.push(getFollowerCount(userId))
864 | tasks.push(getFollowingCount(userId))
865 | tasks.push(getUserName(userId))
866 | const allData = await Promise.all(tasks)
867 | const finalData = {}
868 | allData.map(d => {
869 | finalData[d.key] = d.value
870 | })
871 | return finalData
872 | }
873 | ```
874 |
875 | 很好理解,客户端获取返回后直接使用即可。
876 |
877 | ### 5. 搜索页面
878 |
879 | 这部分其实很好实现。关键的搜索函数实现如下:
880 |
881 | ```js
882 | // 云函数入口文件
883 | const cloud = require('wx-server-sdk')
884 | cloud.init()
885 | const db = cloud.database()
886 |
887 | const MAX_LIMIT = 100
888 | async function getDbData(dbName, whereObj) {
889 | const totalCountsData = await db.collection(dbName).where(whereObj).count()
890 | const total = totalCountsData.total
891 | const batch = Math.ceil(total / 100)
892 | const tasks = []
893 | for (let i = 0; i < batch; i++) {
894 | const promise = db
895 | .collection(dbName)
896 | .where(whereObj)
897 | .skip(i * MAX_LIMIT)
898 | .limit(MAX_LIMIT)
899 | .get()
900 | tasks.push(promise)
901 | }
902 | const rrr = await Promise.all(tasks)
903 | if (rrr.length !== 0) {
904 | return rrr.reduce((acc, cur) => {
905 | return {
906 | data: acc.data.concat(cur.data),
907 | errMsg: acc.errMsg
908 | }
909 | })
910 | } else {
911 | return {
912 | data: [],
913 | errMsg: "empty"
914 | }
915 | }
916 | }
917 |
918 | // 云函数入口函数
919 | exports.main = async (event, context) => {
920 | const text = event.text
921 | const data = await getDbData("poster_users", {
922 | name: {
923 | $regex: text
924 | }
925 | })
926 | return data
927 | }
928 | ```
929 |
930 | 这里参考了官网所推荐的分页检索数据库数据的实现(因为搜索结果可能有很多),筛选条件则是正则模糊匹配关键字。
931 |
932 | 搜索页面的源码路径是 `pages/circle/search-user/search-user`,实现了点击搜索结果项跳转到对应项的用户的 `user-data` 页面,建议直接阅读源码理解。
933 |
934 | ### 6. 其他扩展
935 |
936 | #### 6.1 poster_likes 与 点赞
937 |
938 | 由于转发、评论、点赞的原理基本相同,所以这里只介绍点赞功能如何编写,另外两个功能读者可以自行实现。
939 |
940 | 毫无疑问我们需要新建一个 collection `poster_likes`,其中每一项的数据结构如下:
941 |
942 | ```json
943 | {
944 | "posterId": "xxx",
945 | "likeId": "xxx"
946 | }
947 | ```
948 |
949 | 这里的 `posterId` 就是 `poster` collection 里每条记录的 `_id` 值,`likeId` 就是 `poster_users` 里的 `userId` 了。
950 |
951 | 然后我们扩展一下 `poster-item` 的实现:
952 |
953 | ```html
954 |
955 | ...
956 |
957 |
958 |
959 |
960 |
961 |
962 | ```
963 |
964 | 即,新增一个 `interact-area`,其中 `onLikeTap` 实现如下:
965 |
966 | ```js
967 | onLikeTap: function() {
968 | if (!this.properties.originId) return
969 | const that = this
970 | if (this.data.liked) {
971 | wx.showLoading({
972 | title: "操作中",
973 | mask: true
974 | })
975 | wx.cloud
976 | .callFunction({
977 | name: "cancelLiked",
978 | data: {
979 | posterId: this.properties.data._id,
980 | likeId: this.properties.originId
981 | }
982 | })
983 | .then(res => {
984 | wx.showToast({
985 | title: "取消成功"
986 | })
987 | that.refreshLike()
988 | that.triggerEvent('likeEvent');
989 | })
990 | .catch(error => {
991 | wx.showToast({
992 | title: "取消失败",
993 | image: "/images/error.png"
994 | })
995 | })
996 | .finally(wx.hideLoading())
997 | } else {
998 | wx.showLoading({
999 | title: "操作中",
1000 | mask: true
1001 | })
1002 | db.collection("poster_likes").add({
1003 | data: {
1004 | posterId: this.properties.data._id,
1005 | likeId: this.properties.originId
1006 | }
1007 | }).then(res => {
1008 | wx.showToast({
1009 | title: "已赞"
1010 | })
1011 | that.refreshLike()
1012 | that.triggerEvent('likeEvent');
1013 | })
1014 | .catch(error => {
1015 | wx.showToast({
1016 | title: "赞失败",
1017 | image: "/images/error.png"
1018 | })
1019 | })
1020 | .finally(wx.hideLoading())
1021 | }
1022 |
1023 | }
1024 | ```
1025 |
1026 | 细心的读者会发现这和关注功能原理几乎是一样的。
1027 |
1028 | #### 6.2 数据刷新
1029 |
1030 | 我们可以使用很多方式让主页面刷新数据:
1031 |
1032 | ```js
1033 | onShow: function() {
1034 | wx.showLoading({
1035 | title: "加载中",
1036 | mask: true
1037 | })
1038 | const that = this
1039 | function cb(userId) {
1040 | that.refreshMainPageData(userId)
1041 | that.refreshMePageData(userId)
1042 | }
1043 | this.getUserId(cb)
1044 | }
1045 | ```
1046 |
1047 | 第一种是利用 `onShow` 方法:它会在页面每次从后台转到前台展示时调用,这个时候我们就能刷新页面数据(包括 Feed 流和个人信息)。但是这个时候用户信息可能会丢失,所以我们需要在 `getUserId` 里判断,并将刷新数据的函数们整合起来,作为回调函数。
1048 |
1049 | 第二种是让用户手动刷新:
1050 |
1051 | ```js
1052 | onPageMainTap: function() {
1053 | if (this.data.currentPage === "main") {
1054 | this.refreshMainPageData()
1055 | }
1056 | this.setData({
1057 | currentPage: "main"
1058 | })
1059 | }
1060 | ```
1061 |
1062 | 如图所示,当目前页面是 Feed 流时,如果再次点击 首页 Tab,就会强制刷新数据。
1063 |
1064 | 第三种是关联数据变更触发刷新,比如动态类型选择、删除了一条动态以后触发数据的刷新。这种可以直接看源码学习。
1065 |
1066 | #### 6.3 首次加载等待
1067 |
1068 | 当用户第一次进入主页面时,我们如果想在 Feed 流和个人信息都加载好了再允许用户操作,应该如何实现?
1069 |
1070 | 如果是类似 Vue 或者 React 的框架,我们很容易就能想到属性监控,如 `watch`、`useEffect` 等等,但是小程序目前 `Page` 并没有提供属性监控功能,怎么办?
1071 |
1072 | 除了自己实现,还有一个方法就是利用 `Component` 的 `observers`,它和上面提到的属性监控功能差不多。虽然官网文档对其说明比较少,但摸索了一番还是能用来监控的。
1073 |
1074 | 首先我们来新建一个 `Component` 叫 `abstract-load`,具体实现如下:
1075 |
1076 | ```js
1077 | // pages/circle/component/abstract-load.js
1078 | Component({
1079 | properties: {
1080 | pageMainLoaded: {
1081 | type: Boolean,
1082 | value: false
1083 | },
1084 | pageMeLoaded: {
1085 | type: Boolean,
1086 | value: false
1087 | }
1088 | },
1089 | observers: {
1090 | "pageMainLoaded, pageMeLoaded": function (pageMainLoaded, pageMeLoaded) {
1091 | if (pageMainLoaded && pageMeLoaded) {
1092 | this.triggerEvent("allLoadEvent")
1093 | }
1094 | }
1095 | }
1096 | })
1097 | ```
1098 |
1099 | 然后在 `pages/circle/circle.wxml` 中添加一行:
1100 |
1101 | ```html
1102 |
1103 | ```
1104 |
1105 | 最后实现 `onAllLoad` 函数即可。
1106 |
1107 | 另外,像这种没有实际展示数据的 `Component`,建议在项目中都用 `abstract` 开头来命名。
1108 |
1109 | #### 6.4 scroll-view 在 iOS 的 bug
1110 |
1111 | 如果读者使用 iOS 系统调试这个小程序,可能会发现 Feed 流比较短的时候,滚动 `scroll-view` header 和 `button` 会有鬼畜的上下抖动现象,这是因为 iOS 自己实现的 WebView 对于滚动视图有回弹的效果,而该效果也会触发滚动事件。
1112 |
1113 | 对于这个 bug,[官方人员也表示暂时无法修复](https://developers.weixin.qq.com/community/develop/doc/00064a122e01782f0d583fc0151c00),只能先忍一忍了。
1114 |
1115 | #### 6.5 关于消息 Tab
1116 |
1117 | 读者可能会疑惑我为什么没有讲解消息 Tab 以及消息提醒的实现。首先是因为源码没有这个实现,其次是我觉得目前云开发所提供的能力实现主动提醒比较麻烦(除了轮询想不到其他办法)。
1118 |
1119 | 希望未来云开发可以提供 ***数据库长连接监控*** 的功能,这样通过订阅者模式可以很轻松地获取到数据更新的状态,主动提醒也就更容易实现了。到那时我可能会再更新相关源码。
1120 |
1121 | #### 6.6 关于云函数耗时
1122 |
1123 | 读者可能会发现我有一个叫 `benchmark` 的云函数,这个函数只是做了个查询数据库的操作,目的在于计算查询耗时。
1124 |
1125 | 诡异的是,我前天在调试的时候,发现查询一次需要1秒钟,而写这篇文章时却不到100ms。建议在一些需要多次操作数据库的函数配置里,把超时时间设置长一点吧。目前云函数的性能不太稳定。
1126 |
1127 | ### 7. 结语
1128 |
1129 | 那么关于迷你版微博开发实战介绍就到此为止了,更多资料可以直接下载源码查看哦。
1130 |
--------------------------------------------------------------------------------
/miniprogram/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 |
5 | if (!wx.cloud) {
6 | console.error('请使用 2.2.3 或以上的基础库以使用云能力')
7 | } else {
8 | wx.cloud.init({
9 | traceUser: true,
10 | })
11 | }
12 |
13 | this.globalData = {}
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/miniprogram/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/circle/circle",
4 | "pages/index/index",
5 | "pages/circle/search-user/search-user",
6 | "pages/circle/user-poster/user-poster",
7 | "pages/circle/add-poster/add-poster",
8 | "pages/circle/user-data/user-data",
9 | "pages/circle/user-follower/user-follower",
10 | "pages/circle/user-following/user-following",
11 | "pages/circle/poster-detail/poster-detail"
12 | ],
13 | "window": {
14 | "backgroundColor": "#F6F6F6",
15 | "backgroundTextStyle": "light",
16 | "navigationBarBackgroundColor": "#F6F6F6",
17 | "navigationBarTitleText": "云开发 QuickStart",
18 | "navigationBarTextStyle": "black"
19 | },
20 | "sitemapLocation": "sitemap.json",
21 | "networkTimeout": {
22 | "request": 30000
23 | }
24 | }
--------------------------------------------------------------------------------
/miniprogram/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | box-sizing: border-box;
7 | }
8 |
9 | button {
10 | background: initial;
11 | }
12 |
13 | button:focus{
14 | outline: 0;
15 | }
16 |
17 | button::after{
18 | border: none;
19 | }
--------------------------------------------------------------------------------
/miniprogram/component/image-wrapper/image-wrapper.js:
--------------------------------------------------------------------------------
1 | // pages/circle/component/image-wrapper/image-wrapper.js
2 | Component({
3 | lifetimes: {
4 | attached: function() {
5 | const p = this.properties.placeholder
6 | this.setData({
7 | placeText: p
8 | })
9 | }
10 | },
11 | /**
12 | * Component properties
13 | */
14 | properties: {
15 | src: {
16 | type: String,
17 | value: ""
18 | },
19 | placeholder: {
20 | type: String,
21 | value: "加载中"
22 | }
23 | },
24 |
25 | /**
26 | * Component initial data
27 | */
28 | data: {
29 | isShowPlaceholdler: true,
30 | placeText: "",
31 | },
32 |
33 | /**
34 | * Component methods
35 | */
36 | methods: {
37 | onImageLoad: function () {
38 | this.setData({
39 | isShowPlaceholdler: false,
40 | })
41 | },
42 | onImageError: function() {
43 | this.setData({
44 | placeText: "加载失败"
45 | })
46 | }
47 | }
48 | })
49 |
--------------------------------------------------------------------------------
/miniprogram/component/image-wrapper/image-wrapper.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/miniprogram/component/image-wrapper/image-wrapper.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{placeText}}
6 |
7 |
--------------------------------------------------------------------------------
/miniprogram/component/image-wrapper/image-wrapper.wxss:
--------------------------------------------------------------------------------
1 | /* pages/circle/component/image-wrapper/image-wrapper.wxss */
2 |
3 | .image-wrapper {
4 | display: inline-block;
5 | position: relative;
6 | width: 100%;
7 | float: left;
8 | font-size: 12px;
9 | background: #aaa;
10 | }
11 |
12 | .image-wrapper:before {
13 | content: "";
14 | display: block;
15 | padding-top: 100%;
16 | }
17 |
18 | .image-inner {
19 | position: absolute;
20 | width: 100%;
21 | height: 100%;
22 | top: 0;
23 | left: 0;
24 | bottom: 0;
25 | right: 0;
26 | padding: 0;
27 | text-align: center;
28 | vertical-align: center;
29 | text-transform: uppercase;
30 | color: #fff;
31 | }
32 |
33 | .placeholder {
34 | position: absolute;
35 | width: 100%;
36 | height: 100%;
37 | top: 0;
38 | left: 0;
39 | bottom: 0;
40 | right: 0;
41 | padding: 0;
42 | display: flex;
43 | justify-content: center;
44 | align-items: center;
45 | color: #fff;
46 | }
47 |
--------------------------------------------------------------------------------
/miniprogram/images/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KuthorX/KuthorX-Helper/03ac0aba45ac1b44b6f485a0eb2d8e340c1ae49d/miniprogram/images/error.png
--------------------------------------------------------------------------------
/miniprogram/pages/circle/add-poster/add-poster.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/add-poster/add-poster.js
2 |
3 | wx.cloud.init()
4 | const db = wx.cloud.database()
5 |
6 | Page({
7 | /**
8 | * Page initial data
9 | */
10 | data: {
11 | userId: undefined,
12 | text: "",
13 | remainLen: 140,
14 | imageSrc: "",
15 | clickable: true
16 | },
17 |
18 | bindTextInput: function(e) {
19 | const t = e.detail.value
20 | const len = e.detail.value.length
21 | const r = 140 - len
22 | this.setData({
23 | text: e.detail.value,
24 | remainLen: r
25 | })
26 | },
27 |
28 | sendToDb: function(fileId = "") {
29 | const that = this
30 | const posterData = {
31 | authorId: that.data.userId,
32 | msg: that.data.text,
33 | photoId: fileId,
34 | date: db.serverDate()
35 | }
36 | db.collection("poster")
37 | .add({
38 | data: {
39 | ...posterData
40 | }
41 | })
42 | .then(res => {
43 | wx.showToast({
44 | title: "发送成功"
45 | })
46 | wx.navigateBack({
47 | delta: 1
48 | })
49 | })
50 | .catch(error => {
51 | that.onSendFail()
52 | })
53 | .finally(wx.hideLoading())
54 | },
55 |
56 | onSendFail: function() {
57 | wx.hideLoading()
58 | wx.showToast({
59 | title: "发送失败",
60 | image: "/images/error.png"
61 | })
62 | this.setData({
63 | clickable: true
64 | })
65 | },
66 |
67 | onSendTap: function() {
68 | if (this.data.text === "" && this.data.imageSrc === "") {
69 | wx.showModal({
70 | title: "错误",
71 | content: "不能发送空内容",
72 | showCancel: false,
73 | confirmText: "好的"
74 | })
75 | return
76 | }
77 | if (!this.data.clickable) return
78 | this.setData({
79 | clickable: false
80 | })
81 | const that = this
82 | wx.showLoading({
83 | title: "发送中",
84 | mask: true
85 | })
86 | const imageSrc = this.data.imageSrc
87 | if (imageSrc !== "") {
88 | const finalPath = imageSrc.replace("//", "/").replace(":", "")
89 | wx.cloud
90 | .uploadFile({
91 | cloudPath: finalPath,
92 | filePath: imageSrc // 文件路径
93 | })
94 | .then(res => {
95 | that.sendToDb(res.fileID)
96 | })
97 | .catch(error => {
98 | that.onSendFail()
99 | })
100 | } else {
101 | that.sendToDb()
102 | }
103 | },
104 |
105 | onImageTap: function() {
106 | let that = this
107 | wx.chooseImage({
108 | count: 1,
109 | success: function(res) {
110 | const tempFilePaths = res.tempFilePaths
111 | that.setData({
112 | imageSrc: tempFilePaths[0]
113 | })
114 | }
115 | })
116 | },
117 |
118 | /**
119 | * Lifecycle function--Called when page load
120 | */
121 | onLoad: function(options) {
122 | wx.setNavigationBarTitle({
123 | title: "编辑新动态"
124 | })
125 | try {
126 | var value = wx.getStorageSync("userId")
127 | if (!value) {
128 | wx.showToast({
129 | title: "获取用户信息失败,请重新授权登陆",
130 | image: "/images/error.png"
131 | })
132 | wx.navigateTo({
133 | url: "/pages/index/index"
134 | })
135 | } else {
136 | this.setData({
137 | userId: value
138 | })
139 | }
140 | } catch (e) {
141 | wx.showToast({
142 | title: "获取用户信息失败,请重新授权登陆",
143 | image: "/images/error.png"
144 | })
145 | wx.navigateTo({
146 | url: "/pages/index/index"
147 | })
148 | }
149 | },
150 |
151 | /**
152 | * Lifecycle function--Called when page is initially rendered
153 | */
154 | onReady: function() {},
155 |
156 | /**
157 | * Lifecycle function--Called when page show
158 | */
159 | onShow: function() {},
160 |
161 | /**
162 | * Lifecycle function--Called when page hide
163 | */
164 | onHide: function() {},
165 |
166 | /**
167 | * Lifecycle function--Called when page unload
168 | */
169 | onUnload: function() {},
170 |
171 | /**
172 | * Page event handler function--Called when user drop down
173 | */
174 | onPullDownRefresh: function() {},
175 |
176 | /**
177 | * Called when page reach bottom
178 | */
179 | onReachBottom: function() {},
180 |
181 | /**
182 | * Called when user click on the top right corner to share
183 | */
184 | onShareAppMessage: function() {}
185 | })
186 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/add-poster/add-poster.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "image-wrapper": "/component/image-wrapper/image-wrapper"
4 | }
5 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/add-poster/add-poster.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/add-poster/add-poster.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/add-poster/add-poster.wxss */
2 |
3 | .app-poster-container {
4 | width: 100%;
5 | height: 100%;
6 | }
7 |
8 | .body {
9 | position: relative;
10 | flex-grow: 1;
11 | height: calc(100vh - 120rpx);
12 | overflow: auto;
13 | z-index: 1;
14 | }
15 |
16 | .text-area-wrapper {
17 | padding: 30rpx 20rpx;
18 | height: 230rpx;
19 | }
20 |
21 | textarea {
22 | width: 100%;
23 | height: 100%;
24 | }
25 |
26 | .text-area-footer {
27 | font-size: 20rpx;
28 | color: #aaa;
29 | text-align: end;
30 | padding-right: 20rpx;
31 | }
32 |
33 | .image-area {
34 | padding: 30rpx 20rpx;
35 | }
36 |
37 | .image-outer {
38 | width: 33%;
39 | }
40 |
41 | .footer {
42 | border-top: 3rpx solid #aaa;
43 | display: flex;
44 | position: fixed;
45 | height: 120rpx;
46 | bottom: 0;
47 | width: 100%;
48 | flex-direction: row;
49 | align-items: center;
50 | justify-content: space-around;
51 | z-index: 100;
52 | }
53 |
54 | .footer-btn {
55 | font-size: 30rpx;
56 | display: flex;
57 | align-items: center;
58 | justify-content: center;
59 | width: 100%;
60 | height: 100%;
61 | border-radius: 0;
62 | }
63 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/circle.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle.js
2 |
3 | wx.cloud.init()
4 | const db = wx.cloud.database()
5 |
6 | Page({
7 | /**
8 | * Page initial data
9 | */
10 | data: {
11 | pageMainLoaded: false,
12 | pageMeLoaded: false,
13 | userId: undefined,
14 | addPosterBtnBottom: "190rpx",
15 | mainHeaderMaxHeight: "80rpx",
16 | mainAreaHeight: "calc(100vh - 200rpx)",
17 | mainAreaMarginTop: "80rpx",
18 | groupArray: ["关注动态", "所有动态"],
19 | groupArrayIndex: 0,
20 | // main, msg, me
21 | currentPage: "main",
22 | pageMainData: [],
23 | userName: "Loading",
24 | posterCount: "Loading",
25 | followingCount: "Loading",
26 | followerCount: "Loading"
27 | },
28 |
29 | onItemDelete: function() {
30 | this.refreshMainPageData()
31 | },
32 |
33 | onAddPosterTap: function() {
34 | wx.navigateTo({
35 | url: "add-poster/add-poster"
36 | })
37 | },
38 |
39 | onMainPageScroll: function(e) {
40 | if (e.detail.deltaY < 0) {
41 | this.setData({
42 | addPosterBtnBottom: "-190rpx",
43 | mainHeaderMaxHeight: "0",
44 | mainAreaHeight: "calc(100vh - 120rpx)",
45 | mainAreaMarginTop: "0rpx"
46 | })
47 | } else {
48 | this.setData({
49 | addPosterBtnBottom: "190rpx",
50 | mainHeaderMaxHeight: "80rpx",
51 | mainAreaHeight: "calc(100vh - 200rpx)",
52 | mainAreaMarginTop: "80rpx"
53 | })
54 | }
55 | },
56 |
57 | bindGroupPickerChange: function(e) {
58 | const that = this
59 | this.setData(
60 | {
61 | groupArrayIndex: e.detail.value
62 | },
63 | function() {
64 | that.refreshMainPageData()
65 | }
66 | )
67 | },
68 |
69 | onSearchTap: function() {
70 | wx.navigateTo({
71 | url: "/pages/circle/search-user/search-user?userId=" + this.data.userId
72 | })
73 | },
74 |
75 | onPageMainTap: function() {
76 | if (this.data.currentPage === "main") {
77 | this.refreshMainPageData()
78 | }
79 | this.setData({
80 | currentPage: "main"
81 | })
82 | },
83 |
84 | onPageMsgTap: function() {
85 | this.setData({
86 | currentPage: "msg"
87 | })
88 | },
89 |
90 | onPageMeTap: function() {
91 | if (this.data.currentPage === "me") {
92 | this.refreshMePageData()
93 | }
94 | this.setData({
95 | currentPage: "me"
96 | })
97 | },
98 |
99 | getUserId: function(cb) {
100 | let that = this
101 | var value = this.data.userId || wx.getStorageSync("userId")
102 | console.log(value)
103 | if (value) {
104 | if (cb) {
105 | cb(value)
106 | }
107 | return value
108 | }
109 | wx.getSetting({
110 | success(res) {
111 | if (res.authSetting["scope.userInfo"]) {
112 | wx.getUserInfo({
113 | withCredentials: true,
114 | success: function(userData) {
115 | wx.setStorageSync("userId", userData.signature)
116 | that.setData({
117 | userId: userData.signature
118 | })
119 | db.collection("poster_users")
120 | .where({
121 | userId: userData.signature
122 | })
123 | .get()
124 | .then(searchResult => {
125 | if (searchResult.data.length === 0) {
126 | wx.showToast({
127 | title: "新用户录入中"
128 | })
129 | db.collection("poster_users")
130 | .add({
131 | data: {
132 | userId: userData.signature,
133 | date: db.serverDate(),
134 | name: userData.userInfo.nickName,
135 | gender: userData.userInfo.gender
136 | }
137 | })
138 | .then(res => {
139 | console.log(res)
140 | if (res.errMsg === "collection.add:ok") {
141 | wx.showToast({
142 | title: "录入完成"
143 | })
144 | if (cb) cb()
145 | }
146 | })
147 | .catch(err => {
148 | wx.showToast({
149 | title: "录入失败,请稍后重试",
150 | image: "/images/error.png"
151 | })
152 | wx.navigateTo({
153 | url: "/pages/index/index"
154 | })
155 | })
156 | } else {
157 | if (cb) cb()
158 | }
159 | })
160 | }
161 | })
162 | } else {
163 | wx.showToast({
164 | title: "登陆失效,请重新授权登陆",
165 | image: "/images/error.png"
166 | })
167 | wx.navigateTo({
168 | url: "/pages/index/index"
169 | })
170 | }
171 | }
172 | })
173 | },
174 |
175 | onAllLoad: function() {
176 | wx.hideLoading()
177 | },
178 |
179 | getMainPageData: function(userId) {
180 | const that = this
181 | wx.cloud
182 | .callFunction({
183 | name: "getMainPageData",
184 | data: {
185 | userId: userId,
186 | isEveryOne: that.data.groupArrayIndex === 0 ? false : true
187 | }
188 | })
189 | .then(res => {
190 | that.setData({
191 | pageMainData: res.result,
192 | pageMainLoaded: true
193 | })
194 | })
195 | .catch(err => {
196 | wx.showToast({
197 | title: "获取动态失败",
198 | image: "/images/error.png"
199 | })
200 | wx.hideLoading()
201 | })
202 | },
203 |
204 | refreshMainPageData: function(userId) {
205 | try {
206 | userId = userId || this.data.userId || wx.getStorageSync("userId")
207 | if (!userId) {
208 | throw Error
209 | }
210 | wx.showLoading({
211 | title: "加载中",
212 | mask: true
213 | })
214 | this.getMainPageData(userId)
215 | } catch (e) {
216 | console.log(e)
217 | wx.hideLoading()
218 | wx.showToast({
219 | title: "获取用户信息失败,请重新授权登陆",
220 | image: "/images/error.png"
221 | })
222 | wx.navigateTo({
223 | url: "/pages/index/index"
224 | })
225 | }
226 | },
227 |
228 | getMePageData: function(userId) {
229 | const that = this
230 | wx.cloud
231 | .callFunction({
232 | name: "getMePageData",
233 | data: {
234 | userId: userId
235 | }
236 | })
237 | .then(res => {
238 | that.setData({
239 | ...res.result,
240 | pageMeLoaded: true
241 | })
242 | })
243 | .catch(err => {
244 | wx.showToast({
245 | title: "获取信息失败",
246 | image: "/images/error.png"
247 | })
248 | wx.hideLoading()
249 | })
250 | },
251 |
252 | refreshMePageData: function(userId) {
253 | try {
254 | userId = userId || this.data.userId || wx.getStorageSync("userId")
255 | if (!userId) {
256 | throw Error
257 | }
258 | wx.showLoading({
259 | title: "加载中",
260 | mask: true
261 | })
262 | this.getMePageData(userId)
263 | } catch (e) {
264 | wx.hideLoading()
265 | wx.showToast({
266 | title: "获取用户信息失败,请重新授权登陆",
267 | image: "/images/error.png"
268 | })
269 | wx.navigateTo({
270 | url: "/pages/index/index"
271 | })
272 | }
273 | },
274 |
275 | onLoad: function(options) {
276 | wx.setNavigationBarTitle({
277 | title: "小圈圈"
278 | })
279 | },
280 |
281 | /**
282 | * Lifecycle function--Called when page is initially rendered
283 | */
284 | onReady: function() {},
285 |
286 | /**
287 | * Lifecycle function--Called when page show
288 | */
289 | onShow: function() {
290 | wx.showLoading({
291 | title: "加载中",
292 | mask: true
293 | })
294 | const that = this
295 | function cb(userId) {
296 | that.refreshMainPageData(userId)
297 | that.refreshMePageData(userId)
298 | }
299 | this.getUserId(cb)
300 | },
301 |
302 | /**
303 | * Lifecycle function--Called when page hide
304 | */
305 | onHide: function() {},
306 |
307 | /**
308 | * Lifecycle function--Called when page unload
309 | */
310 | onUnload: function() {},
311 |
312 | /**
313 | * Page event handler function--Called when user drop down
314 | */
315 | onPullDownRefresh: function() {},
316 |
317 | /**
318 | * Called when page reach bottom
319 | */
320 | onReachBottom: function() {},
321 |
322 | /**
323 | * Called when user click on the top right corner to share
324 | */
325 | onShareAppMessage: function() {}
326 | })
327 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/circle.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "post-item": "./component/post-item/post-item",
4 | "abstract-load": "./component/abstract-load/abstract-load",
5 | "user-info": "./component/user-info/user-info"
6 | },
7 | "disableScroll": true
8 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/circle.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 无数据
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
49 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/circle.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle.wxss */
2 | @import "./component/post-item/post-item.wxss";
3 | @import "./component/user-info/user-info.wxss";
4 |
5 | .circle-container {
6 | position: relative;
7 | display: flex;
8 | flex-direction: column;
9 | z-index: 1;
10 | }
11 |
12 | .main-area {
13 | position: relative;
14 | flex-grow: 1;
15 | overflow: auto;
16 | z-index: 1;
17 |
18 | transition: height 0.3s, margin-top 0.3s;
19 | }
20 |
21 | .me-area {
22 | overflow-x: hidden;
23 | overflow-y: auto;
24 | width: 100%;
25 | height: calc(100vh - 120rpx);
26 | }
27 |
28 | .user-info-wrapper {
29 | width: 100%;
30 | }
31 |
32 | .main-header .search-btn {
33 | font-size: 30rpx;
34 | width: 100%;
35 | border-radius: 0;
36 | height: 100%;
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | }
41 |
42 | .main-header {
43 | position: fixed;
44 | width: 100%;
45 | height: 80rpx;
46 | background: #fff;
47 | top: 0;
48 | left: 0;
49 | display: flex;
50 | justify-content: space-around;
51 | align-items: center;
52 | z-index: 100;
53 | border-bottom: 3rpx solid #aaa;
54 | transition: max-height 0.3s;
55 | overflow: hidden;
56 | }
57 |
58 | .group-picker-wrapper {
59 | width: 50%;
60 | height: 100%;
61 | display: flex;
62 | justify-content: center;
63 | align-items: center;
64 | }
65 |
66 | .search-btn-wrapper {
67 | width: 50%;
68 | height: 100%;
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | }
73 |
74 | .group-picker {
75 | width: 100%;
76 | height: 100%;
77 | display: flex;
78 | align-items: center;
79 | }
80 |
81 | .group-picker-inner {
82 | width: 50vw;
83 | height: 80rpx;
84 | border-radius: 0;
85 | font-size: 30rpx;
86 | display: flex;
87 | justify-content: center;
88 | align-items: center;
89 | }
90 |
91 | .add-poster-btn {
92 | position: fixed;
93 | right: 60rpx;
94 | box-shadow: 5rpx 5rpx 10rpx #aaa;
95 | display: flex;
96 | justify-content: center;
97 | align-items: center;
98 | color: #333;
99 | padding-bottom: 10rpx;
100 | text-align: center;
101 | border-radius: 50%;
102 | font-size: 60rpx;
103 | width: 100rpx;
104 | height: 100rpx;
105 | transition: bottom 0.3s;
106 | background: #fff;
107 | z-index: 1;
108 | }
109 |
110 | .add-poster-btn-hover {
111 | background: #ddd;
112 | }
113 |
114 | .post-item-wrapper {
115 | display: block;
116 | border-bottom: 3rpx solid #aaa;
117 | }
118 |
119 | .post-item-wrapper:nth-child(n + 2) {
120 | margin: 3rpx 0;
121 | }
122 |
123 | .post-item-wrapper:nth-last-child(1) {
124 | margin-top: 3rpx;
125 | margin-bottom: 0;
126 | border-bottom: 0 solid #aaa;
127 | }
128 |
129 | .main-area-scroll {
130 | width: 100vw;
131 | height: calc(100vh - 120rpx);
132 | }
133 |
134 | .item-placeholder {
135 | width: 100%;
136 | padding: 20rpx 0;
137 | font-size: 20rpx;
138 | color: #aaa;
139 | text-align: center;
140 | }
141 |
142 | .footer {
143 | box-shadow: 0 0 15rpx #ccc;
144 | display: flex;
145 | position: fixed;
146 | height: 120rpx;
147 | bottom: 0;
148 | width: 100%;
149 | flex-direction: row;
150 | justify-content: center;
151 | z-index: 100;
152 | background: #fff;
153 | }
154 |
155 | .footer-item {
156 | display: flex;
157 | justify-content: center;
158 | align-items: center;
159 | height: 100%;
160 | width: 33.33%;
161 | color: #333;
162 | }
163 |
164 | .footer-item:nth-child(2) {
165 | border-left: 3rpx solid #aaa;
166 | border-right: 3rpx solid #aaa;
167 | flex-grow: 1;
168 | }
169 |
170 | .footer-btn {
171 | width: 100%;
172 | height: 100%;
173 | display: flex;
174 | justify-content: center;
175 | align-items: center;
176 | border-radius: 0;
177 | font-size: 30rpx;
178 | }
179 |
180 | .footer-btn-hover {
181 | background: #aaa;
182 | color: #000;
183 | }
184 |
185 | .btn-text {
186 | width: 100%;
187 | height: 100%;
188 | text-align: center;
189 | vertical-align: center;
190 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/abstract-load/abstract-load.js:
--------------------------------------------------------------------------------
1 | // pages/circle/component/abstract-load.js
2 | Component({
3 | /**
4 | * Component properties
5 | */
6 | properties: {
7 | pageMainLoaded: {
8 | type: Boolean,
9 | value: false
10 | },
11 | pageMeLoaded: {
12 | type: Boolean,
13 | value: false
14 | }
15 | },
16 |
17 | /**
18 | * Component initial data
19 | */
20 | data: {},
21 |
22 | observers: {
23 | "pageMainLoaded, pageMeLoaded": function(pageMainLoaded, pageMeLoaded) {
24 | if (pageMainLoaded && pageMeLoaded) {
25 | this.triggerEvent("allLoadEvent")
26 | }
27 | }
28 | },
29 |
30 | /**
31 | * Component methods
32 | */
33 | methods: {}
34 | })
35 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/abstract-load/abstract-load.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/abstract-load/abstract-load.wxml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/abstract-load/abstract-load.wxss:
--------------------------------------------------------------------------------
1 | /* pages/circle/component/abstract-load.wxss */
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/post-item/post-item.js:
--------------------------------------------------------------------------------
1 | // pages/circle/post-item/post-item.js
2 | const db = wx.cloud.database()
3 |
4 | Component({
5 | lifetimes: {
6 | attached: function() {
7 | this.refreshLike()
8 | }
9 | },
10 | /**
11 | * Component properties
12 | */
13 | properties: {
14 | data: {
15 | type: Object,
16 | value: {}
17 | },
18 | originId: {
19 | type: String,
20 | value: undefined
21 | },
22 | dontGoDetail: {
23 | type: Boolean,
24 | value: false
25 | }
26 | },
27 |
28 | /**
29 | * Component initial data
30 | */
31 | data: {
32 | photoUrl: "",
33 | likeCount: "loading",
34 | liked: false,
35 | },
36 |
37 | /**
38 | * Component methods
39 | */
40 | methods: {
41 | onItemTap: function() {
42 | if(!this.properties.dontGoDetail) {
43 | wx.navigateTo({
44 | url: `/pages/circle/poster-detail/poster-detail?posterId=${this.properties.data._id}&originId=${this.properties.originId}`
45 | })
46 | }
47 | },
48 | refreshLike: function() {
49 | const that = this
50 | db.collection("poster_likes").where({
51 | posterId: this.properties.data._id
52 | }).count().then(res => {
53 | that.setData({
54 | likeCount: res.total,
55 | })
56 | }).catch(error => {
57 | that.setData({
58 | likeCount: 0,
59 | })
60 | })
61 | db.collection("poster_likes").where({
62 | posterId: this.properties.data._id,
63 | likeId: this.properties.originId,
64 | }).get().then(res => {
65 | that.setData({
66 | liked: res.data.length > 0,
67 | })
68 | }).catch(error => {
69 | that.setData({
70 | liked: false,
71 | })
72 | })
73 | },
74 | onAuthorTap: function() {
75 | wx.navigateTo({
76 | url: `/pages/circle/user-data/user-data?userId=${
77 | this.properties.data.authorId
78 | }&originId=${this.properties.originId}`
79 | })
80 | },
81 | onImgTap: function() {
82 | const photoId = this.properties.data.photoId
83 | wx.previewImage({
84 | urls: [photoId],
85 | fail: function() {
86 | wx.showToast({
87 | title: "读取大图失败",
88 | image: "/images/error.png"
89 | })
90 | }
91 | })
92 | },
93 | onLikeTap: function() {
94 | if (!this.properties.originId) return
95 | const that = this
96 | if (this.data.liked) {
97 | wx.showLoading({
98 | title: "操作中",
99 | mask: true
100 | })
101 | wx.cloud
102 | .callFunction({
103 | name: "cancelLiked",
104 | data: {
105 | posterId: this.properties.data._id,
106 | likeId: this.properties.originId
107 | }
108 | })
109 | .then(res => {
110 | wx.showToast({
111 | title: "取消成功"
112 | })
113 | that.refreshLike()
114 | that.triggerEvent('likeEvent');
115 | })
116 | .catch(error => {
117 | wx.showToast({
118 | title: "取消失败",
119 | image: "/images/error.png"
120 | })
121 | })
122 | .finally(wx.hideLoading())
123 | } else {
124 | wx.showLoading({
125 | title: "操作中",
126 | mask: true
127 | })
128 | db.collection("poster_likes").add({
129 | data: {
130 | posterId: this.properties.data._id,
131 | likeId: this.properties.originId
132 | }
133 | }).then(res => {
134 | wx.showToast({
135 | title: "已赞"
136 | })
137 | that.refreshLike()
138 | that.triggerEvent('likeEvent');
139 | })
140 | .catch(error => {
141 | wx.showToast({
142 | title: "赞失败",
143 | image: "/images/error.png"
144 | })
145 | })
146 | .finally(wx.hideLoading())
147 | }
148 |
149 | },
150 | onItemLongTap: function() {
151 | const that = this
152 | if (this.properties.originId === this.properties.data.authorId) {
153 | wx.showActionSheet({
154 | itemList: ["删除此动态"],
155 | success: function(e) {
156 | const i = e.tapIndex
157 | if (i === 0) {
158 | wx.cloud
159 | .callFunction({
160 | name: "deletePoster",
161 | data: {
162 | _id: that.properties.data._id
163 | }
164 | })
165 | .then(res => {
166 | wx.showToast({
167 | title: "删除成功"
168 | })
169 | that.triggerEvent('deleteEvent');
170 | })
171 | .catch(err => {
172 | console.log(err)
173 | wx.showToast({
174 | title: "删除失败",
175 | image: "/images/error.png"
176 | })
177 | })
178 | }
179 | }
180 | })
181 | }
182 | }
183 | }
184 |
185 | })
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/post-item/post-item.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {
4 | "image-wrapper": "/component/image-wrapper/image-wrapper"
5 | },
6 | "disableScroll": true
7 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/post-item/post-item.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{data.author}}
5 | {{data.formatDate}}
6 |
7 |
8 | {{data.msg}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/post-item/post-item.wxss:
--------------------------------------------------------------------------------
1 | /* pages/circle/post-item/post-item.wxss */
2 |
3 | .post-item {
4 | width: 100%;
5 | background: #fff;
6 | overflow: hidden;
7 | padding: 30rpx 20rpx;
8 | display: flex;
9 | flex-wrap: wrap;
10 | }
11 |
12 | .post-item-hover {
13 | background: #ddd;
14 | }
15 |
16 | .post-item .post-title {
17 | display: flex;
18 | align-items: center;
19 | padding-right: 40rpx;
20 | justify-content: space-between;
21 | width: 100%;
22 | }
23 |
24 | .post-item .author {
25 | width: 50%;
26 | }
27 |
28 | .post-item .author-hover {
29 | background: #ddd;
30 | }
31 |
32 | .post-item .date {
33 | /* margin-left: 30rpx; */
34 | width: 50%;
35 | text-align: end;
36 | font-size: 25rpx;
37 | color: #888;
38 | }
39 |
40 | .msg-wrapper {
41 | width: 100%;
42 | }
43 |
44 | .post-item .msg {
45 | display: block;
46 | margin: 15rpx 0;
47 | }
48 |
49 | .post-item .image-outer {
50 | width: 33%;
51 | }
52 |
53 | .interact-area {
54 | width: 100%;
55 | display: flex;
56 | align-items: center;
57 | justify-content: flex-end;
58 | }
59 |
60 | .interact-item {
61 | width: 33.33%;
62 | }
63 |
64 | .interact-btn {
65 | font-size: 28rpx;
66 | height: 50rpx;
67 | padding: 0;
68 | border-radius: 0;
69 | display: flex;
70 | justify-content: center;
71 | align-items: center;
72 | }
73 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/user-info/user-info.js:
--------------------------------------------------------------------------------
1 | // pages/circle/component/user-info/user-info.js
2 | const db = wx.cloud.database()
3 |
4 | Component({
5 | lifetimes: {
6 | attached: function() {}
7 | },
8 |
9 | observers: {
10 | "originId, userId": function(originId, userId) {
11 | const that = this
12 | if (originId && userId && originId !== userId) {
13 | wx.cloud
14 | .callFunction({
15 | name: "getIfFollow",
16 | data: {
17 | follwerId: originId,
18 | followingId: userId
19 | }
20 | })
21 | .then(res => {
22 | const len = res.result.data.length
23 | that.setData({
24 | isFollow: len > 0
25 | },
26 | function() {
27 | if (len > 0) {
28 | this.setData({
29 | followText: "取消关注"
30 | })
31 | } else {
32 | this.setData({
33 | followText: "关注"
34 | })
35 | }
36 | }
37 | )
38 | })
39 | .catch(err => {
40 | wx.showToast({
41 | title: "获取关系失败",
42 | image: "/images/error.png"
43 | })
44 | })
45 | }
46 | }
47 | },
48 |
49 | /**
50 | * Component properties
51 | */
52 | properties: {
53 | userId: {
54 | type: String,
55 | value: ""
56 | },
57 | originId: {
58 | type: String,
59 | value: undefined
60 | },
61 | userName: {
62 | type: String,
63 | value: "Loading"
64 | },
65 | posterCount: {
66 | type: String,
67 | value: "Loading"
68 | },
69 | followingCount: {
70 | type: String,
71 | value: "Loading"
72 | },
73 | followerCount: {
74 | type: String,
75 | value: "Loading"
76 | }
77 | },
78 |
79 | /**
80 | * Component initial data
81 | */
82 | data: {
83 | isFollow: undefined,
84 | followText: "获取关系中..."
85 | },
86 |
87 | /**
88 | * Component methods
89 | */
90 | methods: {
91 | onPosterCountTap: function() {
92 | if (this.properties.userId !== "") {
93 | wx.navigateTo({
94 | url: "/pages/circle/user-poster/user-poster?userId=" +
95 | this.properties.userId
96 | })
97 | }
98 | },
99 |
100 | onFollowerCountTap: function() {
101 | if (this.properties.userId !== "") {
102 | wx.navigateTo({
103 | url: "/pages/circle/user-follower/user-follower?userId=" +
104 | this.properties.userId
105 | })
106 | }
107 | },
108 |
109 | onFollowingCountTap: function() {
110 | if (this.properties.userId !== "") {
111 | wx.navigateTo({
112 | url: "/pages/circle/user-following/user-following?userId=" +
113 | this.properties.userId
114 | })
115 | }
116 | },
117 |
118 | onFollowTap: function() {
119 | const that = this
120 | if (this.data.isFollow) {
121 | wx.showLoading({
122 | title: "操作中",
123 | mask: true
124 | })
125 | wx.cloud
126 | .callFunction({
127 | name: "cancelFollowing",
128 | data: {
129 | followerId: this.properties.originId,
130 | followingId: this.properties.userId
131 | }
132 | })
133 | .then(res => {
134 | wx.showToast({
135 | title: "取消关注成功"
136 | })
137 | that.setData({
138 | isFollow: false,
139 | followText: "关注"
140 | })
141 | })
142 | .catch(error => {
143 | wx.showToast({
144 | title: "取消关注失败",
145 | image: "/images/error.png"
146 | })
147 | })
148 | .finally(wx.hideLoading())
149 | } else if (this.data.isFollow !== undefined) {
150 | wx.showLoading({
151 | title: "操作中",
152 | mask: true
153 | })
154 | const data = {
155 | followerId: this.properties.originId,
156 | followingId: this.properties.userId
157 | }
158 | db.collection("poster_user_follows")
159 | .add({
160 | data: {
161 | ...data
162 | }
163 | })
164 | .then(res => {
165 | wx.showToast({
166 | title: "关注成功"
167 | })
168 | that.setData({
169 | isFollow: true,
170 | followText: "取消关注"
171 | })
172 | })
173 | .catch(error => {
174 | wx.showToast({
175 | title: "关注失败",
176 | image: "/images/error.png"
177 | })
178 | })
179 | .finally(wx.hideLoading())
180 | }
181 | }
182 | }
183 | })
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/user-info/user-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/user-info/user-info.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 用户名: {{userName}}
4 | 动态数: {{posterCount}}
5 | 关注数: {{followingCount}}
6 | 粉丝数: {{followerCount}}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/component/user-info/user-info.wxss:
--------------------------------------------------------------------------------
1 | /* pages/circle/component/user-info/user-info.wxss */
2 | .user-info {
3 | width: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | flex-wrap: wrap;
7 | }
8 |
9 | .info-item {
10 | border-bottom: 1rpx solid #aaa;
11 | padding-top: 35rpx;
12 | padding-bottom: 35rpx;
13 | padding-left: 20rpx;
14 | padding-right: 20rpx;
15 | height: auto;
16 | }
17 |
18 | .info-item-hover {
19 | background: #ddd;
20 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/poster-detail/poster-detail.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/poster-detail/poster-detail.js
2 | wx.cloud.init();
3 | const db = wx.cloud.database();
4 |
5 | Page({
6 |
7 | /**
8 | * Page initial data
9 | */
10 | data: {
11 | posterId: undefined,
12 | originId: undefined,
13 | posterData: undefined,
14 | likeList: [],
15 | currentPage: "like"
16 | },
17 |
18 | onItemDelete: function() {
19 | wx.navigateBack({
20 | delta: 1
21 | })
22 | },
23 |
24 | onItemLike: function() {
25 | if (this.data.posterId) {
26 | this.getLikeList(this.data.posterId)
27 | }
28 | },
29 |
30 | onLikeItemTap: function(e) {
31 | const id = e.currentTarget.dataset.likeId
32 | wx.navigateTo({
33 | url: `/pages/circle/user-data/user-data?userId=${this.data.posterData.authorId}&originId=${this.data.originId}`
34 | })
35 | },
36 |
37 | onLikeBtnTap: function() {
38 | if(this.data.posterId) {
39 | this.getLikeList(this.data.posterId)
40 | }
41 | },
42 |
43 | getLikeList: function(posterId) {
44 | const that = this
45 | wx.showLoading({
46 | title: '加载中',
47 | mask: true,
48 | })
49 | wx.cloud.callFunction({
50 | name: "getPosterLikes",
51 | data: {
52 | posterId: posterId
53 | }
54 | }).then(res => {
55 | that.setData({
56 | likeList: res.result
57 | })
58 | }).catch(err => {
59 | wx.showToast({
60 | title: '加载失败',
61 | image: "/images/error.png"
62 | })
63 | wx.navigateBack({
64 | delta: 1
65 | })
66 | }).finally(() => {
67 | wx.hideLoading()
68 | })
69 | },
70 |
71 | getPosterData: function(posterId) {
72 | const that = this
73 | wx.showLoading({
74 | title: '加载中',
75 | mask: true,
76 | })
77 | wx.cloud.callFunction({
78 | name: "getPosterData",
79 | data: {
80 | posterId: posterId
81 | }
82 | }).then(res => {
83 | that.setData({
84 | posterData: res.result
85 | })
86 | }).catch(err => {
87 | wx.showToast({
88 | title: '加载失败',
89 | image: "/images/error.png"
90 | })
91 | wx.navigateBack({
92 | delta: 1
93 | })
94 | }).finally(() => {
95 | wx.hideLoading()
96 | })
97 | },
98 |
99 | /**
100 | * Lifecycle function--Called when page load
101 | */
102 | onLoad: function(options) {
103 | wx.setNavigationBarTitle({
104 | title: "动态详情"
105 | })
106 | if (options.posterId) {
107 | this.setData({
108 | posterId: options.posterId,
109 | originId: options.originId
110 | })
111 | this.getPosterData(options.posterId)
112 | this.getLikeList(options.posterId)
113 | }
114 |
115 | },
116 |
117 |
118 |
119 | /**
120 | * Lifecycle function--Called when page is initially rendered
121 | */
122 | onReady: function() {
123 |
124 | },
125 |
126 | /**
127 | * Lifecycle function--Called when page show
128 | */
129 | onShow: function() {
130 |
131 | },
132 |
133 | /**
134 | * Lifecycle function--Called when page hide
135 | */
136 | onHide: function() {
137 |
138 | },
139 |
140 | /**
141 | * Lifecycle function--Called when page unload
142 | */
143 | onUnload: function() {
144 |
145 | },
146 |
147 | /**
148 | * Page event handler function--Called when user drop down
149 | */
150 | onPullDownRefresh: function() {
151 |
152 | },
153 |
154 | /**
155 | * Called when page reach bottom
156 | */
157 | onReachBottom: function() {
158 |
159 | },
160 |
161 | /**
162 | * Called when user click on the top right corner to share
163 | */
164 | onShareAppMessage: function() {
165 |
166 | }
167 | })
--------------------------------------------------------------------------------
/miniprogram/pages/circle/poster-detail/poster-detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "post-item": "./../component/post-item/post-item"
4 | }
5 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/poster-detail/poster-detail.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 互动详情
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{itemName.name}}
20 |
21 | 无数据
22 |
23 |
24 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/poster-detail/poster-detail.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/poster-detail/poster-detail.wxss */
2 | @import "./../component/post-item/post-item.wxss";
3 |
4 | .inter-border {
5 | border-top: 3rpx solid #aaa;
6 | border-bottom: 3rpx solid #aaa;
7 | height: 60rpx;
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | padding-left: 20rpx;
12 | padding-right: 20rpx;
13 | }
14 |
15 | .inter-detail {
16 | }
17 |
18 | .inter-btn {
19 | border-radius: 0;
20 | height: 60rpx;
21 | font-size: 30rpx;
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | }
26 |
27 | .name-hover {
28 | background: #ddd;
29 | }
30 |
31 | .name-item {
32 | padding: 20rpx 20rpx;
33 | height: 50rpx;
34 | width: 100%;
35 | display: flex;
36 | align-items: center;
37 | border-bottom: 3rpx solid #aaa;
38 | }
39 |
40 | .name-item:last-child {
41 | border-bottom: none;
42 | }
43 |
44 | .empty-placeholder {
45 | width: 100%;
46 | padding: 20rpx 0;
47 | font-size: 20rpx;
48 | color: #aaa;
49 | text-align: center;
50 | }
51 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/search-user/search-user.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/search-user/search-user.js
2 | Page({
3 | /**
4 | * Page initial data
5 | */
6 | data: {
7 | text: "",
8 | searchData: [],
9 | userId: undefined
10 | },
11 |
12 | onSearchItemTap: function(e) {
13 | const id = e.currentTarget.dataset.id
14 | wx.navigateTo({
15 | url: `/pages/circle/user-data/user-data?userId=${id}&originId=${
16 | this.data.userId
17 | }`
18 | })
19 | },
20 |
21 | bindTextInput: function(e) {
22 | const t = e.detail.value
23 | this.setData({
24 | text: t
25 | })
26 | },
27 |
28 | onSearchTap: function() {
29 | wx.showLoading({
30 | title: "查找中",
31 | mask: true
32 | })
33 | const that = this
34 | wx.cloud
35 | .callFunction({
36 | name: "searchUsers",
37 | data: {
38 | text: that.data.text
39 | }
40 | })
41 | .then(res => {
42 | wx.hideLoading()
43 | that.setData({
44 | searchData: res.result.data
45 | })
46 | })
47 | .catch(err => {
48 | wx.showToast({
49 | title: "获取动态失败",
50 | image: "/images/error.png"
51 | })
52 | wx.hideLoading()
53 | })
54 | },
55 |
56 | /**
57 | * Lifecycle function--Called when page load
58 | */
59 | onLoad: function(options) {
60 | wx.setNavigationBarTitle({
61 | title: "搜索用户"
62 | })
63 | if (options) {
64 | if (options.userId) {
65 | this.setData({
66 | userId: options.userId
67 | })
68 | }
69 | }
70 | },
71 |
72 | /**
73 | * Lifecycle function--Called when page is initially rendered
74 | */
75 | onReady: function() {},
76 |
77 | /**
78 | * Lifecycle function--Called when page show
79 | */
80 | onShow: function() {},
81 |
82 | /**
83 | * Lifecycle function--Called when page hide
84 | */
85 | onHide: function() {},
86 |
87 | /**
88 | * Lifecycle function--Called when page unload
89 | */
90 | onUnload: function() {},
91 |
92 | /**
93 | * Page event handler function--Called when user drop down
94 | */
95 | onPullDownRefresh: function() {},
96 |
97 | /**
98 | * Called when page reach bottom
99 | */
100 | onReachBottom: function() {},
101 |
102 | /**
103 | * Called when user click on the top right corner to share
104 | */
105 | onShareAppMessage: function() {}
106 | })
107 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/search-user/search-user.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/search-user/search-user.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{itemName.name}}
16 |
17 | 无数据
18 |
19 |
20 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/search-user/search-user.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/search-user/search-user.wxss */
2 | .search-bar-wrapper {
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | width: 100vw;
7 | height: 80rpx;
8 | border-bottom: 3rpx solid #aaa;
9 | }
10 |
11 | .search-bar {
12 | width: 80%;
13 | display: flex;
14 | align-items: center;
15 | justify-content: flex-start;
16 | }
17 |
18 | .search-btn-wrapper {
19 | width: 20%;
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | }
24 |
25 | .search-btn {
26 | font-size: 30rpx;
27 | }
28 |
29 | .search-bar-inner {
30 | width: 100%;
31 | padding-left: 20rpx;
32 | }
33 |
34 | .search-result-area {
35 | width: 100%;
36 | overflow-y: auto;
37 | height: calc(100vh - 80rpx);
38 | }
39 |
40 | .search-item-placeholder {
41 | width: 100%;
42 | padding: 20rpx 0;
43 | font-size: 20rpx;
44 | color: #aaa;
45 | text-align: center;
46 | }
47 |
48 | .search-item {
49 | width: 100%;
50 | padding: 20rpx 20rpx;
51 | border-bottom: 3rpx solid #aaa;
52 | }
53 |
54 | .search-item:last-child {
55 | border-bottom: 0;
56 | }
57 |
58 | .search-item-hover {
59 | background: #ddd;
60 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-data/user-data.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/user-data.js
2 | Page({
3 | /**
4 | * Page initial data
5 | */
6 | data: {
7 | userName: "Loading",
8 | posterCount: "Loading",
9 | followingCount: "Loading",
10 | followerCount: "Loading",
11 | userId: "",
12 | originId: ""
13 | },
14 |
15 | getUserData: function(userId, originId) {
16 | const that = this
17 | wx.cloud
18 | .callFunction({
19 | name: "getMePageData",
20 | data: {
21 | userId: userId
22 | }
23 | })
24 | .then(res => {
25 | that.setData({
26 | ...res.result
27 | })
28 | wx.hideLoading()
29 | })
30 | .catch(err => {
31 | wx.showToast({
32 | title: "获取个人信息失败",
33 | image: "/images/error.png"
34 | })
35 | wx.hideLoading()
36 | })
37 | },
38 |
39 | /**
40 | * Lifecycle function--Called when page load
41 | */
42 | onLoad: function(options) {
43 | wx.setNavigationBarTitle({
44 | title: "个人信息"
45 | })
46 | wx.showLoading({
47 | title: "加载中",
48 | mask: true
49 | })
50 | if (options) {
51 | if (options.userId) {
52 | this.setData({
53 | userId: options.userId
54 | })
55 | this.getUserData(options.userId)
56 | }
57 | if (options.originId) {
58 | this.setData({
59 | originId: options.originId
60 | })
61 | }
62 | }
63 | },
64 |
65 | /**
66 | * Lifecycle function--Called when page is initially rendered
67 | */
68 | onReady: function() {},
69 |
70 | /**
71 | * Lifecycle function--Called when page show
72 | */
73 | onShow: function() {},
74 |
75 | /**
76 | * Lifecycle function--Called when page hide
77 | */
78 | onHide: function() {},
79 |
80 | /**
81 | * Lifecycle function--Called when page unload
82 | */
83 | onUnload: function() {},
84 |
85 | /**
86 | * Page event handler function--Called when user drop down
87 | */
88 | onPullDownRefresh: function() {},
89 |
90 | /**
91 | * Called when page reach bottom
92 | */
93 | onReachBottom: function() {},
94 |
95 | /**
96 | * Called when user click on the top right corner to share
97 | */
98 | onShareAppMessage: function() {}
99 | })
100 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-data/user-data.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "user-info": "./../component/user-info/user-info"
4 | }
5 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-data/user-data.wxml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-data/user-data.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/user-data.wxss */
2 | @import "./../component/user-info/user-info.wxss";
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-follower/user-follower.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/user-follower/user-follower.js
2 | Page({
3 | /**
4 | * Page initial data
5 | */
6 | data: {
7 | followerData: []
8 | },
9 |
10 | onItemTap: function(e) {
11 | const id = e.currentTarget.dataset.id
12 | wx.navigateTo({
13 | url: `/pages/circle/user-data/user-data?userId=${id}&originId=${
14 | this.data.userId
15 | }`
16 | })
17 | },
18 |
19 | /**
20 | * Lifecycle function--Called when page load
21 | */
22 | onLoad: function(options) {
23 | wx.setNavigationBarTitle({
24 | title: "TA的粉丝"
25 | })
26 | const userId = options.userId
27 | if (userId) {
28 | const that = this
29 | wx.showLoading({
30 | title: "加载中",
31 | mask: true
32 | })
33 | wx.cloud
34 | .callFunction({
35 | name: "getFollowers",
36 | data: {
37 | userId: userId
38 | }
39 | })
40 | .then(res => {
41 | console.log(res)
42 | that.setData({
43 | followerData: res.result.data
44 | })
45 | })
46 | .catch(err => {
47 | wx.showToast({
48 | title: "加载失败",
49 | image: "/images/error.png"
50 | })
51 | })
52 | .finally(function() {
53 | wx.hideLoading()
54 | })
55 | }
56 | },
57 |
58 | /**
59 | * Lifecycle function--Called when page is initially rendered
60 | */
61 | onReady: function() {},
62 |
63 | /**
64 | * Lifecycle function--Called when page show
65 | */
66 | onShow: function() {},
67 |
68 | /**
69 | * Lifecycle function--Called when page hide
70 | */
71 | onHide: function() {},
72 |
73 | /**
74 | * Lifecycle function--Called when page unload
75 | */
76 | onUnload: function() {},
77 |
78 | /**
79 | * Page event handler function--Called when user drop down
80 | */
81 | onPullDownRefresh: function() {},
82 |
83 | /**
84 | * Called when page reach bottom
85 | */
86 | onReachBottom: function() {},
87 |
88 | /**
89 | * Called when user click on the top right corner to share
90 | */
91 | onShareAppMessage: function() {}
92 | })
93 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-follower/user-follower.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-follower/user-follower.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{itemName.name}}
6 |
7 | 无数据
8 |
9 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-follower/user-follower.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/user-follower/user-follower.wxss */
2 | .result-area {
3 | width: 100%;
4 | overflow-y: auto;
5 | height: calc(100vh - 80rpx);
6 | }
7 |
8 | .item-placeholder {
9 | width: 100%;
10 | padding: 20rpx 0;
11 | font-size: 20rpx;
12 | color: #aaa;
13 | text-align: center;
14 | }
15 |
16 | .item {
17 | width: 100%;
18 | padding: 20rpx 20rpx;
19 | border-bottom: 3rpx solid #aaa;
20 | }
21 |
22 | .item:last-child {
23 | border-bottom: 0;
24 | }
25 |
26 | .item-hover {
27 | background: #ddd;
28 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-following/user-following.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/user-follower/user-follower.js
2 | Page({
3 | /**
4 | * Page initial data
5 | */
6 | data: {
7 | followingData: []
8 | },
9 |
10 | onItemTap: function(e) {
11 | const id = e.currentTarget.dataset.id
12 | wx.navigateTo({
13 | url: `/pages/circle/user-data/user-data?userId=${id}&originId=${
14 | this.data.userId
15 | }`
16 | })
17 | },
18 |
19 | /**
20 | * Lifecycle function--Called when page load
21 | */
22 | onLoad: function(options) {
23 | wx.setNavigationBarTitle({
24 | title: "TA的关注"
25 | })
26 | const userId = options.userId
27 | if (userId) {
28 | const that = this
29 | wx.showLoading({
30 | title: "加载中",
31 | mask: true
32 | })
33 | wx.cloud
34 | .callFunction({
35 | name: "getFollowings",
36 | data: {
37 | userId: userId
38 | }
39 | })
40 | .then(res => {
41 | console.log(res)
42 | that.setData({
43 | followingData: res.result.data
44 | })
45 | })
46 | .catch(err => {
47 | wx.showToast({
48 | title: "加载失败",
49 | image: "/images/error.png"
50 | })
51 | })
52 | .finally(function() {
53 | wx.hideLoading()
54 | })
55 | }
56 | },
57 |
58 | /**
59 | * Lifecycle function--Called when page is initially rendered
60 | */
61 | onReady: function() {},
62 |
63 | /**
64 | * Lifecycle function--Called when page show
65 | */
66 | onShow: function() {},
67 |
68 | /**
69 | * Lifecycle function--Called when page hide
70 | */
71 | onHide: function() {},
72 |
73 | /**
74 | * Lifecycle function--Called when page unload
75 | */
76 | onUnload: function() {},
77 |
78 | /**
79 | * Page event handler function--Called when user drop down
80 | */
81 | onPullDownRefresh: function() {},
82 |
83 | /**
84 | * Called when page reach bottom
85 | */
86 | onReachBottom: function() {},
87 |
88 | /**
89 | * Called when user click on the top right corner to share
90 | */
91 | onShareAppMessage: function() {}
92 | })
93 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-following/user-following.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-following/user-following.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{itemName.name}}
6 |
7 | 无数据
8 |
9 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-following/user-following.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/user-following/user-following.wxss */
2 | .result-area {
3 | width: 100%;
4 | overflow-y: auto;
5 | height: calc(100vh - 80rpx);
6 | }
7 |
8 | .item-placeholder {
9 | width: 100%;
10 | padding: 20rpx 0;
11 | font-size: 20rpx;
12 | color: #aaa;
13 | text-align: center;
14 | }
15 |
16 | .item {
17 | width: 100%;
18 | padding: 20rpx 20rpx;
19 | border-bottom: 3rpx solid #aaa;
20 | }
21 |
22 | .item:last-child {
23 | border-bottom: 0;
24 | }
25 |
26 | .item-hover {
27 | background: #ddd;
28 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-poster/user-poster.js:
--------------------------------------------------------------------------------
1 | // miniprogram/pages/circle/user-poster/user-poster.js
2 | Page({
3 | /**
4 | * Page initial data
5 | */
6 | data: {
7 | userId: undefined,
8 | search: undefined,
9 | userPostData: []
10 | },
11 |
12 | getUserPostData: function(userId) {
13 | const that = this
14 | wx.cloud
15 | .callFunction({
16 | name: "getMainPageData",
17 | data: {
18 | userId: userId,
19 | isOnlyMe: true
20 | }
21 | })
22 | .then(res => {
23 | wx.hideLoading()
24 | console.log(res)
25 | that.setData({
26 | userPostData: res.result
27 | })
28 | })
29 | .catch(err => {
30 | wx.showToast({
31 | title: "获取动态失败",
32 | image: "/images/error.png"
33 | })
34 | wx.hideLoading()
35 | })
36 | },
37 |
38 | /**
39 | * Lifecycle function--Called when page load
40 | */
41 | onLoad: function(options) {
42 | wx.setNavigationBarTitle({
43 | title: "个人动态"
44 | })
45 | wx.showLoading({
46 | title: "加载中",
47 | mask: true
48 | })
49 | this.setData({
50 | search: options
51 | })
52 | if (options) {
53 | if (options.userId) {
54 | this.setData({
55 | userId: options.userId
56 | })
57 | this.getUserPostData(options.userId)
58 | }
59 | }
60 | },
61 |
62 | /**
63 | * Lifecycle function--Called when page is initially rendered
64 | */
65 | onReady: function() {},
66 |
67 | /**
68 | * Lifecycle function--Called when page show
69 | */
70 | onShow: function() {},
71 |
72 | /**
73 | * Lifecycle function--Called when page hide
74 | */
75 | onHide: function() {},
76 |
77 | /**
78 | * Lifecycle function--Called when page unload
79 | */
80 | onUnload: function() {},
81 |
82 | /**
83 | * Page event handler function--Called when user drop down
84 | */
85 | onPullDownRefresh: function() {},
86 |
87 | /**
88 | * Called when page reach bottom
89 | */
90 | onReachBottom: function() {},
91 |
92 | /**
93 | * Called when user click on the top right corner to share
94 | */
95 | onShareAppMessage: function() {}
96 | })
97 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-poster/user-poster.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "post-item": "./../component/post-item/post-item"
4 | }
5 | }
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-poster/user-poster.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 无数据
7 |
--------------------------------------------------------------------------------
/miniprogram/pages/circle/user-poster/user-poster.wxss:
--------------------------------------------------------------------------------
1 | /* miniprogram/pages/circle/user-poster/user-poster.wxss */
2 |
3 | .post-item-wrapper {
4 | display: block;
5 | border-bottom: 3rpx solid #aaa;
6 | }
7 |
8 | .post-item-wrapper:nth-child(n + 2) {
9 | margin: 3rpx 0;
10 | }
11 |
12 | .post-item-wrapper:nth-last-child(1) {
13 | margin-top: 3rpx;
14 | margin-bottom: 0;
15 | border-bottom: 0 solid #aaa;
16 | }
17 |
18 | .main-area-scroll {
19 | width: 100vw;
20 | height: 100vh;
21 | }
22 |
23 | .item-placeholder {
24 | width: 100%;
25 | padding: 20rpx 0;
26 | font-size: 20rpx;
27 | color: #aaa;
28 | text-align: center;
29 | }
30 |
--------------------------------------------------------------------------------
/miniprogram/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | const app = getApp()
3 | wx.cloud.init();
4 | const db = wx.cloud.database();
5 |
6 | Page({
7 | data: {
8 | inputValue: "input",
9 | userDetail: undefined,
10 | },
11 |
12 | getUserInfo: function(e) {
13 | this.setData({
14 | userDetail: e.detail
15 | }, function() {
16 | wx.navigateTo({
17 | url: "/pages/circle/circle"
18 | })
19 | })
20 | },
21 |
22 | onLoad: function() {
23 |
24 |
25 | },
26 | })
--------------------------------------------------------------------------------
/miniprogram/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/miniprogram/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/miniprogram/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 |
3 | page {
4 | background: #f6f6f6;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: flex-start;
8 | }
9 |
10 | .goto-contact {
11 | color: #66ccff
12 | }
13 |
14 | .inputs {
15 | background: #fff;
16 | margin: 1px;
17 | }
--------------------------------------------------------------------------------
/miniprogram/pages/index/user-unlogin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KuthorX/KuthorX-Helper/03ac0aba45ac1b44b6f485a0eb2d8e340c1ae49d/miniprogram/pages/index/user-unlogin.png
--------------------------------------------------------------------------------
/miniprogram/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/miniprogram/style/guide.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | background: #f6f6f6;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: flex-start;
6 | }
7 |
8 | .list {
9 | margin-top: 40rpx;
10 | height: auto;
11 | width: 100%;
12 | background: #fff;
13 | padding: 0 40rpx;
14 | border: 1px solid rgba(0, 0, 0, 0.1);
15 | border-left: none;
16 | border-right: none;
17 | transition: all 300ms ease;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: stretch;
21 | box-sizing: border-box;
22 | }
23 |
24 | .list-item {
25 | width: 100%;
26 | padding: 0;
27 | line-height: 104rpx;
28 | font-size: 34rpx;
29 | color: #007aff;
30 | border-top: 1px solid rgba(0, 0, 0, 0.1);
31 | display: flex;
32 | flex-direction: row;
33 | align-content: center;
34 | justify-content: space-between;
35 | box-sizing: border-box;
36 | }
37 |
38 | .list-item:first-child {
39 | border-top: none;
40 | }
41 |
42 | .list-item image {
43 | max-width: 100%;
44 | max-height: 20vh;
45 | margin: 20rpx 0;
46 | }
47 |
48 | .request-text {
49 | color: #222;
50 | padding: 20rpx 0;
51 | font-size: 24rpx;
52 | line-height: 36rpx;
53 | word-break: break-all;
54 | }
55 |
56 | .guide {
57 | width: 100%;
58 | padding: 40rpx;
59 | box-sizing: border-box;
60 | display: flex;
61 | flex-direction: column;
62 | }
63 |
64 | .guide .headline {
65 | font-size: 34rpx;
66 | font-weight: bold;
67 | color: #555;
68 | line-height: 40rpx;
69 | }
70 |
71 | .guide .p {
72 | margin-top: 20rpx;
73 | font-size: 28rpx;
74 | line-height: 36rpx;
75 | color: #666;
76 | }
77 |
78 | .guide .code {
79 | margin-top: 20rpx;
80 | font-size: 28rpx;
81 | line-height: 36rpx;
82 | color: #666;
83 | background: white;
84 | white-space: pre;
85 | }
86 |
87 | .guide .code-dark {
88 | margin-top: 20rpx;
89 | background: rgba(0, 0, 0, 0.8);
90 | padding: 20rpx;
91 | font-size: 28rpx;
92 | line-height: 36rpx;
93 | border-radius: 6rpx;
94 | color: #fff;
95 | white-space: pre
96 | }
97 |
98 | .guide image {
99 | max-width: 100%;
100 | }
101 |
102 | .guide .image1 {
103 | margin-top: 20rpx;
104 | max-width: 100%;
105 | width: 356px;
106 | height: 47px;
107 | }
108 |
109 | .guide .image2 {
110 | margin-top: 20rpx;
111 | width: 264px;
112 | height: 100px;
113 | }
114 |
115 | .guide .flat-image {
116 | height: 100px;
117 | }
118 |
119 | .guide .code-image {
120 | max-width: 100%;
121 | }
122 |
123 | .guide .copyBtn {
124 | width: 180rpx;
125 | font-size: 20rpx;
126 | margin-top: 16rpx;
127 | margin-left: 0;
128 | }
129 |
130 | .guide .nav {
131 | margin-top: 50rpx;
132 | display: flex;
133 | flex-direction: row;
134 | align-content: space-between;
135 | }
136 |
137 | .guide .nav .prev {
138 | margin-left: unset;
139 | }
140 |
141 | .guide .nav .next {
142 | margin-right: unset;
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "miniprogramRoot": "miniprogram/",
3 | "cloudfunctionRoot": "cloudfunctions/",
4 | "setting": {
5 | "urlCheck": true,
6 | "es6": true,
7 | "postcss": true,
8 | "minified": true,
9 | "newFeature": true
10 | },
11 | "appid": "wx053c16123ba5149d",
12 | "projectname": "kuthorx-helper",
13 | "libVersion": "2.7.7",
14 | "simulatorType": "wechat",
15 | "simulatorPluginLibVersion": {},
16 | "cloudfunctionTemplateRoot": "cloudfunctionTemplate",
17 | "condition": {
18 | "search": {
19 | "current": -1,
20 | "list": []
21 | },
22 | "conversation": {
23 | "current": -1,
24 | "list": []
25 | },
26 | "plugin": {
27 | "current": -1,
28 | "list": []
29 | },
30 | "game": {
31 | "list": []
32 | },
33 | "miniprogram": {
34 | "current": 0,
35 | "list": [
36 | {
37 | "id": -1,
38 | "name": "db guide",
39 | "pathName": "pages/databaseGuide/databaseGuide"
40 | }
41 | ]
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------