十一在家期间,我看了九型人格这本书,觉得很不错,想要做一下测试,测试的时候就是去网上搜了一下相关的测试,就开始了。但是转念一想,能不能做一个专门测试的小程序,里面可以增加各种各样的测试题目?于是,“国庆没去旅游的我”就开始了基于SCF的测试小程序的编写。
当然,不适太会写小程序,所以代码也未必好看。前端就是微信小程序,用了WEUI,后端就是Python,数据库用了腾讯云的CDB。
废话不多说,直接上干货。
前端页面设计
由于我只是个人开发一个小工具,所以也没有设置原型图,就是根据看的WEUI进行了一些简单的拼装。先发一下效果图:
整体来说,勉勉强强,还可以看,这个前端,基本上就是使用了WEUI的那些组件,基本上和拼图类似,例如首页:
wxml:
代码语言:javascript复制<!--pages/index/index.wxml-->
<view>
<view class="page__bd">
<view>
<swiper class="swiper" autoplay="true" interval="2000">
<swiper-item wx:for="{{hot_test}}" wx:key="item">
<image src="{{item.src}}" data-Tid='{{item.tid}}' bindtap='hotClick' mode="aspectFill" class="img"></image>
<view class="child">{{item.title}}</view>
</swiper-item>
</swiper>
</view>
</view>
<view class="page__bd">
<view class="weui-cells weui-grids">
<block wx:for="{{category}}" wx:key="item">
<navigator url="{{item.url}}" class="weui-grid" hover-class="weui-grid_active">
<image class="weui-grid__icon" src="{{item.src}}" />
<view class="weui-grid__label">{{item.title}}</view>
</navigator>
</block>
</view>
</view>
<view class="weui-cells weui-cells_after-title">
<view class="page__hd">
<view class="page__title">热门测试</view>
</view>
<block wx:for="{{content}}" wx:key="item">
<navigator url="/pages/content/index?tid={{item.tid}}" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<block wx:if="{{item.type}}">
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">{{item.title}}</view>
</block>
<block wx:else>
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;" />
</view>
<view class="weui-cell__bd">
<view>{{item.title}}</view>
<view style="font-size: 13px;color: #888888;">{{item.desc}}</view>
</view>
</block>
</navigator>
</block>
</view>
<text>n</text>
<view class="weui-footer">
<view class="weui-footer__text">爱测试 Copyright © 2019</view>
</view>
<text>nnn</text>
<view class="page__bd">
<view class="weui-tabbar">
<view class="weui-tabbar__item weui-bar__item_on">
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/test_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">测试首页</view>
</view>
<view class="weui-tabbar__item" bindtap='toRandom'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/random.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">随机一测</view>
</view>
<view class="weui-tabbar__item" bindtap='toUser'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/user_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">我的测试</view>
</view>
</view>
</view>
</view>
wxss:
代码语言:javascript复制/* pages/index/index.wxss */
.weui-tabbar{
position:fixed;
bottom:0;
left:0;
right:0;
}
.child {
width: 100%;
position: absolute;
left: 0;
right: 0;
bottom: 0;
margin: auto;
text-align: left;
background: white;
opacity: 0.8
}
.img {
width: 100%;
}
当然,这一部分更多还是学习的过程。我只是在WEUI中找到几个木块,例如九宫格、例如列表以及尾部(Footer)。
整个前端包括:
用户打开小程序,进入到首页,在首页选择好测试内容或者通过分类选择测试内容,并且进入到测试页面,测试将以选择题形式出现,每个选项都有分数,用户选择好之后,提交到结果页面,在结果页面可以查看到测试结果。用户也可以在用户管理页面,看到自己的测试历史信息。如果没有登录可以在登录页面进行登录。
当然以首页为例,在做这一部分的时候,我其实就已经定义好了,大概的接口内容,以及部分结构。
例如分类列表部分,这一部分的结构是:
代码语言:javascript复制category: [
{
'title': "热门",
'src': "/image/category/fire.png",
'url': "/pages/list/index?type=fire"
},
{
'title': "情感",
'src': "/image/category/heart.png",
'url': "/pages/list/index?type=heart"
},
{
'title': "性格",
'src': "/image/category/cross.png",
'url': "/pages/list/index?type=cross"
},
{
'title': "健康",
'src': "/image/category/health.png",
'url': "/pages/list/index?type=health"
},
{
'title': "职场",
'src': "/image/category/work.png",
'url': "/pages/list/index?type=work"
},
{
'title': "人际",
'src': "/image/category/group.png",
'url': "/pages/list/index?type=group"
},
{
'title': "能力",
'src': "/image/category/do.png",
'url': "/pages/list/index?type=do"
},
{
'title': "亲子",
'src': "/image/category/family.png",
'url': "/pages/list/index?type=family"
},
{
'title': "其他",
'src': "/image/category/detail.png",
'url': "/pages/list/index?type=others"
}
]
整个项目,使用最多的函数/方法应该就是后端的数据交互了,就是request,以获取首页热门测试为例:
代码语言:javascript复制wx.request({
url: '',
data: {
category: "fire",
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
that.setData({ content: res.data.result });
console.log("result len: ", res.data.result.length)
console.log("result: ", res.data.result)
if (res.data.result.length == 0) {
that.setData({ has_data: false });
}
},
})
这其中,入参实际已经被定义为:category:"fire"
出参则是:
tid: int 测试索引
title: str 测试题目
icon: str 测试图标
type: bool 测试类型(是否是单个内容的测试)
desc: str 测试题描述
Index
wxml
代码语言:javascript复制<!--pages/index/index.wxml-->
<view>
<view class="page__bd">
<view>
<swiper class="swiper" autoplay="true" interval="2000">
<swiper-item wx:for="{{hot_test}}" wx:key="item">
<image src="{{item.src}}" data-Tid='{{item.tid}}' bindtap='hotClick' mode="aspectFill" class="img"></image>
<view class="child">{{item.title}}</view>
</swiper-item>
</swiper>
</view>
</view>
<view class="page__bd">
<view class="weui-cells weui-grids">
<block wx:for="{{category}}" wx:key="item">
<navigator url="{{item.url}}" class="weui-grid" hover-class="weui-grid_active">
<image class="weui-grid__icon" src="{{item.src}}" />
<view class="weui-grid__label">{{item.title}}</view>
</navigator>
</block>
</view>
</view>
<view class="weui-cells weui-cells_after-title">
<view class="page__hd">
<view class="page__title">热门测试</view>
</view>
<block wx:for="{{content}}" wx:key="item">
<navigator url="/pages/content/index?tid={{item.tid}}" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<block wx:if="{{item.type}}">
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">{{item.title}}</view>
</block>
<block wx:else>
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;" />
</view>
<view class="weui-cell__bd">
<view>{{item.title}}</view>
<view style="font-size: 13px;color: #888888;">{{item.desc}}</view>
</view>
</block>
</navigator>
</block>
</view>
<text>n</text>
<view class="weui-footer">
<view class="weui-footer__text">爱测试 Copyright © 2019</view>
</view>
<text>nnn</text>
<view class="page__bd">
<view class="weui-tabbar">
<view class="weui-tabbar__item weui-bar__item_on">
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/test_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">测试首页</view>
</view>
<view class="weui-tabbar__item" bindtap='toRandom'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/random.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">随机一测</view>
</view>
<view class="weui-tabbar__item" bindtap='toUser'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/user_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">我的测试</view>
</view>
</view>
</view>
</view>
wxss
代码语言:javascript复制/* pages/index/index.wxss */
.weui-tabbar{
position:fixed;
bottom:0;
left:0;
right:0;
}
.child {
width: 100%;
position: absolute;
left: 0;
right: 0;
bottom: 0;
margin: auto;
text-align: left;
background: white;
opacity: 0.8
}
.img {
width: 100%;
}
js
代码语言:javascript复制// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
hot_test: [],
content: [],
category: [
{
'title': "热门",
'src': "/image/category/fire.png",
'url': "/pages/list/index?type=fire"
},
{
'title': "情感",
'src': "/image/category/heart.png",
'url': "/pages/list/index?type=heart"
},
{
'title': "性格",
'src': "/image/category/cross.png",
'url': "/pages/list/index?type=cross"
},
{
'title': "健康",
'src': "/image/category/health.png",
'url': "/pages/list/index?type=health"
},
{
'title': "职场",
'src': "/image/category/work.png",
'url': "/pages/list/index?type=work"
},
{
'title': "人际",
'src': "/image/category/group.png",
'url': "/pages/list/index?type=group"
},
{
'title': "能力",
'src': "/image/category/do.png",
'url': "/pages/list/index?type=do"
},
{
'title': "亲子",
'src': "/image/category/family.png",
'url': "/pages/list/index?type=family"
},
{
'title': "其他",
'src': "/image/category/detail.png",
'url': "/pages/list/index?type=others"
}
]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this
wx.setNavigationBarTitle({
title: '爱测试' //修改title
})
wx.request({
url: '',
data: {
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
that.setData({ hot_test: res.data.result });
console.log("result len: ", res.data.result.length)
console.log("result: ", res.data.result)
}
})
wx.request({
url: '',
data: {
category: "fire",
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
that.setData({ content: res.data.result });
console.log("result len: ", res.data.result.length)
console.log("result: ", res.data.result)
if (res.data.result.length == 0) {
that.setData({ has_data: false });
}
},
})
},
hotClick: function (e) {
wx.navigateTo({
url: '/pages/content/index?tid=' e.currentTarget.dataset.tid,
})
},
toUser: function(e){
wx.redirectTo({
url: '/pages/user/index',
})
},
toRandom: function (e) {
wx.request({
url: '',
data: {
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: "POST",
success: (res) => {
console.log(res)
wx.navigateTo({
url: '/pages/content/index?tid=' res.data.result.tid,
})
},
fail: (res) => {
wx.navigateTo({
url: '/pages/content/index?tid=0',
})
}
})
},
})
content
wxml
代码语言:javascript复制<!--pages/content/index.wxml-->
<view class="page">
<view class="page__bd">
<block wx:for="{{question}}" wx:key="item">
<view class="weui-panel">
<view class="weui-panel__hd">{{item.question}}</view>
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_small-appmsg">
<view class="weui-cells weui-cells_in-small-appmsg">
<view class="weui-cells weui-cells_after-title">
<radio-group bindchange="radioChange">
<label class="weui-cell weui-check__label" wx:for="{{item.selection}}" wx:for-item="selection" wx:key="item">
<radio class="weui-check" value="{{item.index}}-{{selection.value}}" checked="{{selection.checked}}"/>
<view class="weui-cell__bd">{{selection.value}}</view>
<view class="weui-cell__ft weui-cell__ft_in-radio" wx:if="{{selection.checked}}">
<icon class="weui-icon-radio" type="success_no_circle" size="16"></icon>
</view>
</label>
</radio-group>
</view>
</view>
</view>
</view>
</view>
</block>
<text>n</text>
<button class="weui-btn" type="primary" disabled="{{button_disabled}}" bindtap='buttonClick'>{{button_content}}</button>
</view>
</view>
wxss
代码语言:javascript复制/* pages/content/index.wxss */
.weui-btn{
margin-left: 10px;
margin-right: 10px;
}
js
代码语言:javascript复制// pages/content/index.js
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
page_title: "测试",
button_content: "提交测试",
button_disabled: false,
tid: 0,
question: []
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
var that = this
wx.setNavigationBarTitle({
title: this.data.page_title,
})
that.setData({
tid: options['tid']
})
wx.request({
url: '',
data: {
test: options['tid'],
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
that.setData({ question: res.data.result });
console.log("result len: ", res.data.result.length)
console.log("result: ", res.data.result)
},
fail: function (res) {
wx.showToast({
title: '获取测试题失败',
})
wx.redirectTo({
url: '/pages/index/index',
})
}
})
},
radioChange: function(e) {
console.log(e.detail.value)
var that = this
var question = this.data.question;
var selection_list = e.detail.value.split("-")
var change_quesetion = question[parseInt(selection_list[0])]
console.log(change_quesetion)
for (var j = 0, len = change_quesetion['selection'].length; j < len; j) {
change_quesetion['selection'][j].checked = change_quesetion['selection'][j].value == selection_list[1];
}
question[parseInt(selection_list[0])] = change_quesetion
that.setData({
question: question
});
},
buttonClick: function(e){
console.log("click")
var that = this
that.setData({
button_disabled: true,
button_content: "已经提交,请等待"
})
var sum = 0
var question = this.data.question
for (var j = 0, arr_len = question.length; j < arr_len; j) {
for (var i = 0, len = question[j]['selection'].length; i < len; i) {
if (question[j]['selection'][i].checked ){
sum = sum question[j]['selection'][i].score
console.log(sum)
}
}
}
var tid = this.data.tid
wx.request({
url: '',
data: {
test: tid,
score: sum,
user: app.globalData.openID,
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
console.log(res)
wx.redirectTo({
url: '/pages/result/index?tid=' tid '&rid=' res.data.result.rid,
})
},
fail: function (res) {
wx.showToast({
title: '获取测试结果失败,请稍后再试',
})
wx.redirectTo({
url: '/pages/index/index',
})
}
})
},
})
list
wxml
代码语言:javascript复制<!--pages/list/index.wxml-->
<view>
<view class="page__bd">
<block wx:if="{{has_data}}">
<view class="weui-cells weui-cells_after-title">
<block wx:for="{{content}}" wx:key="item">
<navigator url="/pages/content/index?tid={{item.tid}}" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<block wx:if="{{item.type}}">
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">{{item.title}}</view>
</block>
<block wx:else>
<view class="weui-cell__hd">
<image src="/image/content/{{item.icon}}.png" style="margin-right: 16px;vertical-align: middle;width:20px; height: 20px;" />
</view>
<view class="weui-cell__bd">
<view>{{item.title}}</view>
<view style="font-size: 13px;color: #888888;">{{item.desc}}</view>
</view>
</block>
</navigator>
</block>
</view>
</block>
<block wx:else>
<view class="weui-loadmore weui-loadmore_line">
<view class="weui-loadmore__tips weui-loadmore__tips_in-line">暂无数据</view>
</view>
</block>
</view>
<view class="weui-footer">
<view class="weui-footer__text">爱测试 Copyright © 2019</view>
</view>
</view>
js
代码语言:javascript复制// pages/list/index.js
Page({
/**
* 页面的初始数据
*/
data: {
has_data: true,
category: {
'fire': "热门",
'heart': "情感",
'cross': "性格",
'health': "健康",
'work': "职场",
'group': "人际",
'do': "能力",
'family': "亲子",
'others': "其他",
},
content: []
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
var that = this
var page_type = options['type']
wx.setNavigationBarTitle({
title: this.data.category[page_type] //修改title
})
wx.request({
url: '',
data: {
category: page_type,
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
that.setData({ content: res.data.result });
console.log("result len: ", res.data.result.length)
console.log("result: ", res.data.result)
if (res.data.result.length==0){
that.setData({ has_data: false });
}
},
fail: function (res) {
wx.showToast({
title: '获取列表失败',
})
wx.redirectTo({
url: '/pages/index/index',
})
}
})
},
})
login
wxml
代码语言:javascript复制<!--index.wxml-->
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 点击授权登陆 </button>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="weui-footer">
<view class="weui-footer__text">爱测试 Copyright © 2019</view>
</view>
</view>
wxss
代码语言:javascript复制/**index.wxss**/
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}
.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.userinfo-nickname {
color: #aaa;
}
.usermotto {
margin-top: 200px;
}
js
代码语言:javascript复制//index.js
//获取应用实例
const app = getApp()
Page({
data: {
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
wx.setNavigationBarTitle({
title: '授权登录',
})
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse){
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function(e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
console.log("openID: ", app.globalData.openID)
console.log("nickName: ", app.globalData.userInfo.nickName)
wx.request({
url: '',
data: {
"wechat": app.globalData.openID,
"nickname": app.globalData.userInfo.nickName,
"remark": app.globalData.userInfo,
"pic": app.globalData.userInfo.avatarUrl,
"local": app.globalData.userInfo.country "-" app.globalData.userInfo.province "-" app.globalData.userInfo.city,
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: "POST",
success: (res) => {
console.log("Success: ", res.data)
if (res.data.result != true) {
this.setData({
userInfo: null,
hasUserInfo: false
})
} else {
console.log("result is true")
this.setData({
hasUserInfo: true
})
wx.redirectTo({
url: '/pages/user/index',
})
}
},
fail: (res) => {
console.log("Faild: ", res.data)
this.setData({
userInfo: null,
hasUserInfo: false
})
wx.showToast({
title: "注册/登录失败,请稍后再试",
})
wx.redirectTo({
url: '/pages/user/index',
})
}
})
}
})
result
wxml
代码语言:javascript复制<!--pages/result/index.wxml-->
<import src="../../dependence/wxParse/wxParse.wxml" />
<view class="page">
<view class="page__hd">
<view class="page__title">{{page_title}}</view>
<view class="page__desc">* 本结果仅供参考</view>
</view>
<view class="page__bd">
<view class="weui-article">
<template is="wxParse" data="{{wxParseData:insertData.nodes}}" />
</view>
</view>
<button class="weui-btn" type="primary" bindtap='buttonClick'>再测一次</button>
<view class="page__bd">
<view class="weui-tabbar">
<view class="weui-tabbar__item" bindtap='toIndex'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/test_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">测试首页</view>
</view>
<view class="weui-tabbar__item" bindtap='toRandom'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/random.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">随机一测</view>
</view>
<view class="weui-tabbar__item" bindtap='toUser'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/user_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">我的测试</view>
</view>
</view>
</view>
</view>
wxss
代码语言:javascript复制/* pages/result/index.wxss */
@import "/dependence/wxParse/wxParse.wxss";
page{
background-color: #FFFFFF;
}
image{
margin: 4px 0;
}
.weui-tabbar{
position:fixed;
bottom:0;
left:0;
right:0;
}
.weui-btn{
margin-left: 10px;
margin-right: 10px;
}
js
代码语言:javascript复制// pages/result/index.js
var WxParse = require('../../dependence/wxParse/wxParse.js');
Page({
/**
* 页面的初始数据
*/
data: {
page_title: "测试结果",
tid: 0,
rid: 0,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this;
wx.setNavigationBarTitle({
title: this.data.page_title,
})
that.setData({
tid: options['tid'],
rid: options['rid']
})
wx.request({
url: '',
data: {
result: options['rid'],
},
method: "POST",
header: {
"Content-Type": "application/x-www-form-json"
},
success: function (res) {
/**
* WxParse.wxParse(bindName , type, data, target,imagePadding)
* 1.bindName绑定的数据名(必填)
* 2.type可以为html或者md(必填)
* 3.data为传入的具体数据(必填)
* 4.target为Page对象,一般为this(必填)
* 5.imagePadding为当图片自适应是左右的单一padding(默认为0,可选)
*/
console.log(res.data.result.content)
WxParse.wxParse('insertData', 'html', res.data.result.content, that);
},
fail: function (res) {
wx.showToast({
title: '获取结果失败,请稍后再试',
})
wx.redirectTo({
url: '/pages/index/index',
})
}
})
},
toUser: function (e) {
wx.redirectTo({
url: '/pages/user/index',
})
},
toIndex: function (e) {
wx.redirectTo({
url: '/pages/index/index',
})
},
buttonClick: function (e) {
console.log("click")
wx.redirectTo({
url: '/pages/content/index?tid=' this.data.tid,
})
},
toRandom: function (e) {
wx.request({
url: '',
data: {
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: "POST",
success: (res) => {
console.log(res)
wx.navigateTo({
url: '/pages/content/index?tid=' res.data.result.tid,
})
},
fail: (res) => {
wx.navigateTo({
url: '/pages/content/index?tid=0',
})
}
})
},
})
user
wxml
代码语言:javascript复制<!--pages/user/index.wxml-->
<view class="page">
<view class="page__bd">
<view class="weui-panel weui-panel_access">
<view class="weui-panel__bd">
<navigator url="{{login_url}}" class="weui-media-box weui-media-box_appmsg" hover-class="weui-cell_active">
<view class="weui-media-box__hd weui-media-box__hd_in-appmsg">
<image class="weui-media-box__thumb" src="{{user_pic}}" />
</view>
<view class="weui-media-box__bd weui-media-box__bd_in-appmsg">
<view class="weui-media-box__title">{{user_name}}</view>
<view class="weui-media-box__desc">{{user_local}}</view>
</view>
</navigator>
</view>
</view>
<view class="weui-loadmore weui-loadmore_line" wx:if="{{!login}}">
<view class="weui-loadmore__tips weui-loadmore__tips_in-line">登陆查看历史测试</view>
</view>
<view class="weui-cells weui-cells_after-title" wx:else>
<view class="weui-cell" wx:for="{{history}}" wx:key="item">
<navigator url="/pages/result/index?tid={{item.tid}}&rid={{item.rid}}">
<view class="weui-cell__bd">
<view>{{item.title}}</view>
<view style="font-size: 13px;color: #888888;">{{item.desc}}</view>
</view>
</navigator>
</view>
</view>
</view>
<view class="weui-footer">
<view class="weui-footer__text">爱测试 Copyright © 2019</view>
</view>
<view class="page__bd">
<view class="weui-tabbar">
<view class="weui-tabbar__item" bindtap='toIndex'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/test_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">测试首页</view>
</view>
<view class="weui-tabbar__item" bindtap='toRandom'>
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/random.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">随机一测</view>
</view>
<view class="weui-tabbar__item weui-bar__item_on">
<view style="position: relative;display:inline-block;">
<image src="/image/tabbar/user_1.png" class="weui-tabbar__icon"></image>
</view>
<view class="weui-tabbar__label">我的测试</view>
</view>
</view>
</view>
</view>
wxss
代码语言:javascript复制/* pages/user/index.wxss */
.weui-tabbar{
position:fixed;
bottom:0;
left:0;
right:0;
}
js
代码语言:javascript复制// pages/user/index.js
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
user_pic: "/image/others/user.png",
user_name: "点击登录",
user_local: "中国-广东-深圳",
login_url: "/pages/login/index",
login: false,
userInfo: {},
history: []
},
toIndex: function (e) {
wx.redirectTo({
url: '/pages/index/index',
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
var that = this
wx.setNavigationBarTitle({
title: '个人中心',
})
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
login: true,
user_pic: app.globalData.userInfo.avatarUrl,
user_name: app.globalData.userInfo.nickName,
user_local: app.globalData.userInfo.country "-" app.globalData.userInfo.province "-" app.globalData.userInfo.city,
login_url: ""
})
this.getHistory()
} else if (this.data.canIUse) {
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
login: true,
user_pic: res.userInfo.avatarUrl,
user_name: res.userInfo.nickName,
user_local: res.userInfo.country "-" res.userInfo.province "-" res.userInfo.city,
login_url: ""
})
this.getHistory()
}
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
login: true,
user_pic: res.userInfo.avatarUrl,
user_name: res.userInfo.nickName,
user_local: res.userInfo.country "-" res.userInfo.province "-" res.userInfo.city,
login_url: ""
})
this.getHistory()
}
})
}
},
getHistory: function(){
var that = this
wx.request({
url: '',
data: {
"wechat": app.globalData.openID,
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: "POST",
success: (res) => {
that.setData({ history: res.data.result })
}
})
},
toRandom: function (e) {
wx.request({
url: '',
data: {
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: "POST",
success: (res) => {
console.log(res)
wx.navigateTo({
url: '/pages/content/index?tid=' res.data.result.tid,
})
},
fail: (res) => {
wx.navigateTo({
url: '/pages/content/index?tid=0',
})
}
})
},
})
APP
js
代码语言:javascript复制//app.js
App({
onLaunch: function () {
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
wx.login({
success: function (res) {
if (res.code) {
console.log("res: " res.code)
wx.request({
url: '',
data: {
code: res.code,
},
header: {
"Content-Type": "application/x-www-form-json"
},
method: 'POST',
success: function (res) {
console.log('Success_1:', res)
var data = res.data.openid
console.log(data)
if (data) {
getApp().globalData.openID = data
wx.getUserInfo({
success: function (res) {
console.log('Success_2:', res)
}
})
} else {
console.log('Faild', res)
}
},
fail: function (res) {
console.log('Faild', res)
}
})
} else {
console.log('获取用户登录态失败!' res.errMsg)
}
}
});
// 获取用户信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 可以将 res 发送给后台解码出 unionId
this.globalData.userInfo = res.userInfo
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
},
globalData: {
userInfo: null,
userAuth: false,
testAccount: false,
openID: null
}
})
小结
由于我真的不会写js,写的代码也比较难看,所以还请各位看官大老爷多多理解。而且这个代码也不算优雅,有很多的wxss的样式和js的方法没有进行复用(但是作为demo问题不大,哈哈哈哈)
数据库开发
数据库,也就是边建表,边思考,边修改,大概就是这样:
数据库的设计,貌似也不是很好,我是将评价/测试/问题/选项/分类等分开建立了表,这样在用户多的时候可能会出现比较多的跨表查询,所以可能也不是很好,但是更多优化可以各位大佬来自己实现一下。
后端接口
后端接口主要是SCF的函数。并没有什么特殊的,基本就是数据库的增删改查。
YAML
代码语言:javascript复制Resources:
iceshi:
Type: TencentCloud::Serverless::Namespace
login:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: login
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_list:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_list
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_index:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_index
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_question:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_question
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_result:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_result
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_result_content:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_result_content
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_openid:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_openid
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_history:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_history
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
get_random:
Type: TencentCloud::Serverless::Function
Properties:
CodeUri: get_random
Type: Event
Description: This is a template function
Role: QCS_SCFExcuteRole
Environment:
Variables:
ENV_FIRST: env1
ENV_SECOND: env2
Handler: index.main_handler
MemorySize: 128
Runtime: Python3.6
Timeout: 3
Events:
hello_world_apigw: # ${FunctionName} '_apigw'
Type: APIGW
Properties:
StageName: release
ServiceId: service-n9j2nmdg
HttpMethod: ANY
Globals:
Function:
Timeout: 10
get_index的核心:
代码语言:javascript复制def getTestList(connection):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"SELECT * FROM `test` WHERE remark=0"
)
data = ()
cursor.execute(search_stmt, data)
cursor.close()
test_list = []
total = 5
for eve_test in cursor.fetchall():
icon = 'page'
if eve_test["times"] > 50:
icon = 'fire'
else:
if total > 0:
icon = 'new'
total = total - 1
test = {
"tid": eve_test["tid"],
"title": eve_test["name"],
"src": eve_test["pic"],
}
test_list.append(test)
return test_list
except Exception as e:
print("getTestList", e)
return []
get_history的核心:
代码语言:javascript复制def getHistoryList(connection, wechat):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"SELECT * FROM `result` LEFT JOIN test on test.tid=result.test "
"WHERE user=(SELECT uid FROM user WHERE wechat=%s) ORDER BY -rid"
)
data = (wechat)
cursor.execute(search_stmt, data)
cursor.close()
return [{"tid": eve_history["tid"], "rid": eve_history["rid"], "title": eve_history["name"],
"desc": eve_history["description"] if len(eve_history["description"]) > 10 else eve_history[
"description"][
0:10] "..."} for
eve_history in cursor.fetchall()]
except Exception as e:
print("getHistoryList", e)
return []
get_list的核心:
代码语言:javascript复制def getTestList(connection, category):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
if category == "fire":
search_stmt = (
"SELECT * FROM `test` ORDER BY -times Limit 0,100"
)
data = ()
else:
search_stmt = (
"SELECT * FROM `test` WHERE category=(SELECT cid FROM `category` WHERE remark=%s) ORDER BY -tid"
)
data = (category)
cursor.execute(search_stmt, data)
cursor.close()
test_list = []
total = 5
for eve_test in cursor.fetchall():
icon = 'page'
if eve_test["times"] > 50:
icon = 'fire'
else:
if total > 0:
icon = 'new'
total = total - 1
test = {
"tid": eve_test["tid"],
"title": eve_test["name"],
"icon": icon,
"type": False if eve_test["description"] else True,
"desc": eve_test["description"] if len(eve_test["description"]) > 40 else eve_test["description"][0:40] "..."
}
test_list.append(test)
return test_list
except Exception as e:
print("getTestList", e)
return []
get_openid的核心:
代码语言:javascript复制def main_handler(event, context):
url = "https://api.weixin.qq.com/sns/jscode2session"
data = {
'appid': AppId,
'secret': AppSecret,
'js_code': json.loads(event["body"])["code"],
'grant_type': 'authorization_code'
}
post_data = urllib.parse.urlencode(data).encode("utf-8")
req = urllib.request.Request(url=url, data=post_data)
resp = urllib.request.urlopen(req).read().decode("utf-8")
try:
return {
"openid": json.loads(resp)["openid"],
}
except:
return {
"openid": False
}
get_question的核心:
代码语言:javascript复制def getQuestionList(connection, test):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"SELECT * FROM question LEFT JOIN selection on question.qid=selection.question WHERE test=%s"
)
data = (test)
cursor.execute(search_stmt, data)
cursor.close()
question_dict = {}
question = []
for eve_question in cursor.fetchall():
print(eve_question)
if eve_question['qid'] not in question_dict:
question_dict[eve_question['qid']] = {
"qid": eve_question['qid'],
"question": eve_question['content'],
"selection": []
}
question_dict[eve_question['qid']]["selection"].append(
{
"score": eve_question["score"],
"value": eve_question["selection.content"],
"checked": True if len(question_dict[eve_question['qid']]["selection"]) == 0 else False
})
index_data = 0
for eve_key, eve_value in question_dict.items():
eve_value['index'] = index_data
question.append(eve_value)
index_data = index_data 1
return question
except Exception as e:
print("getQuestionList", e)
return []
def addNumber(connection, test):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"UPDATE `test` SET times=times 1 WHERE tid=%s"
)
data = (test)
cursor.execute(search_stmt, data)
cursor.close()
return True
except Exception as e:
print("addNumber", e)
return False
get_random的核心:
代码语言:javascript复制def getRandom(connection):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"select * from test order by rand() LIMIT 1"
)
data = ()
cursor.execute(search_stmt, data)
cursor.close()
return {"tid": cursor.fetchall()[0]["tid"]}
except Exception as e:
print("getRandom", e)
return {"tid": 0}
get_result的核心:
代码语言:javascript复制def getEvaluateList(connection, score, test, user):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"INSERT INTO `result` (`rid`, `user`, `test`, `evaluate`, `remark`, `date`) VALUES "
"(NULL, (SELECT uid FROM `user` WHERE wechat=%s), %s, (SELECT eid FROM `evaluate` "
"WHERE %s>=score_min and %s<=score_max and test=%s), '', CURRENT_TIMESTAMP);"
)
data = (user, test, score, score, test)
cursor.execute(search_stmt, data)
cursor.close()
result = cursor.lastrowid
return {"rid": result}
except Exception as e:
print("getEvaluateList", e)
return {}
get_result_content的核心:
代码语言:javascript复制def getEvaluateList(connection, rid):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"SELECT * FROM evaluate WHERE eid=(SELECT evaluate FROM `result` WHERE rid=%s)"
)
data = (rid)
cursor.execute(search_stmt, data)
cursor.close()
try:
result = cursor.fetchall()[0]
except:
result = "暂无数据,请稍后再试"
return {"content": result["content"]}
except Exception as e:
print("getEvaluateList", e)
return {}
login:
代码语言:javascript复制def getUserInfor(connection, wechat):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
search_stmt = (
"SELECT * FROM `user` WHERE `wechat`=%s"
)
data = (wechat)
cursor.execute(search_stmt, data)
cursor.close()
result = cursor.fetchall()
return len(result)
except Exception as e:
print("getUserInfor", e)
return False
def addUserInfor(connection, wechat,username,local,pic):
try:
connection.ping(reconnect=True)
cursor = connection.cursor()
insert_stmt = (
"INSERT INTO user(`wechat`,`username`,`local`,`pic`) "
"VALUES (%s,%s,%s,%s)"
)
data = (wechat,username,local,pic)
cursor.execute(insert_stmt, data)
cursor.close()
connection.close()
return True
except Exception as e:
print("addUserInfor", e)
return False
总结
这个小程序是我当天早晨开始做,因为实在不会写小程序,所以小程序部分比较费时间,大约用了一上午,中午吃个饭,弄了一下数据库,下午和老爹老妈出去溜达一圈,晚上回来写了后台,并进行了调试和部分BUG的修复,晚上算是完成了这个小程序的开发。我不清楚,作为一个小菜鸟,如果从头开始开发一个小程序要多久,但是通过这样一个例子,我可以说,在SCF的帮助下,真的可以很简单。
简答1: 小程序部分使用了WEUI更多就像用拼图,可以很快拼凑出自己想要的或者基本满足自身需求的UI,整个一个数据交互,逻辑基本一致,只是用了request的方法。
简单2: 按照道理来说,我使用前后端数据分离,那么后端提供接口的时候就要有一个WEB服务,但是通过SCF,我们不需要考虑这个WEB服务,我们只需要处理好自己的逻辑,就可以根据固定的入参出参,与API网关结合,实现传统的可能要我们自己配置Nginx,Apache等软件才能实现的WEB服务。
简单3: 我不需要配置服务器,我只需要用Python写好数据库的增删改查逻辑,并将参数传入,结果返回即可。
简单4: 我不需要评估服务器规模,这是一个简单的Demo,我如果想用起来,我可能要买一个服务器,或者云主机等。但是这就涉及到一笔开销,也需要评估一个规格配置,但是,使用SCF是按量付费,无需考虑这些,自动伸缩等功能都是供应商来实现,相对更便利。
简单5: 真的很方便……