uni- app前后端 基础+项目实战课 笔记

一、项目介绍

课程内容项目页面说明

二、创建项目、后端环境介绍

  • 创建项目
  1. 打开HBuilderX -> 新建 uniapp 项目;
  2. 创建页面
    |_ index.vue 入口页面
    |_ write.vue 文章撰写页面
    |_ my.vue 账户中心页面
  • 后端使用php+mysql
    完善底部导航栏
  1. 下载图标, 图标地址 :
    https://pan.baidu.com/s/1iaWd6l_-cIJ3RNUsqNqFNQ
  2. 将图标部署至 /static 目录;
  3. 修改 page.json
"tabBar" : {
        "color" : "#707070",
        "selectedColor" : "#DE533A",
        "list" : [
            {
                "pagePath" : "pages/index/index",
                "text" : "文章",
                "iconPath" : "static/nav1.png",
                "selectedIconPath" : "static/nav1-a.png"
            },
            {
                "pagePath" : "pages/write/write",
                "text" : "写作",
                "iconPath" : "static/nav2.png",
                "selectedIconPath" : "static/nav2-a.png"
            },
            {
                "pagePath" : "pages/my/my",
                "text" : "我的",
                "iconPath" : "static/nav3.png",
                "selectedIconPath" : "static/nav3-a.png"
            }
        ]
    }
  1. 修改项目名称
"globalStyle" : {
    "navigationBarTextStyle" : "black",
    "navigationBarTitleText" : "悦读",
    "navigationBarBackgroundColor" : "#F8F8F8",
    "backgroundColor" : "#F8F8F8"
}

5.加载 colorUI 框架
https://github.com/weilanwl/ColorUI

三、封装全局登录检查函数并部署

1、在 main.js 中封装全局登录函数

Vue.prototype.checkLogin = function(backpage, backtype){
    var SUID  = uni.getStorageSync('SUID');
    var SRAND = uni.getStorageSync('SRAND');
    var SNAME = uni.getStorageSync('SNAME');
    var SFACE = uni.getStorageSync('SFACE');
    if(SUID == '' || SRAND == '' || SFACE == ''){
        uni.redirectTo({url:'../login/login?backpage='+backpage+'&backtype='+backtype});
        return false;
    }
    return [SUID, SRAND, SNAME, SFACE];
}

参数说明
backpage, backtype 2个参数分别代表:
backpage : 登录后返回的页面
backtype : 打开页面的类型[1 : redirectTo 2 : switchTab]
返回值说明
已经登录返回数组 [用户 id, 用户随机码, 用户昵称, 用户表情]

2、创建 login 页面
login 页面作为登录过度页面,多端登录都通过此页面完成!

3、在页面中应用登录检查函数,如 write.vue

<script>
export default {
    data() {
        return {
            
        };
    },
    onLoad : function() {
        var loginRes = this.checkLogin('../my/my', '2');
        if(!loginRes){return false;}
    }
}
</script>

return 或终止函数运行哦!

四、UNI-APP端使用微信登录原理和条件编译


<script>
export default {
    data() {
        return {
            
        };
    },
    onLoad:function(){
        //app 端微信登录
        // 手册位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
        // #ifdef APP-PLUS
        uni.login({
            success: (res) => {
                // res 对象格式
                //{"code":"***",
                //"authResult":{
                    //"openid":"***",
                    //"scope":"snsapi_userinfo",
                    //"refresh_token":"**",
                    //"code":"****",
                    //"unionid":"***",
                    //"access_token":"***",
                    //"expires_in":7200
                //},
                //"errMsg":"login:ok"}
                uni.getUserInfo({
                    success: (info) => {
                        // info 对象格式
                        // {"errMsg":"getUserInfo:ok",
                        // "rawData":"...
                        // "userInfo":{
                            //"openId":"***",
                            //"nickName":"***",
                            //"gender":1,
                            // "city":"Xi'an",
                            // "province":"Shaanxi",
                            // "country":"China",
                            // "avatarUrl":"头像",
                            // "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
                        //},"signature":""}
                        // 与服务器交互将数据提交到服务端数据库
                        
                    },
                    fail: () => {
                        uni.showToast({title:"微信登录授权失败"});
                    }
                })
            },
            fail: () => {
                uni.showToast({title:"微信登录授权失败"});
            }
        })
        // #endif
    }
}
</script>

四、部署php环境

1、集成环境 phpstudy 安装
phpstudy 下载地址
http://phpstudy.php.cn/
修改时间php.ini 配置

date.timezone = PRC  //中国时区

2、固定内网ip

开始 > 设置 > 网络和internet > 以太网 > 更改适配器属性 在以太网图标 > 右键 > 状态 > 详细信息

注意 : localhost 代表本机,手机中 localhost 代表手机自己并不能代表内网服务器!

image

