通过SCF做一个性格测试的小程序

2019-10-08 10:18:29 浏览数 (1)

十一在家期间,我看了九型人格这本书,觉得很不错,想要做一下测试,测试的时候就是去网上搜了一下相关的测试,就开始了。但是转念一想,能不能做一个专门测试的小程序,里面可以增加各种各样的测试题目?于是,“国庆没去旅游的我”就开始了基于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: 真的很方便……

0 人点赞