本次学习为hcoder.net提供的原始教程,本文为学习笔记(对部分做了完善和扩展),仅供各位参考!
如若学习,请参见原版教程,支持正版,谢谢!
本项目为初学者项目,功能简单,就不做项目演示站了,贴上项目图片:
项目建立,本次为学习uniapp官方教程的学习笔记:
一、建立项目、配置公共登陆函数新建--项目--uniapp项目--默认模版,进入main.js,编写公共登陆函数:Vue.config.productionTip = false
...
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 == '' || SNAME == '' || SRAND == ''){ //如果没有值,跳转登陆页
uni.redirectTo({url:'../login/login?backpage='+backpage+'&backtype='+backtype});
console.log("qqw");
return false;
}
return [SUID, SRAND, SNAME, SFACE]; //有值的话,返回值
}
...
App.mpType = 'app'新建页面,调用公共登陆函数,检查登陆:
内容页面
var loginres;
export default {
data() {
return {
}
},
onLoad() {//加载页面先判断是否登陆
//console.log(options)
var Loginres = this.checkLogin('../index/index', 2) //调用公共的登陆检查函数
console.log(Loginres);
if(!Loginres){return false;} //如果返回值不存在,返回false
},
methods: {
}
}
二、服务端代码及配置服务端源码
你可以在文末或页面右侧的资源包里下载本次项目的服务端源码,上传至服务端,并在index.php配置数据库信息
数据库
你可以使用Navicat等数据库工具,建立新的数据表,并执行以下语句,创建表字段: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;或者可以手敲php端
php原理:
这里说一下php的运行原理 ,看图
php的设计模式
MVC(models、controllers、views)和index.php
C控制器层:对不同的业务
M模型:分为数据模型和业务逻辑模型
V视图:本项目因为是前后端分离,不包含V视图层
用户首先进入index.php,进入控制器,由控制器进入视图层和模型层
控制器调用其他静态类:
以下为后端php方面代码的手记
在index.php中新建访问规则:index.php入口文件的编写(详细查看注释)<?php
/*
*oldlee
*/
//设置编码
header('content-type:text/html; charset=utf-8');
// 接口认证,每次的接口访问都带上token,token可以自定义
if(empty($_GET['token'])){exit(jsonCode('error', 'token error'));} //如果没有token,结束并token error
if($_GET['token'] != 'api2020'){exit(jsonCode('error', 'token error'));}//如果token不对,结束并token error
//定义常量
// 文件夹的定义
define("HS_DS" , DIRECTORY_SEPARATOR);
define("HS_ROOT" , dirname(__FILE__).HS_DS);
define("HS_CONTROLLERS" , HS_ROOT.'controllers'.HS_DS);
define("HS_MODELS" , HS_ROOT.'models'.HS_DS);
define("HS_TOOLS" , HS_ROOT.'tools'.HS_DS);
/* 过滤及定义 POST 减少跨站攻击的可能性*/
if(!empty($_POST)){
define("IS_POST", false);
}else{
define("IS_POST", true);
$_POST = str_replace(array('', '"', "'"),array('<','>', '"', ''), $_POST); //过滤尖括号,引号等
}
// 数据库配置
define('HS_DB_HOST' , '127.0.0.1'); // mysql 服务器地址
define('HS_DB_NAME' , '***'); // 数据库名称
define('HS_DB_USER' , 'root'); // 数据库账号
define('HS_DB_PWD' , '***'); // 数据库密码
define('HS_DB_PRE' , '***'); // 数据表统一前缀
define('HS_DB_CHARSET' , 'utf8'); // mysql 字符集类型
// 微信小程序相关设置
define('HS_APPID' , '*****');
define('HS_SECRET' , '*****');
// 自动加载各种控制器、方法和类
function hsAutoLoad($className){
$className = explode('\', $className);
if(empty($className[0])){array_shift($className);}
if(count($className) != 2){return false;}
switch($className[0]){
case 'hsModel':
$classFileName = HS_MODELS.$className[1].'.php';
break;
case 'hsTool':
$classFileName = HS_TOOLS.$className[1].'.php';
break;
}
if(empty($classFileName)){return false;}
if(is_file($classFileName)){require $classFileName;}
}
spl_autoload_register("hsAutoLoad");
// 路由解析
$_GET['c'] = empty($_GET['c']) ? 'index' : $_GET['c'];
$_GET['m'] = empty($_GET['m']) ? 'index' : $_GET['m'];
$pattern = '/^[a-zA-Z]+[0-9]*[a-zA-Z]*$/';
if(!preg_match($pattern, $_GET['c'])){$_GET['c'] = 'index';}
if(!preg_match($pattern, $_GET['m'])){$_GET['m'] = 'index';}
$controllerFileName = HS_CONTROLLERS.$_GET['c'].'.php';
if(is_file($controllerFileName)){
require $controllerFileName;
$className = '\\hsC\'.$_GET['c'];
$controller = new $className;
if(method_exists($controller, $_GET['m'])){
call_user_func(array($controller, $_GET['m']));
}
}
// json 输出
function jsonCode($status, $data){
return json_encode(array('status' => $status, 'data' => $data));
}
// 签名验证
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();
}
// 验证用户合法性
function checkUser(){
if(empty($_POST['uid'])){exit(jsonCode('error', 'uid error'));}
if(empty($_POST['random'])){exit(jsonCode('error', 'random error'));}
$db = \hsTool\db::getInstance('members');
$user = $db->where('u_id = ?', array($_POST['uid']))->fetch();
if(empty($user)){exit(jsonCode('error', 'user error'));}
if($user['u_random'] != $_POST['random']){exit(jsonCode('error', 'user error'));}
return $user;
}
在控制器或者模型方法里,即可返回对应的内容至前端,如echo和return等:前端接收和提交数据(定义在main.js中)://定义公共的api接口
var apitoken = 'api2020';
Vue.prototype.apiService = 'http://yuedu.oldlee.cn/index.php?token='+apitoken+'&c=';
三、用户登录
这里使用条件编译对各端的登陆做控制和区分,先使用uni.login获取用户基础信息,再调用uni.getUserInfo获取用户详细信息:
APP端登陆用户详细信息的获取(包含了unionid)// #ifdef APP-PLUS //条件编译
uni.login({ //获取基础信息
success: (res) => {
console.log(res);
uni.getUserInfo({ //获取详细信息
success: (info) => {
console.log(info);
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
// #endif
打印:
APP端用户登陆,并提交用户信息到服务端:
APP端用户登录的后端代码:
APP端用户登录的前端代码:// #ifdef APP-PLUS
uni.login({
success: (res) => {
console.log(res);
//-------------------------
uni.getUserInfo({
success: (info) => {
//***********************
uni.request({
url: _self.apiService+'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(res);
},
fail: () => {},
complete: () => {}
});
//***********************
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
//-------------------------
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
// #endif
服务端返回结果打印:
APP端用户登陆信息返回成功后,对用户信息缓存并跳转:
在获取服务端返回值后,哪找返回值中data中的status的值来判断是否登陆成功,为“ok”,即登陆成功:
然后,将服务端返回的用户信息进行缓存,并根据全局变量的跳转地址和方式进行跳转:success: res => {
//console.log(res); 登陆成功
//{"data":{"status":"ok","data":{"u_openid":"...","u_name":"..","u_face":"..."}
if(res.data.status == 'ok'){ //如果登陆成功
uni.showToast({title:"登录成功"});
//进行缓存
//加个空格,使其转变为字符串
uni.setStorageSync('SUID' , res.data.data.u_id + '');
uni.setStorageSync('SRAND', res.data.data.u_random + '');
uni.setStorageSync('SNAME', res.data.data.u_name + '');
uni.setStorageSync('SFACE', res.data.data.u_face + '');
//根据访问登陆页面之前的页面带过来的页面地址和跳转方式,进行跳转
if(options.backtype == 1){
uni.redirectTo({url:options.backpage});
}else{
uni.switchTab({url:options.backpage});
}
}else{ //否则直接弹出错误信息
uni.showToast({title:res.data.data.});
}
}
APP端登陆完成;微信小程序端用户登陆(后端代码同上方APP登陆的后端代码)
通过uni.login获取用户基本信息:
打开manifest.json,找到微信小程序配置,填写appid重启应用;// #ifdef MP-WEIXIN
uni.login({
success: (res) => {
console.log(res)
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
// #endif打印:
小程序端登陆用户详细信息的获取
小程序端需要通过用户点击按钮获取详细信息,在uni.login完成用户基础信息获取后,配置uni.getUserInfo至view页面按钮,用户点击后获取详细信息:
视图层的按钮:
使用微信登录
js层给按钮配置getUserInfo:methods: {
getUserInfo(){
uni.getUserInfo({ //获取用户信息
success(res) {
console.log(res)
}
})
}
}
前端获取的用户信息的打印:
这时发现,获取的登陆成功的信息是没有openid的,我们需要使用uni.login获得的code值在服务端向腾讯服务器换取用户openid:通过uni.login获取的用户code值获取用户的openid:
//定一个全局变量,接受uni.login换取的openid
var _self , wx_res , g_options;
...
// #ifdef MP-WEIXIN
uni.login({
success: (res) => {
//console.log(res)
//直接把code通过get的方式提交 的服务器,在服务端操作换取openid
uni.request({
//这里提交到member控制器的codeToSession方法,并带上code
url: _self.apiServer+'member&m=codeToSession&code='+res.code,
method: 'GET',
success: resi => {
wx_res=resi;
}
});
},
fail: () => {
uni.showToast({title:"微信登录授权失败"});
}
})
// #endif
...
服务端通过前端获取的用户code值获取用户的openid:public function codeToSession(){ //定义类
//如果没通过get传过来code,直接报错并结束
if(empty($_GET['code'])){exit(jsonCode('error', 'code error'));}
//拼接腾讯换取openid的接口地址,要加上当前小程序的AppID、Secret(已在入口文件定义:常量)和传过来的code
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=".HS_APPID.
"&secret=".HS_SECRET."&js_code=".$_GET['code']."&grant_type=authorization_code";
//调用工具类curl,这里需要开启php的扩展工具类php_curl
$curl = new \hsTool\curl();
//调用工具的get方法
$res = $curl->get($url);
echo $res;//输出结果
}
这里需要用到一个工具类:点击查看curl工具类
namespace hsTool;
class curl {
public $httpStatus;
public $curlHndle;
public $speed;
public $timeOut = 60;
public function __construct(){
$this->curlHandle = curl_init();
curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, $this->timeOut);
}
public function setopt($key, $val){
curl_setopt($this->curlHandle, $key , $val);
}
public function get($url){
curl_setopt($this->curlHandle, CURLOPT_URL , $url);
curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER , true);
curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER , false);
curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST , false);
curl_setopt($this->curlHandle, CURLOPT_ENCODING , 'gzip,deflate');
curl_setopt($this->curlHandle, CURLOPT_TIMEOUT , $this->timeOut);
$result = curl_exec($this->curlHandle);
$this->http_status = curl_getinfo($this->curlHandle);
$this->speed = round($this->httpStatus['pretransfer_time']*1000, 2);
return $result;
}
public function post($url, $data){
curl_setopt($this->curlHandle, CURLOPT_POST, 1);
curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, $data);
return $this->get($url);
}
}
返回两个值:openid和session_key,再加上前面uni.getUserInfo获得的昵称和头像等信息,就可以存储用户信息了;
微信小程序端用户登陆,提交用户信息到服务端(后端代码和返回值同APP端):
后端代码:
前端代码:getUserInfo(){
uni.getUserInfo({ //获取用户信息
success(info) {
//console.log(res)
//***********************
uni.request({
url: _self.apiService+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
openid : wx_res.data.openId,//wx_res是保存了openid的全局变量
name : info.userInfo.nickName,
face : info.userInfo.avatarUrl,
},
success: res => {
//console.log(res); 登陆成功
//{"data":{"status":"ok","data":{"u_openid":"...","u_name":"..","u_face":"..."}
if(res.data.status == 'ok'){ //如果登陆成功
uni.showToast({title:"登录成功"});
//进行缓存
//加个空格,使其转变为字符串
uni.setStorageSync('SUID' , res.data.data.u_id + '');
uni.setStorageSync('SRAND', res.data.data.u_random + '');
uni.setStorageSync('SNAME', res.data.data.u_name + '');
uni.setStorageSync('SFACE', res.data.data.u_face + '');
//根据访问登陆页面之前的页面带过来的页面地址和跳转方式,进行跳转
if(g_options.backtype == 1){
uni.redirectTo({url:g_options.backpage});
}else{
uni.switchTab({url:g_options.backpage});
}
}else{ //否则直接弹出错误信息
uni.showToast({title:res.data.data});
}
},
fail: (res) => {
console.log(res);},
complete: () => {}
});
//***********************
}
})
}
返回结果打印:
至此,登陆完成!
四、登陆扩展---unionid
打开 manifest.json ,找到APP的sdk配置,在第三方登陆里填写微信相关 appid 重启应用;
然后,根据前面的APP登陆逻辑就可以成功获取用户的unionid,但是要想实现多平台统一用户,在小程序端也还需要获取用户的unionid:
小程序获取用户的unionid需要到微信开放平台,注册账号,并完成认证(300元/年,坑~~),并将微信小程序的AppID填写到已认证的开发者账号下;
获取步骤 :1、配置小程序appid(此appid在微信开放平台已经绑定);
2、使用uni.login登录时会获取code,用code换取seesion_key;
3、在获取用户信息函数中获取到加密信息;
4、利用seesion_key及加密信息在服务端解密获取unionID
在前面微信小程序登陆的时候,调用uni.getUserInfo获取到的结果除了userinfo外,还有其他三个数据:
将iv、encryptedData取出,把这三个数据连同uni.login获取到的session_key传到服务端的member的wxaes的方法里:// #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":"..."}
//与服务器交互进行解密
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: () => {}
});
}
php端服务器代码:错误码的任意门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';
//使用session_kei和小程序AppID实例化解密函数
$pc = new \WXBizDataCrypt(HS_APPID, $_POST['session_key']);
//定义空值,保存解密结果
$data = '';
//执行解密方法,保存错误至errCode,保存解密结果至data
$errCode = $pc->decryptData($_POST['encryptedData'], $_POST['iv'], $data);
//错误码为0,表示请求成功
if ($errCode == 0) {
exit($data);
} else {
exit(jsonCode('error', $errCode));
}
}
}
返回结果打印:
最后,把上面的想服务端提交登陆信息的过程中,openid替换为unionid,即可,登陆功能完成;
五、登陆扩展---获取手机号码APP端获取用户手机号码(手册任意门):安卓端:
待完善
IOS端:
待完善
微信小程序获取用户手机号码(手册任意门):
待完善
六、API接口签名安全策略
原理(类似json web token):1、从服务器端获取一个唯一性的token,我们称之为accessToken;
2、前端对accessToken进行随机性拆分及md5加密,产生签名(保存在本地存储中);
3、前端在与后端进行交互时传递签名;
4、后端接收数据是验证签名;
准备两个js文件,存与commons中
md5.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
});
}
});
}
}
创建数据表,存放token: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
namespace hsc;
class getAccessToken{
public function index(){
//链接数据库的access_tokens表
$db = \hsTool\db::getInstance('access_tokens');
//利用php的uniqid函数时间函数自动生成字符串和时间戳
$token = array(
'token' => uniqid(),
'time' => time()
);
//将token添加到数据表,并返回给前端
$db->add($token);
exit(jsonCode('ok', $token));
}
}
完成以上工作后,我们就可以在前端页面中使用该签名方式:首先,在页面引入sign.js
然后在onLoad方法中,加载sign.js文件的sign方法,并传入当前接口请求地址,实现token的生成,并在前端进行缓存
前端每次在对服务端进行request的时候,都要在缓存中取出sign,并带给服务端
服务端在登陆函数中,在调用用户模型之前,进行签名验证
//引入签名文件
var sign = require('../../commons/sign.js');
var _self,wx_res,g_option;
export default {
...
}
var loginres;
export default {
data() {
return {
}
},
onLoad() {
//预先签名
sign.sign(this.apiService);
},
methods: {
}
}
uni.login({
//***********************
// 与服务器交互时将缓存中的签名带过去
var sign = uni.getStorageSync('sign');
uni.request({
url: _self.apiService+'member&m=login',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
...
sign : sign,//将签名带过去
},
success: res => {
...
}
});
//***********************
php服务端处理验证签名:// 签名验证
function checkSign(){
//如果签名为空,退出
if(empty($_POST['sign'])){exit(jsonCode('error', 'sign error'));}
//对签名拆分
$sign = explode('-', $_POST['sign']);
//判断签名是否是以-隔开的两段,否则退出
if(count($sign) != 2){exit(jsonCode('error', 'sign error'));}
//链接token数据表
$db = \hsTool\db::getInstance('access_tokens');
//根据前端提供的token查看,数据库是否存在
$token = $db->where('token = ?', array($sign[1]))->fetch();
//如果不存在,退出程序
if(empty($token)){exit(jsonCode('error', 'sign error'));}
//如果有的话,就对这个token和时间戳,进行md5
$signMd5 = md5($token['token'].$token['time']);
//对加密后的判断,如果签名不一致,退出程序
if($signMd5 != $sign[0]){exit(jsonCode('error', 'sign error'));}
// 验证成功则删除
$db->where('token = ?', array($sign[1]))->delete();
}
然后在php服务端的member控制器下的login方法中,登陆之前,调用此签名验证方法即可:public function login(){
checkSign();
$memberModel = new \hsModel\member();
$memberModel->login();
}
完成!
七、写作页面视图层代码(没啥可说的,都是css),关于vue知识点,详细看注释:点击这里查看视图代码
删除
+图片
添加
文章分类
{{caties[currentCateIndex]}}
发布文章
js代码,注释很清晰点击这里查看JS代码:
var sign = require('../../commons/sign.js');
var _self, loginres;
export default {
data() {
return {
title : '',//用户输入的标题
artList : [],//文章内容:图片和文本
inputContent : "",//用户输入的文本的双向绑定
needUploadImg : [],//需要上传的图片
uploadIndex : 0,//记录当前上传的图片数量
//分类
caties : ['点击选择'],//分类
currentCateIndex : 0,//分类索引
catiesFromApi : [],
// 记录真实选择的api接口数据的分类id
sedCateIndex : 0
}
},
onLoad() {//加载页面先判断是否登陆
//console.log(options)
_self = this;
//预先签名
sign.sign(this.apiService);
loginres = this.checkLogin('../write/write', 2) //调用公共的登陆检查函数
if(!loginres){return false;} //如果返回值不存在,返回false
// 加载文章分类
uni.request({
url: this.apiService+'category&m=index',
method: 'GET',
success: res => {
//console.log(res);
//1: "游戏" 2: "运动" 3: "聚会"
if(res.data.status == 'ok'){//如果返回值状态为:ok
// 把数据格式整理为数组格式: picker 支持的格式 ['分类名', ...]
var categories = res.data.data;
for(var k in categories){
//遍历值,并压入caties分类数组
_self.caties.push(categories[k]);
}
// 记录分类信息
_self.catiesFromApi = categories;
}
}
});
},
methods: {
//选择分类
cateChange : function(e){
//获取到用户在页面上点击的分类列表的索引,比如2
var sedIndex = e.detail.value;
//将它赋值给变量,便于视图层的展示:caties[currentCateIndex]
this.currentCateIndex = sedIndex;
// 判断用户所点击的分类的索引,如果小于1,就是没选,直接返回
if(sedIndex < 1){return ;}
//取到所选值的名称,比如2对应的是:运动
var cateName = this.caties[sedIndex];
// 根据所选的名称,在api接口传过来的分类数据里筛选
for(var k in this.catiesFromApi){
//如果相同,把api接口里对应值的索引,进行赋值
if(cateName == this.catiesFromApi[k]){
//找到他,并记录真实的api里该分类对应的索引
this.sedCateIndex = k;
break;
}
}
this.currentCateIndex = sedIndex;
},
//添加图片
addImg : function(){
uni.chooseImage({ //调用图片选择的函数
//一次选择一张
count: 1,
//图片形式:压缩
sizeType: ['compressed'],
success: function(res) {
// 成功后把类型和图片内容(临时文件地址)压入文章内容的数组中
_self.artList.push({"type":"image", "content" : res.tempFilePaths[0]})
}
})
},
//移除图片
removeImg : function(e){
//点击那张图删除那一张,先获取被点击元素 的索引
var index = e.currentTarget.dataset.index;
//console.log(e);
//弹出确认框
uni.showModal({
content:"确定要删除此图片吗",
title:'提示',
success(e) {
//如果用户点击确认
//e.confirm为true的话,代表用户点击了确认,cancel: true代表用户点击了取消
if (e.confirm) {
// 然后根据索引,在内容数组中,移除一个元素
_self.artList.splice(index, 1);
}
}
});
},
//添加文本
submit : function(res){
//console.log(res);
//获取到用户刚输入的值,这里的artText是在定义input框时候的name
var content = res.detail.value.artText;
//如果没有内容,提示用户并返回
if(content.length < 1){uni.showToast({title:"请输入内容",icon:'none'}); return ;}
//否则把用户提交的内容压入内容数组,并指定格式text
this.artList.push({"type":"text", "content" : content});
// 同时把输入框清空
this.inputContent = '';
},
//删除 文本
deleteText : function(e){
//获取当前所点击的文本的索引
var index = e.currentTarget.dataset.index;
//弹出提醒框
uni.showModal({
content:"确定要删除吗",
title:'提示',
success(e) {
//如果用户点击确认
//e.confirm为true的话,代表用户点击了确认,cancel: true代表用户点击了取消
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');
// 将数据内容以POST方法提交到服务端art控制器的add方法
uni.request({
url: _self.apiService + '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],//用户信息uid
random : loginres[1],//用户信息random
cate : _self.sedCateIndex,//文章分类
sign : sign //签名
},
success: res => {
console.log(res);
if(res.data.status == 'ok'){
uni.showToast({title:"提交成功", icon:"none"});
//提交成功后,清空内容数组和标题
_self.artList = [];
_self.title = '';
// 清空签名数据
sign.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) => {
console.log(res);
},
complete: () => {
}
});
return ;
}else{
//图片上传
uni.showLoading({title:"上传图片"});//先来个提示
//调用uniapp的文件上传接口,传至服务器uploadImg控制器
var uploader = uni.uploadFile({
url : _self.apiService+'uploadImg&m=index',
//上传文件一个一个上传
filePath : _self.needUploadImg[_self.uploadIndex].tmpurl,
name : 'file',
success: (uploadFileRes) => {
//上传成功返回的数据进行json格式转换
uploadFileRes = JSON.parse(uploadFileRes.data);
//如果状态不是:OK
if(uploadFileRes.status != 'ok'){
//打印返回值,并提示失败,退出
console.log(uploadFileRes);
uni.showToast({title:"上传图片失败,请重试!", icon:"none"});
return false;
}
// 将已经上传的文件地址赋值给文章数据
//将上传的图片的索引值取出来
var index = _self.needUploadImg[_self.uploadIndex].indexID;
// 将返回的已上传图片的地址,按照从内容数组中取出的图片的索引重新替换回去
// 这里返回的服务器图片地址为imgs/xx.png,所以要在前面加上域名地址_self.staticServer
_self.artList[index].content = _self.staticServer + uploadFileRes.data;
console.log('请问');
console.log(_self.artList);
//然后让图片的数量++,一直到大于等于需要上传图片的数量,停止
_self.uploadIndex ++;
// 递归上传,此方法1秒执行一次
setTimeout(function(){_self.uploadImg();}, 1000);
},
fail: () => {
uni.showToast({title:"上传图片失败,请重试!", icon:"none"});
}
})
}
}
}
}
由于上面js在加载页面时就获取了文章分类,我们需要在服务端数据库建立文章分类表: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处理分类的代码:<?php
namespace hsC;
class category{
public function index(){
//查询是否有pid传过来,如果没有,就是0
$_GET['pid'] = empty($_GET['pid']) ? 0 : intval($_GET['pid']);
//链接数据表
$db = \hsTool\db::getInstance('categories');
//如果没有pid,就认为查询所有分类
if(empty($_GET['pid'])){
$categories = $db->order('cate_order asc')->fetchAll();
}else{
//否则查询指定分类
$categories = $db->order('cate_order asc')->where('cate_pid = ?', array($_GET['pid']))->fetchAll();
}
//如果查询结果为空,就返回空
if(empty($categories)){exit(jsonCode('empty', ''));}
$caties = array();
//否则遍历查询结果为数组
foreach($categories as $cate){
$caties[$cate['cate_id']] = $cate['cate_name'];
}
//并以json的形式返回
exit(jsonCode('ok', $caties));
}
}编写服务端php处理图片上传的代码:<?php
namespace hsC;
class uploadImg{
public function index(){
//判断前端提交的文件域是否存在
if(!empty($_FILES['file'])){
//获取扩展名,即后缀
$exename = $this->getExeName($_FILES['file']['name']);
//如果后缀不在以下范围内,退出程序
if(!in_array($exename, array('png', 'gif', 'jpeg', 'jpg'))){exit(jsonCode('error', 'exename error'));}
//否则,利用存储路径、扩展名和随机生成的图片名称生成存储信息
$imageSavePath = 'imgs/'.uniqid().'.'.$exename;
//利用php函数,保存图片
if(move_uploaded_file($_FILES['file']['tmp_name'], $imageSavePath)){
exit(jsonCode('ok', $imageSavePath));
}else{
exit(jsonCode('error', 'upload error'));
}
}else{
exit(jsonCode('error', 'upload error'));
}
}
public function getExeName($fileName){
$pathinfo = pathinfo($fileName);
return strtolower($pathinfo['extension']);
}
}编写服务端php文章处理及存储的代码:先创建文章数据表:CREATE TABLE `yuedu_articles` (
`art_id` int(11) NOT NULL AUTO_INCREMENT,
`art_cate` int(10) DEFAULT NULL,
`art_title` varchar(200) DEFAULT NULL,
`art_uid` int(11) DEFAULT NULL,
`art_content` text,
`art_createtime` int(11) DEFAULT NULL,
PRIMARY KEY (`art_id`),
KEY `art_uid` (`art_uid`),
KEY `art_cate` (`art_cate`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;然后处理前端传递的数据:<?php
namespace hsC;
class art{
public function add(){
/* $_POST 格式
(
[title] => title
[content] => [
* {"type":"image","content":"http://192.168.31.188/imgs/5c174b0fb3825.png"},
* {"type":"text","content":"hi123"},
* {"type":"image","content":"http://192.168.31.188/imgs/5c174b0fc3297.png"},
* {"type":"text","content":"hi222"}]
[uid] => 8
[random] => ****
[cate] => 1
[sign] => sign
)
*/
// 先验证签名
checkSign();
// 再验证用户合法性
$user = checkUser();
// 提交主要信息,连接数据库
$dbArticles = \hsTool\db::getInstance('articles');
$addData = array(
'art_title' => $_POST['title'],
'art_uid' => $user['u_id'],
'art_cate' => intval($_POST['cate']),
'art_content' => $_POST['content'],
'art_createtime' => time()
);
//执行存储
$articleId = $dbArticles->add($addData);
if(!$articleId){exit(jsonCode('error', '服务器忙请重试'));}
// 更新会员积分(这是后面功能逻辑)
$memberDb = \hsTool\db::getInstance('members');
$memberDb->where('u_id = ?', array($user['u_id']))->filed('u_integral', 10);
exit(jsonCode('ok', 'ok'));
}
}这里用到了一个验证用户合法性的函数
// 验证用户合法性
function checkUser(){
//如果前端用户uid为空,退出
if(empty($_POST['uid'])){exit(jsonCode('error', 'uid error'));}
//如果random为空,退出
if(empty($_POST['random'])){exit(jsonCode('error', 'random error'));}
//链接数据库
$db = \hsTool\db::getInstance('members');
//按照uid取出用户
$user = $db->where('u_id = ?', array($_POST['uid']))->fetch();
//如果为空,退出程序
if(empty($user)){exit(jsonCode('error', 'user error'));}
//如果用户的random和提交过来不一致,退出程序
if($user['u_random'] != $_POST['random']){exit(jsonCode('error', 'user error'));}
//通过后,返回用户
return $user;
}
八、会员中心页面
包含文章的删除、编辑,加载更多和会员信息等视图层代码点击查看视图层代码
{{user.artCount}}
文章
{{user.u_integral}}
积分
{{user.u_remainder}}
余额
0
消息
我的文章
{{item.art_title}}
编辑
删除
{{loadMore}}
JS代码点击查看JS代码
var _self, loginRes, page = 1;
export default {
data() {
return {
myFace : '',//头像
arts : [],//该用户的文章
loadMore : "点击加载更多",
user : [] //用户信息
};
},
onLoad:function(){
_self = this;
loginRes = this.checkLogin('../my/my', '2');
if(!loginRes){return false;}
this.myFace = loginRes[3];
},
onShow:function(){
//页面每次展示的时候,加载数据
this.arts = [];//置空用户文章
page = 1;//第一页
//获取文章列表
this.getArtList();
// 加载会员信息
uni.request({
//访问服务端的my控制器的info方法,获取用户信息和文章数
url: this.apiService + 'my&m=info',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
uid : loginRes[0],
random : loginRes[1]
},
success: res => {
console.log(res);
if(res.data.status == 'ok'){
// 成功后,赋值给全局变量user
this.user = res.data.data;
}
}
});
},
methods:{
editArt : function(e){
var artId = e.currentTarget.dataset.artid;
uni.navigateTo({
url:"../editArt/editArt?artId="+artId
});
},
removeArt : function(e){
//获取用户点击的文章id和索引(在前面::data中都有指定)
var artId = e.currentTarget.dataset.artid;
var index = e.currentTarget.dataset.index;
//展示提示框
uni.showModal({
title:"提示",
content:"确定要删除吗?",
success:function(e){
if(e.confirm == true){
//用户确定后,向服务器的my控制器的removeArt方法请求
uni.request({
url: _self.apiService + 'my&m=removeArt',
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
//带过去用户信息和文章id
data: {
uid : loginRes[0],
random : loginRes[1],
artId : artId
},
success: res => {
// console.log(res);
if(res.data.status == 'ok'){
//返回成功后,如果状态是OK,代表已删除
uni.showToast({title: "已删除", icon:"none"});
//本地的文章数据表也删除一个,(实现无需刷新页面删除)
_self.arts.splice(index, 1);
}else{
uni.showToast({title: "删除失败", icon:"none"});
}
}
});
}
}
});
},
getArtList : function(){
//如果不是'点击加载更多',说明是其他状态(加载中...),就直接返回
if(this.loadMore != '点击加载更多'){return ;}
//先改变内容为加载中...
this.loadMore = '加载中...';
//访问服务端的my控制器的arts方法发起请求,并带上当前页面,第一次页面==1
uni.request({
url: this.apiService + 'my&m=arts&page='+page,
method: 'POST',
header: {'content-type' : "application/x-www-form-urlencoded"},
data: {
uid : loginRes[0],
random : loginRes[1]
},
success: res => {
console.log(res);
if(res.data.status == 'ok'){
//返回成功后,把取到的文章值赋值给arts
this.arts = this.arts.concat(res.data.data);
//页面数量加一
page++;
//恢复底部加载显示内容
this.loadMore = '点击加载更多';
}else if(res.data.status == 'empty'){
// 如果返回空,说明没取到值,或已经全部取完
this.loadMore = '已经加载全部';
}else{
this.loadMore = '点击加载更多';
}
}
});
}
}
}
.myface{width:88px; height:88px; border:5px solid #F1F2F3; border-radius:100%; margin:15px auto;}
.myface image{width:100%; border-radius:100%;}
.myart-list{width:100%; margin:8px 0; overflow:hidden; border-bottom:1px dashed #D7D8D9;}
.myart-list .title{line-height:2em; width:100%;}
.myart-list .btns{line-height:2em; width:100%;}
.myart-list .btns view{float:right; padding:0 6px; margin:0 5px; color:#00B26A;}
.myart-list .btns view:last-child{color:#F76260;}
.loadMore{width:100%; padding:8px 0; text-align:center; color:#CCCCCC;}
php后端代码点击查看php后端代码<?php
namespace hsC;
class my{
//获取用户信息
public function info(){
//用户合法性验证
$user = checkUser();
// 链接文章数据表
$dbArt = \hsTool\db::getInstance('articles');
// 根据会员uid,查询会员文章总数
$artCountNumber = $dbArt->where('art_uid = ?', array($user['u_id']))->count();
//赋值,返回
$user['artCount'] = $artCountNumber;
exit(jsonCode('ok', $user));
}
//获取用户的文章
public function arts(){
//用户合法性验证
$user = checkUser();
// 链接文章数据表
$db = \hsTool\db::getInstance('articles');
//如果前端请求中页面的值为空,就默认为一,否则取传过来的值
$page = empty($_GET['page']) ? 1 : intval($_GET['page']);
//按照用户、文章id排序取值,然后按照(page-1)*10开始取值,每次10条数据取文章内容
$arts = $db->where('art_uid = ?', array($user['u_id']))->order('art_id desc')->limit(($page - 1) * 10, 10)->fetchAll();
//如果取出的内容为空,就返回空,否则返回取出的值
if(empty($arts)){exit(jsonCode('empty', ''));}
exit(jsonCode('ok', $arts));
}
//用户删除文章
public function removeArt(){
//用户合法性验证
$user = checkUser();
//如果用户没指定文章id,退出
if(empty($_POST['artId'])){exit(jsonCode('error', 'art id error ...'));}
// 链接文章数据表
$db = \hsTool\db::getInstance('articles');
//找到用户指定文章id的文章
$art = $db->where('art_id = ?', array($_POST['artId']))->fetch();
//如果不存在,退出
if(empty($art)){exit(jsonCode('error', 'art id error ...'));}
// 如果文章的用户id不是该用户,退出
if($art['art_uid'] != $user['u_id']){exit(jsonCode('error', 'user error'));}
// 最后,开始删除
$db->where('art_id = ?', array($_POST['artId']))->delete();
// 扣除积分
$memberDb = \hsTool\db::getInstance('members');
$memberDb->where('u_id = ?', array($user['u_id']))->filed('u_integral', -10);
exit(jsonCode('ok', 'ok'));
}
}
八、文章编辑页面视图层代码代码点击查看视图代码
删除
+图片
添加
文章分类
{{caties[currentCateIndex]}}
编辑文章
JS代码点击查看JS代码
var artId, loginRes, _self;
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(options){
artId = options.artId; //现获取到需要编辑的文章的id
_self = this; //获得本页面的this
signModel.sign(this.apiService); //对本页面进行签名
loginRes = this.checkLogin('../my/my', '2'); //验证用户登陆
if(!loginRes){return false;}
// 加载文章内容
uni.request({
//向服务器的art控制器的info方法请求,带过去文章id
url: this.apiService+'art&m=info&artid='+artId,
method: 'GET',
data: {},
success: res => {
//将返回的文章内容赋值给art
var art = res.data.data;
// 文章内容转换并展示
//将内容转换为json格式,然后赋值
var artContent = art.art_content;
artContent = JSON.parse(artContent);
_self.artList = artContent;
// 赋值文章标题
this.title = art.art_title;
// 加载文章分类并设置默认值
uni.request({
//向服务端的api请求,没有带任何参数,表示查询所有分类
url: _self.apiService+'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;
// 获取当前分类的默认值
_self.sedCateIndex = art.art_cate;
// 对应的查找picker的默认值
var cateName = categories[art.art_cate];
for(var i = 0; i < _self.caties.length; i++){
if(cateName == _self.caties[i]){
_self.currentCateIndex = i;
break;
}
}
console.log(_self.currentCateIndex);
}
}
});
}
});
},
methods:{
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'){
if(this.artList[i].content.indexOf('192.168.31.') == -1){
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.apiService + 'art&m=edit&artid='+artId,
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 => {
if(res.data.status == 'ok'){
uni.showToast({title:"提交成功", icon:"none"});
setTimeout(function(){
uni.switchTab({
url:'../my/my'
})
}, 1000);
}else{
uni.showToast({title:res.data.data, icon:"none"});
}
}
});
return ;
}
// 上传图片
uni.showLoading({title:"上传图片"});
var uploader = uni.uploadFile({
url : _self.apiService+'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"});
}
})
},
cateChange : function(e){
var sedIndex = e.detail.value;
this.currentCateIndex = sedIndex;
// 获取选择的分类名称
if(sedIndex < 1){return ;}
var cateName = this.caties[sedIndex];
for(var i = 0; i < this.catiesFromApi.length; i++){
if(cateName == this.catiesFromApi[i].cate_name){
this.sedCateIndex = this.catiesFromApi[i].cate_id;
break;
}
}
this.currentCateIndex = sedIndex;
console.log(this.sedCateIndex);
},
removeImg : function(e){
console.log(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);
}
}
});
},
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]})
}
});
}
}
}
php后端代码点击查看php后端代码<?php
namespace hsC;
class art{
public function info(){
//如果前端的文章ID为空,退出
if(empty($_GET['artid'])){exit(jsonCode('error', 'art data error'));}
//将前端传过来的值转为int属性
$_GET['artid'] = intval($_GET['artid']);
//连接文章数据表,
$dbArticles = \hsTool\db::getInstance('articles');
//按照条件查询,如果为空就返回空
$art = $dbArticles->where('art_id = ?', $_GET['artid'])->fetch();
if(empty($art)){exit(jsonCode('empty', ''));}
exit(jsonCode('ok', $art));
}
public function edit(){
// 验证签名
checkSign();
// 验证用户合法性
$user = checkUser();
// 检查文章
if(empty($_GET['artid'])){exit(jsonCode('error', 'art data error'));}
$dbArticles = \hsTool\db::getInstance('articles');
$art = $dbArticles->where('art_id = ?', $_GET['artid'])->fetch();
if(empty($art)){exit(jsonCode('error', 'art data error'));}
if($art['art_uid'] != $user['u_id']){exit(jsonCode('error', 'art data error'));}
$data = array(
'art_title' => $_POST['title'],
'art_uid' => $user['u_id'],
'art_cate' => intval($_POST['cate']),
'art_content' => $_POST['content']
);
$dbArticles->where('art_id = ?', $_GET['artid'])->update($data);
exit(jsonCode('ok', 'ok'));
}
}
九、首页布局、下拉刷新、上拉加载、分类切换视图层代码点击查看视图层代码
{{cate.name}}
{{item.art_title}}
{{item.art_title}}
JS代码点击查看JS代码
var _self, cate = 0, page = 1;
export default {
data() {
return {
categories:[{cateId : 0, name : "全部"}],
cateCurrentIndex : 0,
artList:[]
}
},
onLoad() {
_self = this;
// 页面一开始,加载文章分类
uni.request({
//请求接口,不指定pid,就是请求全部分类
url: this.apiService+'category&m=index',
method: 'GET',
success: res => {
if(res.data.status == 'ok'){
// 把请求到的分类数据保存仅数组json对象中
var categories = res.data.data;
for(var k in categories){
_self.categories.push({cateId : k, name : categories[k]});
}
}
}
});
// 页面一开始,加载全部文章
this.getNewsList();
},
//下拉刷新
onPullDownRefresh: function(){
//把页面初始化为1,文章清空,重新请求数据
page = 1;
this.artList = [];
this.getNewsList();
},
// 加载更多
//直接请求数据,这时候,page的值还是保持上一次请求的状态的
onReachBottom:function(){
this.getNewsList();
},
methods: {
tabChange : function(e){
//console.log(e);
//获得用户点击了哪一个分类
var cateid = e.currentTarget.dataset.cateid;
var index = e.currentTarget.dataset.index;
//初始化page的值,从第一页开始加载
page = 1;
//把当前点击的分类的索引赋值,通过view标签的判断,给当前分类加上样式
this.cateCurrentIndex = index;
//获取
cate = cateid;
this.artList = [];
this.getNewsList();
},
getNewsList : function(){
uni.showLoading({'title':"加载中..."});
uni.request({
url: this.apiService + 'art&m=getList&cate='+cate+'&page='+page,
method: 'GET',
success: res => {
if(res.data.status == 'empty'){
uni.showToast({
title:"已经加载全部新闻",
icon: "none"
});
}else if(res.data.status == 'ok'){
//整理新闻信息
var newsList = res.data.data;
for(var i = 0; i < newsList.length; i++){
// 把图片分离出来
var imgs = [];
var content = newsList[i].art_content;
content = JSON.parse(content);
for(var ii = 0; ii < content.length; ii++){
if(content[ii].type == 'image'){
imgs.push(content[ii].content);
}
}
newsList[i].art_content = imgs;
}
//填充数据
_self.artList = _self.artList.concat(newsList);
uni.hideLoading();
page++;
}
},
//页面完成时停止下拉刷新
complete:function(){
uni.stopPullDownRefresh();
}
});
}
}
}
php后端代码点击查看php后端代码public function getList(){
$_GET['cate'] = empty($_GET['cate']) ? 0 : intval($_GET['cate']);
$_GET['page'] = empty($_GET['page']) ? 1 : intval($_GET['page']);
$dbArticles = \hsTool\db::getInstance('articles');
if(empty($_GET['cate'])){
$arts = $dbArticles->order('art_id desc')->limit(($_GET['page'] - 1) * 10, 10)->fetchAll();
}else{
$arts = $dbArticles->where('art_cate = ?', array($_GET['cate']))->order('art_id desc')->limit(($_GET['page'] - 1) * 10, 10)->fetchAll();
}
if(empty($arts)){exit(jsonCode('empty', ''));}
exit(jsonCode('ok', $arts));
}
十、内容详情页功能、骨架屏的应用
这里的骨架屏其实就是样式,判断诗句是否加出来,而决定该标签是否存在某样式视图层代码点击查看视图层代码
{{article.art_title}}
{{article.u_name}}
{{article.art_createtime}}
{{item.content}}
JS代码点击查看JS代码
var _self ;
export default {
data() {
return {
article : [], //文章基础信息
artContents : [], // 文章项目
graceSkeleton : 'ing' //定义骨架屏的默认值就是ing
};
},
onLoad:function(options){
_self = this;
//拿到用户点击的文章的id
var artid = options.artid;
// 加载文章详情
uni.showLoading({title:""});
//请求接口获取文章
uni.request({
url: this.apiService + 'art&m=infoWithUser&artid='+artid,
method: 'GET',
data: {},
success: res => {
console.log(res);
var art = res.data.data;
// 将文章内容转换成数组
var artContentItems = JSON.parse(art.art_content);
console.log(artContentItems);
// 首先规划骨架,先拿到返回数据的类型,利用类型先把页面撑起来,骨架画出来,这时候artContents里只有类型,没有数据
this.artContents = [];
for(var i = 0; i < artContentItems.length; i++){
this.artContents.push({'type': artContentItems[i].type});
}
// 延迟添加数据,然后把文章各类数据在赋值,同时撤销骨架屏
setTimeout(function(){
_self.article = art;
_self.artContents = artContentItems;
_self.graceSkeleton = 'end';
uni.hideLoading();
}, 500);
}
});
}
}
php后端代码点击查看php后端代码public function infoWithUser(){
if(empty($_GET['artid'])){exit(jsonCode('error', 'art data error'));}
$_GET['artid'] = intval($_GET['artid']);
$dbArticles = \hsTool\db::getInstance('articles');
$art = $dbArticles
->join('as a left join yuedu_members as b on a.art_uid = b.u_id')
->where('a.art_id = ?', $_GET['artid'])
->fetch('a.*, b.u_id, b.u_name, b.u_face');
if(empty($art)){exit(jsonCode('empty', ''));}
$art['art_createtime'] = date('Y-m-d H:i', $art['art_createtime']);
exit(jsonCode('ok', $art));
}
十一、内容页图片预览功能
这里的骨架屏其实就是样式,判断诗句是否加出来,而决定该标签是否存在某样式!视图层代码JS代码methods:{
showImgs : function(e){
var currentUrl = e.currentTarget.dataset.url;
// 找出图片
var imgsNeedShow = [];
for(var i = 0; i < this.artContents.length; i++){
if(this.artContents[i].type == 'image'){
imgsNeedShow.push(this.artContents[i].content);
}
}
uni.previewImage({
urls :imgsNeedShow,
current :currentUrl
});
}
}用户注销视图代码:
{{user.u_name}}注销js代码:useroff:function(){
//用户退出登陆,删除缓存
uni.removeStorageSync("SUID");
uni.removeStorageSync("SRAND");
//提示用户已经退出登陆
uni.showToast({
title:"您已经退出登陆",
icon:"none"
});
//一秒后跳转首页
setTimeout(function(){
uni.switchTab({
url:"../index/index"
})
},1000)
}
完成!