获取内网ip规则,如 : 192.168.1.***。
修改ipv4地址,固定内网ip:

开始 > 设置 > 网络和internet > 以太网 > 更改适配器属性 在以太网图标 > 右键 > 属性 > ipv4 地址 > 点击修改,如 : 192.168.1.188

image

修改后测试网络畅通即可!
浏览器访问 http://192.168.1.188/ 即可直接访问刚刚搭建的php环境 ;

3、部署基础php 代码
我们为大家精心准备了一个极小的基础开发库,请大家在课程内下载并部署至 php 环境的根目录;

4、基础开发库原理详解
路由解析例子

<?php
namespace hsC;
class index{
    public function index(){
        echo 'index';
    }
    public function test(){
        echo 'test';
    }
}
//定义路由输出
[http://192.168.199.166/index.php?token=api2018&c=member&m=t](http://192.168.199.166/index.php?token=api2018&c=member&m=t)


        $res = array('name' => 'hcoder');
        echo jsonCode('ok', $res);
//输出字符串  {"status":"ok","data":{"name":"hcoder"}}

//创建test类 getUser函数
<?php
namespace hsModel;
class test{
    public function getUser(){
        echo 'get user ...';
    }
}

    //创建新类名,调用getUser函数
    $mTest = new \hsModel\test();
    $mTest -> getUser();

最后打开phpStudy》phpMyAdmin 默认用户密码root
创建名字xxx的数据库
    

五、创建用户数据表,完成app端用户登录功能

1、创建项目数据库
1.1 打开 phpMyadmin 或者其他 mysql 管理工具,phpstudy 环境下 mysql 账户 root 密码 root;
1.2 创建数据库 "yuedu"

CREATE TABLE `yuedu_members` (
  `u_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `u_openid` varchar(100) NOT NULL COMMENT 'openid',
  `u_name` varchar(50) NOT NULL COMMENT '用户昵称',
  `u_face` varchar(200) NOT NULL COMMENT '用户头像',
  `u_random` varchar(30) NOT NULL COMMENT '用户随机码',
  `u_integral` int(10) DEFAULT '0' COMMENT '积分',
  `u_remainder` int(10) DEFAULT '0' COMMENT '余额',
  `u_regtime` int(11) NOT NULL COMMENT '用户注册时间',
  PRIMARY KEY (`u_id`),
  UNIQUE KEY `u_openid` (`u_openid`),
  UNIQUE KEY `u_id` (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2、phpMyAdmin 错误修正

Fatal error:  Cannot 'break' 2 levels in D:\phpStudy\PHPTutorial\WWW\phpMyAdmin\export.php on line 590
590 行把 break 2 改成 break;

3、配置数据库
打开 index.php 修改以下配置

/* 数据库配置 */

define('HS_DB_HOST'    , '127.0.0.1'); // mysql 服务器地址
define('HS_DB_NAME'    , 'yuedu');     // 数据库名称
define('HS_DB_USER'    , 'root');      // 数据库账号
define('HS_DB_PWD'     , 'root');      // 数据库密码
define('HS_DB_PRE'     , 'yuedu_');    // 数据表统一前缀
define('HS_DB_CHARSET' , 'utf8mb4');   // mysql 字符集类型

4、创建 member 控制器,编写以下代码:

<?php
namespace hsC;
class member{
    public function index(){
        
    }
    
    public function login(){
        
        //调用模型完成用户登录及注册
        $memberModel = new \hsModel\member();
        $memberModel->login();
        
    }
}
// 原理见视频教程

5、在 app 端定义全局变量,定义全局的 api 接口地址及token

var APITOKEN  = 'api2018';
Vue.prototype.apiServer = 'http://192.168.1.188/index.php?token='+APITOKEN+'&c=member&m=login';

为什么这样定义? 便于后期修改!

  • 如果手机端无法访问局域网ip,如何解决?

方案1、使用花生壳软件 > 内网穿透(功能收费6元)
方案2、使用模拟器调试,模拟器下载地址 :

方案3、将后端api 部署到公网服务器上

6、login.vue 与api交互完成app端登录功能

<template>
    <view>
        
    </view>
</template>
<script>
var _self;
export default {
    data() {
        return {
            
        };
    },
    onLoad:function(options){
        _self = this;
        //app 端微信登录
        // 手册位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
        // #ifdef APP-PLUS
        uni.login({
            success: (res) => {
                // res 对象格式
                //{"code":"***",
                //"authResult":{
                    //"openid":"***",
                    //"scope":"snsapi_userinfo",
                    //"refresh_token":"**",
                    //"code":"****",
                    //"unionid":"***",
                    //"access_token":"***",
                    //"expires_in":7200
                //},
                //"errMsg":"login:ok"}
                uni.getUserInfo({
                    success: (info) => {
                        // info 对象格式
                        // {"errMsg":"getUserInfo:ok",
                        // "rawData":"...
                        // "userInfo":{
                            //"openId":"***",
                            //"nickName":"***",
                            //"gender":1,
                            // "city":"Xi'an",
                            // "province":"Shaanxi",
                            // "country":"China",
                            // "avatarUrl":"头像",
                            // "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
                        //},"signature":""}
                        // 与服务器交互将数据提交到服务端数据库
                        uni.request({
                            url: _self.apiServer+'member&m=login',
                            method: 'POST',
                            header: {'content-type' : "application/x-www-form-urlencoded"},
                            data: {
                                openid : info.userInfo.openId,
                                name   : info.userInfo.nickName,
                                face   : info.userInfo.avatarUrl,
                            },
                            success: res => {
                                console.log(JSON.stringify(res));
                                res = res.data;
                                // 登录成功 记录会员信息到本地
                                if(res.status == 'ok'){
                                    uni.showToast({title:"登录成功"});
                                    uni.setStorageSync('SUID' , res.data.u_id + '');
                                    uni.setStorageSync('SRAND', res.data.u_random + '');
                                                uni.setStorageSync('SNAME', res.data.u_name + '');
                                    uni.setStorageSync('SFACE', res.data.u_face + '');
                                    // 跳转
                                    if(options.backtype == 1){
                                        uni.redirectTo({url:options.backpage});
                                    }else{
                                        uni.switchTab({url:options.backpage});
                                    }
                                }else{
                                    uni.showToast({title:res.data});
                                }
                            },
                            fail: (e) => {
                                console.log(JSON.stringify(e));
                            }
                        });
                    },
                    fail: () => {
                        uni.showToast({title:"微信登录授权失败"});
                    }
                })
            },
            fail: () => {
                uni.showToast({title:"微信登录授权失败"});
                uni.hideLoading();
            }
        })
        // #endif
    }
}
</script>

<style>

</style>

六、完成小程序端登录

1、在uniapp中配置小程序appid

打开 manifest.json ,找到微信小程序配置,填写 appid 重启应用;
// 您需要注册成为微信小程序开发者才能获取 appid

2、视图添加登录按钮

<template>
    <view>
        <!-- #ifdef MP-WEIXIN -->
        <button type="primary" open-type="getUserInfo" @getuserinfo="getUserInfo">使用微信登录</button>
        <!-- #endif -->
    </view>
</template>

3、js 核心代码

<script>
var _self, pageOptions, session_key, openid;
export default {
    data() {
        return {
            
        };
    },
    
    methods:{
        // #ifdef MP-WEIXIN
        getUserInfo : (info) => {
            info = info.mp.detail.userInfo;
            //userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
            // 与服务器交互将数据提交到服务端数据库
            uni.request({
                url: _self.apiServer+'member&m=login',
                method: 'POST',
                header: {'content-type' : "application/x-www-form-urlencoded"},
                data: {
                    openid : openid,
                    name   : info.nickName,
                    face   : info.avatarUrl,
                },
                success: res => {
                    console.log(res);
                    res = res.data;
                    // 登录成功 记录会员信息到本地
                    if(res.status == 'ok'){
                        uni.showToast({title:"登录成功"});
                        uni.setStorageSync('SUID' , res.data.u_id + '');
                        uni.setStorageSync('SRAND', res.data.u_random + '');
                        uni.setStorageSync('SNAME', res.data.u_name + '');
                        uni.setStorageSync('SFACE', res.data.u_face + '');
                        // 跳转
                        if(pageOptions.backtype == 1){
                            uni.redirectTo({url:pageOptions.backpage});
                        }else{
                            uni.switchTab({url:pageOptions.backpage});
                        }
                    }else{
                        uni.showToast({title:res.data});
                    }
                },
                fail: (e) => {
                    console.log(JSON.stringify(e));
                }
            });
            
        },
        // #endif
    },
    onLoad:function(options){
        _self = this;
        pageOptions = options;
        // #ifdef MP-WEIXIN
        // 调用 微信 login 获取 code
        uni.login({
            success: (res) => {
                uni.request({
                    url:_self.apiServer+'member&m=codeToSession&code='+res.code,
                    success: (sessions) => {
                        // sessions.date 数据格式
                        // expires_in:7200
                        // openid:"oS6of0V0rdp9nY_BuvCnQUasOHYc"
                        // session_key:"87sE2oDD8lc+aDJj0tB6+g=="
                        // 获取 unionId
                        session_key = sessions.data.session_key;
                        openid      = sessions.data.openid;
                    },
                });
            }
        });
        // #endif
        //app 端微信登录
        // 手册位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
        // #ifdef APP-PLUS
        uni.login({
            success: (res) => {
                // res 对象格式
                //{"code":"***",
                //"authResult":{
                    //"openid":"***",
                    //"scope":"snsapi_userinfo",
                    //"refresh_token":"**",
                    //"code":"****",
                    //"unionid":"***",
                    //"access_token":"***",
                    //"expires_in":7200
                //},
                //"errMsg":"login:ok"}
                uni.getUserInfo({
                    success: (info) => {
                        // info 对象格式
                        // {"errMsg":"getUserInfo:ok",
                        // "rawData":"...
                        // "userInfo":{
                            //"openId":"***",
                            //"nickName":"***",
                            //"gender":1,
                            // "city":"Xi'an",
                            // "province":"Shaanxi",
                            // "country":"China",
                            // "avatarUrl":"头像",
                            // "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
                        //},"signature":""}
                        // 与服务器交互将数据提交到服务端数据库
                        uni.request({
                            url: _self.apiServer+'member&m=login',
                            method: 'POST',
                            header: {'content-type' : "application/x-www-form-urlencoded"},
                            data: {
                                openid : info.userInfo.openId,
                                name   : info.userInfo.nickName,
                                face   : info.userInfo.avatarUrl,
                            },
                            success: res => {
                                console.log(JSON.stringify(res));
                                res = res.data;
                                // 登录成功 记录会员信息到本地
                                if(res.status == 'ok'){
                                    uni.showToast({title:"登录成功"});
                                    uni.setStorageSync('SUID' , res.data.u_id + '');
                                    uni.setStorageSync('SRAND', res.data.u_random + '');
                                                uni.setStorageSync('SNAME', res.data.u_name + '');
                                    uni.setStorageSync('SFACE', res.data.u_face + '');
                                    // 跳转
                                    if(options.backtype == 1){
                                        uni.redirectTo({url:options.backpage});
                                    }else{
                                        uni.switchTab({url:options.backpage});
                                    }
                                }else{
                                    uni.showToast({title:res.data});
                                }
                            },
                            fail: (e) => {
                                console.log(JSON.stringify(e));
                            }
                        });
                    },
                    fail: () => {
                        uni.showToast({title:"微信登录授权失败"});
                    }
                })
            },
            fail: () => {
                uni.showToast({title:"微信登录授权失败"});
                uni.hideLoading();
            }
        })
        // #endif
    }
}
</script>
<style></style>
php 后端代码

<?php
namespace hsC;
class member{
    public function index(){
        
    }
    
    public function login(){
        
        //调用模型完成用户登录及注册
        $memberModel = new \hsModel\member();
        $memberModel->login();
        
    }
    
    public function codeToSession(){
        if(empty($_GET['code'])){exit(jsonCode('error', 'code error'));}
        $url =  "https://api.weixin.qq.com/sns/jscode2session?appid=".HS_APPID.
        "&secret=".HS_SECRET."&js_code=".$_GET['code']."&grant_type=authorization_code";
        $curl = new \hsTool\curl();
        $res = $curl->get($url);
        echo $res;
    }
}

七、多平台多应用 统一登录微信关系

多平台统一登录之 unionID

通过获取用户基本信息接口,开发者可通过OpenID来获取用户基本信息,而如果开发者拥有多个应用,可使用以下办法通过UnionID机制来在多个应用进行用户帐号互通。
只要是同一个微信开放平台帐号下的公众号,用户的UnionID是唯一的。

换句话说,同一用户,对同一个微信开放平台帐号下的不同应用,UnionID是相同的。
此前的OpenID机制,每个微信号对应每个应用有唯一的OpenID,所以不同应用之间是不能共享用户的,现在有了UnionID就可以了。

APP端获取 unionID
使用 uni.login 即可;

小程序端获取 unionID
步骤 :

1、配置小程序 appid (此appid 在微信开放平台已经绑定);
2、使用 uni.login 登录时会获取 code,用 code 换取 seesion_key;
3、在获取用户信息函数中获取到加密信息;
4、利用 seesion_key 及加密信息在服务端解密获取 unionID

php 后端注意事项

需要开启 php_openssl 扩展
前端实现过程代码

export default {
    data() {
        return {
            
        };
    },
    
    methods:{
        // #ifdef MP-WEIXIN
        getUserInfo : (info) => {
            //加密数据
            var encryptedData = info.mp.detail.encryptedData;
            var iv            = info.mp.detail.iv;
            info              = info.mp.detail.userInfo;
            //info
            //userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
            //与服务器交互进行解密
            uni.request({
                url: _self.apiServer+'member&m=wxaes',
                method: 'POST',
                header: {'content-type' : "application/x-www-form-urlencoded"},
                data: {
                    session_key   : session_key,
                    encryptedData : encryptedData,
                    iv            : iv
                },
                success: res => {
                    console.log(res);
                    //此处可以成功获取 unionId 利用 unionId 完成登录即可
                },
                fail: () => {},
                complete: () => {}
            });
        }
    },
    onLoad:function(options){
            _self = this;
            pageOptions = options;
            // #ifdef MP-WEIXIN
            // 调用 微信 login 获取 code
        uni.login({
                    success: (res) => {
                        uni.request({
                            url:_self.apiServer+'member&m=codeToSession&code='+res.code,
                            success: (sessions) => {
                                session_key = sessions.data.session_key;
                            }
                        }
                    }
                });
            }
        });
        // #endif
}

php 后端代码

<?php
namespace hsC;
class member{
        //......
    public function wxaes(){
        if(empty($_POST['session_key']) || empty($_POST['encryptedData']) || empty($_POST['iv'])){exit(jsonCode('error', 'data error'));}
        include HS_TOOLS.'WXBizDataCrypt.php';
        $pc = new \WXBizDataCrypt(HS_APPID, $_POST['session_key']);
        $data = '';
        $errCode = $pc->decryptData($_POST['encryptedData'], $_POST['iv'], $data);
        if ($errCode == 0) {
            exit($data);
        } else {
            exit(jsonCode('error', $errCode));
        }
    }
}

八、api接口安全策略 - 签名策略

安全概述
前面章节讲解的接口是裸露的、不安全的!使用post、get模拟可以轻松对api进行请求,最简单的攻击就可以瞬间完成近万会员的注册!
所以在进行api接口通讯的同时我们应该进行数据的验证工作!

加密原理及流程

1、从服务器端获取一个唯一性的token,我们称之为 accessToken;
2、前端对accessToken进行随机性拆分及md5加密,产生签名(保存在本地存储中);
3、前端在与后端进行交互时传递签名;
4、后端接收数据是验证签名;

签名准备

1、在 commons 文件夹内创建

1.1 md5.js //js md5 加密 [ 在课程内获取此 js文件 ]
1.2 sign.js // 签名函数
sign.js

var md5 = require('./md5.js');
module.exports = {
    sign : function(apiServer){
        // 环境判断非uni环境不支持
        if(!uni){return '...';}
        // 连接服务器获取一个临时的accessToken
        uni.request({
            url: apiServer+'getAccessToken',
            method: 'GET',
            success: res => {
                if(res.data.status != 'ok'){return ;}
                var data = res.data.data;
                // 对 accessToken 进行md5加密
                var accessToken = md5.hex_md5(data.token + data.time);
                // 签名 = md5(accessToekn + time) + '-' + 'accessToekn';
                var sign = accessToken + '-' + data.token;
                //console.log(sign);
                // 记录在本地
                uni.setStorage({
                    key:"sign",
                    data:sign
                });
            }
        });
    }
}

数据库

DROP TABLE IF EXISTS `yuedu_access_tokens`;
CREATE TABLE `yuedu_access_tokens` (
  `token` varchar(30) NOT NULL,
  `time` int(11) DEFAULT NULL,
  PRIMARY KEY (`token`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

php 端代码

<?php
//getAccessToken.php
namespace hsC;
class getAccessToken{
    public function index(){
        $db = \hsTool\db::getInstance('access_tokens');
        $token = array(
            'token' => uniqid(),
            'time'  => time()
        );
        $db->add($token);
        exit(jsonCode('ok', $token));
    }
}

使用说明
在数据提交页面提交之前进行预签名,提交数据时携带此签名!

更合理的签名保存
因为大家后端基础不一样,本课程使用数据库保存了accessToken,更好的方式是 redis 或 memcache,可以设置变量有效期并能自动失效!

九、在登录环节使用签名验证策略

后端验证签名原理

// 前端使用签名验证

<script>
var sign= require('../../commons/sign.js');//引入签名文件
……
onLoad:function(options){
sign.sign(this.apiServer); //使用生成签名
……
var sign = uni.getStorageSync('sign');//获取签名
……
sign   : sign, // 提交签名

function checkSign(){
    if(empty($_POST['sign'])){exit(jsonCode('error', 'sign error'));}
    $sign = explode('-', $_POST['sign']);
    if(count($sign) != 2){exit(jsonCode('error', 'sign error'));}
    $db = \hsTool\db::getInstance('access_tokens');
    $token = $db->where('token = ?', array($sign[1]))->fetch();
    if(empty($token)){exit(jsonCode('error', 'sign error'));}
    $signMd5 = md5($token['token'].$token['time']);
    if($signMd5 != $sign[0]){exit(jsonCode('error', 'sign error'));}
    // 验证成功则删除
    $db->where('token = ?', array($sign[1]))->delete();
}
登录页面签名机制改进
<template>
    <view>
        <!-- #ifdef MP-WEIXIN -->
        <button type="primary" open-type="getUserInfo" @getuserinfo="getUserInfo">使用微信登录</button>
        <!-- #endif -->
    </view>
</template>
<script>
var _self, pageOptions, session_key, openid;
var sign = require('../../commons/sign.js');
export default {
    data() {
        return {
            
        };
    },
    
    methods:{
        // #ifdef MP-WEIXIN
        getUserInfo : (info) => {
            info = info.mp.detail.userInfo;
            //userInfo {"nickName":"深海","gender":1,...avatarUrl":"https://7tdPvkPaJlkaLFFbLAffGVApluZdanLkDVplNlAhq1EJA/132"}
            // 与服务器交互将数据提交到服务端数据库
            var sign = uni.getStorageSync('sign');
            uni.request({
                url: _self.apiServer+'member&m=login',
                method: 'POST',
                header: {'content-type' : "application/x-www-form-urlencoded"},
                data: {
                    openid : openid,
                    name   : info.nickName,
                    face   : info.avatarUrl,
                    sign   : sign
                },
                success: res => {
                    console.log(res);
                    res = res.data;
                    // 登录成功 记录会员信息到本地
                    if(res.status == 'ok'){
                        uni.showToast({title:"登录成功"});
                        uni.setStorageSync('SUID' , res.data.u_id + '');
                        uni.setStorageSync('SRAND', res.data.u_random + '');
                        uni.setStorageSync('SNAME', res.data.u_name + '');
                        uni.setStorageSync('SFACE', res.data.u_face + '');
                        // 跳转
                        if(pageOptions.backtype == 1){
                            uni.redirectTo({url:pageOptions.backpage});
                        }else{
                            uni.switchTab({url:pageOptions.backpage});
                        }
                    }else{
                        uni.showToast({title:res.data});
                    }
                },
                fail: (e) => {
                    console.log(JSON.stringify(e));
                }
            });
            
        },
        // #endif
    },
    onLoad:function(options){
        // 预先签名
        sign.sign(this.apiServer);        
        _self = this;
        pageOptions = options;
        // #ifdef MP-WEIXIN
        // 调用 微信 login 获取 code
        uni.login({
            success: (res) => {
                uni.request({
                    url:_self.apiServer+'member&m=codeToSession&code='+res.code,
                    success: (sessions) => {
                        // sessions.date 数据格式
                        // expires_in:7200
                        // openid:"oS6of0V0rdp9nY_BuvCnQUasOHYc"
                        // session_key:"87sE2oDD8lc+aDJj0tB6+g=="
                        // 获取 unionId
                        session_key = sessions.data.session_key;
                        openid      = sessions.data.openid;
                    },
                });
            }
        });
        // #endif
        //app 端微信登录
        // 手册位置 https://uniapp.dcloud.io/api/plugins/login?id=getuserinfo
        // #ifdef APP-PLUS
        uni.login({
            success: (res) => {
                // res 对象格式
                //{"code":"***",
                //"authResult":{
                    //"openid":"***",
                    //"scope":"snsapi_userinfo",
                    //"refresh_token":"**",
                    //"code":"****",
                    //"unionid":"***",
                    //"access_token":"***",
                    //"expires_in":7200
                //},
                //"errMsg":"login:ok"}
                uni.getUserInfo({
                    success: (info) => {
                        // info 对象格式
                        // {"errMsg":"getUserInfo:ok",
                        // "rawData":"...
                        // "userInfo":{
                            //"openId":"***",
                            //"nickName":"***",
                            //"gender":1,
                            // "city":"Xi'an",
                            // "province":"Shaanxi",
                            // "country":"China",
                            // "avatarUrl":"头像",
                            // "unionId":"oU5Yyt_agt547zWyA0v0eLfFPqxo"
                        //},"signature":""}
                        // 与服务器交互将数据提交到服务端数据库
                        var sign = uni.getStorageSync('sign');
                        uni.request({
                            url: _self.apiServer+'member&m=login',
                            method: 'POST',
                            header: {'content-type' : "application/x-www-form-urlencoded"},
                            data: {
                                openid : info.userInfo.openId,
                                uni.setStorageSync('SRAND', res.data.u_random + '');
                                        uni.setStorageSync('SNAME', res.data.u_name + '');
                                face   : info.userInfo.avatarUrl,
                                sign   : sign
                            },
                            success: res => {
                                console.log(JSON.stringify(res));
                                res = res.data;
                                // 登录成功 记录会员信息到本地
                                if(res.status == 'ok'){
                                    uni.showToast({title:"登录成功"});
                                    uni.setStorageSync('SUID' , res.data.u_id + '');
                                    uni.setStorageSync('SRAND', res.data.u_name + '');
                                    uni.setStorageSync('SFACE', res.data.u_face + '');
                                    // 跳转
                                    if(options.backtype == 1){
                                        uni.redirectTo({url:options.backpage});
                                    }else{
                                        uni.switchTab({url:options.backpage});
                                    }
                                }else{
                                    uni.showToast({title:res.data});
                                }
                            },
                            fail: (e) => {
                                console.log(JSON.stringify(e));
                            }
                        });
                    },
                    fail: () => {
                        uni.showToast({title:"微信登录授权失败"});
                    }
                })
            },
            fail: () => {
                uni.showToast({title:"微信登录授权失败"});
                uni.hideLoading();
            }
        })
        // #endif
    }
}
</script>
<style></style>

十、完成写作页面布局

页面功能

1、基础布局
2、文章内容展示
3、添加图片
4、添加文本
5、动态展示提交按钮

完整代码

<template>
    <view class="wrap">
        <view class="write_title">
            <input type="text" v-model="title" placeholder="请输入标题" />
        </view>
        <!-- 内容展示区 -->
        <view class="artList">
            <block v-for="(item, index) in artList" :key="index">
                <view class="item" v-if="item.type == 'image'"><image :src="item.content" :data-index="index" mode="widthFix" @tap="removeImg" /></view>
                <view class="item" v-if="item.type == 'text'">
                    <textarea :value="item.content" placeholder="" v-model="artList[index].content" />
                    <view :data-index="index" class="deleteText" @tap="deleteText">删除</view>
                </view>
            </block>
        </view>
        <!-- 输入区 -->
        <form @submit="submit">
            <view class="inputArea">
                <view class="addImg" @tap="addImg">+图片</view>
                <view class="addText">
                    <textarea name="artText" maxlength="-1" v-model="inputContent" placeholder="请输入文本" />
                    <button type="primary" form-type="submit">添加</button>
                </view>
            </view>
        </form>
        <!-- 选择分类 -->
        <view class="art-cate">
            <view>文章分类</view>
            <view class="">
                <picker mode="selector" :range="caties" @change="cateChange">
                    <view>{{caties[currentCateIndex]}}</view>
                </picker>
            </view>
        </view>
        <!-- 提交按钮 -->
        <view class="submitNow" v-if="artList.length > 0" @tap="submitNow">发布文章</view>
    </view>
</template>
<script>
var _self, loginRes;
var signModel = require('../../commons/sign.js');
export default {
    data() {
        return {
            title : '',
            artList : [],
            inputContent : "",
            needUploadImg : [],
            uploadIndex : 0,
            //分类
            caties : ['点击选择'],
            currentCateIndex : 0,
            catiesFromApi : [],
            // 记录真实选择的api接口数据的分类id
            sedCateIndex  : 0
        };
    },
    onLoad : function() {
        _self = this;
        signModel.sign(this.apiServer);
        loginRes = this.checkLogin('../write/write', '2');
        if(!loginRes){return false;}
        // 加载文章分类
        uni.request({
            url: this.apiServer+'category&m=index',
            method: 'GET',
            success: res => {
                if(res.data.status == 'ok'){
                    // 把数据格式整理为 picker 支持的格式 ['分类名', ...]
                    var categories = res.data.data;
                    for(var k in categories){
                        _self.caties.push(categories[k]);
                    }
                    // 记录分类信息
                    _self.catiesFromApi = categories;
                }
            }
        });
    },
    methods:{
        cateChange : function(e){
            var sedIndex          = e.detail.value;
            this.currentCateIndex = sedIndex;
            // 获取选择的分类名称
            if(sedIndex < 1){return ;}
            var cateName = this.caties[sedIndex];
            for(var k in this.catiesFromApi){
                if(cateName == this.catiesFromApi[k]){
                    this.sedCateIndex = k;
                    break;
                }
            }
            this.currentCateIndex = sedIndex;
        },
        removeImg : function(e){
            var index = e.currentTarget.dataset.index;
            uni.showModal({
                content:"确定要删除此图片吗",
                title:'提示',
                success(e) {
                    if (e.confirm) {
                        _self.artList.splice(index, 1);
                    }
                }
            });
        },
        deleteText : function(e){
            var index = e.currentTarget.dataset.index;
            uni.showModal({
                content:"确定要删除吗",
                title:'提示',
                success(e) {
                    if (e.confirm) {
                        _self.artList.splice(index, 1);
                    }
                }
            });
        },
        submitNow : function(){
            // 数据验证
            if(this.title.length < 2){uni.showToast({title:'请输入标题', icon:"none"}); return ;}
            if(this.artList.length < 1){uni.showToast({title:'请填写文章内容', icon:"none"}); return ;}
            if(this.sedCateIndex < 1){uni.showToast({title:'请选择分类', icon:"none"}); return ;}
            // 上传图片 一次一个多次上传 [ 递归函数 ]
            // 上传完成后整体提交数据
            // 首先整理一下需要上传的图片
            // this.needUploadImg = [{tmpurl : 临时地址, index : 数据索引}]
            this.needUploadImg = [];
            for(var i = 0; i < this.artList.length; i++){
                if(this.artList[i].type == 'image'){
                    this.needUploadImg.push({"tmpurl" : this.artList[i].content , "indexID" : i});
                }
            }
            this.uploadImg();
        },
        uploadImg : function(){
            // 如果没有图片 或者已经上传完成 则执行提交
            if(this.needUploadImg.length < 1 || this.uploadIndex >=  this.needUploadImg.length){
                uni.showLoading({title:"正在提交"});
                // 将信息整合后提交到服务器
                var sign = uni.getStorageSync('sign');
                uni.request({
                    url: this.apiServer + 'art&m=add',
                    method: 'POST',
                    header: {'content-type' : "application/x-www-form-urlencoded"},
                    data: {
                        title   : _self.title,
                        content : JSON.stringify(_self.artList),
                        uid     : loginRes[0],
                        random  : loginRes[1],
                        cate    : _self.sedCateIndex,
                        sign    : sign
                    },
                    success: res => {
                        console.log(res);
                        if(res.data.status == 'ok'){
                            uni.showToast({title:"提交成功", icon:"none"});
                            _self.artList = [];
                            // 清空数据
                            signModel.sign(_self.apiServer);
                            // 防止数据缓存
                            _self.currentCateIndex = 0;
                            _self.sedCateIndex     = 0;
                            _self.needUploadImg    = [];
                            _self.title            = '';
                            setTimeout(function(){
                                uni.switchTab({
                                    url:'../my/my'
                                })
                            }, 1000);
                        }else{
                            uni.showToast({title:res.data.data, icon:"none"});
                        }
                    },
                    fail: (res) => {
                        
                    },
                    complete: () => {
                        
                    }
                });
                return ;
            }
            // 上传图片
            uni.showLoading({title:"上传图片"});
            var uploader = uni.uploadFile({
                url      : _self.apiServer+'uploadImg&m=index',
                filePath : _self.needUploadImg[_self.uploadIndex].tmpurl,
                name     : 'file',
                success: (uploadFileRes) => {
                    uploadFileRes = JSON.parse(uploadFileRes.data);
                    if(uploadFileRes.status != 'ok'){
                        console.log(uploadFileRes);
                        uni.showToast({title:"上传图片失败,请重试!", icon:"none"});
                        return false;
                    }
                    // 将已经上传的文件地址赋值给文章数据
                    var index = _self.needUploadImg[_self.uploadIndex].indexID;
                    _self.artList[index].content = _self.staticServer + uploadFileRes.data;
                    console.log(_self.artList); 
                    _self.uploadIndex ++;
                    // 递归上传
                    setTimeout(function(){_self.uploadImg();}, 1000);
                },
                fail: () => {
                    uni.showToast({title:"上传图片失败,请重试!", icon:"none"});
                }
            })
        },
        submit : function(res){
            var content = res.detail.value.artText;
            if(content.length < 1){uni.showToast({title:"请输入内容",icon:'none'}); return ;}
            this.artList.push({"type":"text", "content" : content});
            this.inputContent = '';
        },
        addImg : function(){
            uni.chooseImage({
                count: 1,
                sizeType: ['compressed'],
                success: function(res) {
                    _self.artList.push({"type":"image", "content" : res.tempFilePaths[0]})
                }
            })
        }
    }
}
</script>
<style></style>

分类数据表

DROP TABLE IF EXISTS `yuedu_categories`;
CREATE TABLE `yuedu_categories` (
  `cate_id` int(10) NOT NULL AUTO_INCREMENT,
  `cate_pid` int(10) DEFAULT '0',
  `cate_name` varchar(50) DEFAULT NULL,
  `cate_order` int(10) DEFAULT NULL,
  PRIMARY KEY (`cate_id`),
  KEY `cate_pid` (`cate_pid`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of yuedu_categories
-- ----------------------------
INSERT INTO `yuedu_categories` VALUES ('1', '0', '美文', '1');
INSERT INTO `yuedu_categories` VALUES ('2', '0', '互联网', '2');
INSERT INTO `yuedu_categories` VALUES ('3', '0', '汽车', '3');

后端代码

<?php
namespace hsC;
class category{
    public function index(){
        $_GET['pid'] = empty($_GET['pid']) ? 0 : intval($_GET['pid']); //分析pid没有的话为0
        $db = \hsTool\db::getInstance('categories'); //连接数据表
        if(empty($_GET['pid'])){
            $categories = $db->order('cate_order asc')->fetchAll();//没有pid就查询所有分类
        }else{
            $categories = $db->order('cate_order asc')->where('cate_pid = ?', array($_GET['pid']))->fetchAll();
        }//有pid就查询子分类
        if(empty($categories)){exit(jsonCode('empty', ''));}//如果是空的就输出空
        $caties = array();
        foreach($categories as $cate){
            $caties[$cate['cate_id']] = $cate['cate_name']; //如果不是空就把数据传入数组caties
        }
        exit(jsonCode('ok', $caties));//输出caties
    }
}

后续代码可查看

http://www.hcoder.net/tutorials/info_1380.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容