fastadmin-微信小程序实战课程:todolist项目文档(课件)整理汇总

网址整理

视频教程地址:

https://www.ixigua.com/7017702820048536071

后端接口仓库地址:

https://gitee.com/maoshushare/fast-todolist

第一节 项目计划和准备工作

项目简介

本项目是一个fastadmin+微信小程序的实战案例,一个简单的todolist任务管理功能,包括后台数据增删改查、数据统计、api接口开发(小程序对接登录、小程序端管理数据)等。

功能比较简单,覆盖到的知识点不会太多,适合初学者,尤其适合没有做过小程序和fa结合使用的同学。

为便于大家利用碎片时间学习,每节课的时间会尽量控制在10分钟左右。

效果截图

本项目是模仿滴答清单的部分功能,大概界面是这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vrVqSwq-1641880230864)(https://cdn.jsdelivr.net/gh/978165754/picgo@master/blog/20211011104255.png)]

项目计划

本项目制作思路:

  • 1.先制作基础的后端api部分。
  • 2.再制作小程序界面。
  • 3.小程序对接api,打通基本功能。
  • 4.缺少的api一边补充一边对接。

准备工作

  • 1.准备工具:VsCode(或phpStorm)、phpStudy、chrome浏览器(或Edge)、微信小程序开发工具、apipost
  • 2.下载fastadmin框架代码(https://gitee.com/maoshushare/fastmodel)
  • 3.导入mysql数据库,配置环境,启动项目

第二节 登录和自动注册api

验证字段必填

先引入Validate,use think\Validate;

$rule = [
    'openid'  => 'require|length:10,30'
];

$msg = [
    'openid.require' => '缺少openid',
    'openid.length' => 'openid长度不符合',
];

$v = new Validate($rule,$msg);


登录和注册

如果已经存在,则登录,不存在则先注册,然后再自动登录

api相关的拓展教程:https://www.bilibili.com/video/BV1Ji4y1V7ZV?p=11

$post=$this->request->param();
if (!$v->check($post)) {
    $this->error('登录失败:' . $v->getError());
}
$u = model('admin/User')->where('openid',$post["openid"])->find();
if($u){
    Token::clear($u["id"]);
    $this->auth->direct($u["id"]);
    $this->success('登录成功', $this->auth->getUserinfo());
}
else{
    $username = $post["openid"];
    // 初始密码给一个随机数
    $password = Random::alnum(15);
    $this->auth->register($username,$password,'','',[
        "openid"=>$post["openid"]
    ]);
    $this->success('注册成功', $this->auth->getUserinfo());
}

第三节 创建数据表 生成crud

创建数据表:todo

字段:
userid task status tags prio desc
todotime
donetime
giveuptime
createtime updatetime deletetime

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNJbJLnN-1641880230868)(https://cdn.jsdelivr.net/gh/978165754/picgo@master/blog/20211012141903.png)]

一键生成CRUD

fastadmin-微信小程序实战课程:todolist项目文档(课件)整理汇总_第1张图片

第四节 编写增删改查api

新建一个控制器Xcxtodo,专门写关于任务控制的api

增加 和 修改

增加和修改写在一起,方便维护

增加

$this->model
->allowField(true)
->save([
    "userid"=>$this->uid,
    "tags"=>"测试",
    "task"=>"测试3331232233",
]);

修改

$this->model
->allowField([
  "task"
])
->save([
    "task"=>"89898",
],["id"=>20]);

删除

根据任务id删除(软删除)

关于软删除的文档说明:https://www.kancloud.cn/manual/thinkphp5/189658

$this->model->get($id)->delete();

$this->model->destroy($id);

查询

  • 根据关键词查询任务内容(模糊查询)

    $task = input("task");
    $where=[
        "userid"=>$this->uid,
    ];
    if($task){
        $where["task"]=["like","%$task%"];
    }
    $list = $this->model->where($where)->select();
    

    处理标签id为标签文字

    $tags = model('admin/Todotags')->where([
          "userid"=>$this->uid,
        ])->column('tags','id');
    
    foreach ($list as $key => &$value) {
      $tagsArr = explode(',',$value["tags"]);
    
      $tagsNameArr = [];
      foreach ($tagsArr as $id) {
        $tagsNameArr[] = $tags[$id] ?? $id;
      }
    
      $value["tagsName"] = implode(',',$tagsNameArr);
    }
    
  • 根据关键词查询标签

    $tags = input("tags");
    $where=[
        "userid"=>$this->uid,
    ];
    if($tags){
        $where["tags"]=["like","%$tags%"];
    }
    $list = $this->model->where($where)->group("tags")->column("tags");
    

    改为专表之后:

    $tags = input("tags");
    $where=[
        "userid"=>$this->uid,
    ];
    if($tags){
        $where["tags"]=["like","%$tags%"];
    }
    $list = model('admin/Todotags')->where($where)->select();
    
  • 根据标签查询任务

    $tags = input("tags");
    $where=[
        "userid"=>$this->uid,
    ];
    if($tags){
        $where["tags"]=$tags;
    }
    $list = $this->model->where($where)->select();
    

    改为专表之后:
    注意exp的写法(https://www.kancloud.cn/manual/thinkphp5/135182)

      $tags = input("tags");
      $where=[
          "userid"=>$this->uid,
      ];
      
      $list = $this->model
      ->where($where);
      if($tags){
          $list=$list->where('','exp', "instr(CONCAT( ',', tags, ',' ),  ',".$tags.",' )");
      }
      
      $list = $list->select();
    

    拓展知识instr:

    INSTR(str,substr)
    str:从哪个字符串中搜索
    substr:要搜索的子字符串
    instr()函数不区分大小写

  • 按日期分组:
    核心:时间查询( https://www.kancloud.cn/manual/thinkphp5/165789 )

已过期的 (status=1 && todotime 在当前时间之前)
根据自己的项目实际需求来做,可以是当前时间以前,也可以是当前日期以前

$where=[
          "userid"=>$this->uid,
          "status"=>1,
      ];
$list =$this->model->where($where)->whereTime('todotime','<',time())->select();

今天的

$list =$this->model->where($where)->whereTime('todotime','d')->select();

明天的

$day = date('Y-m-d',strtotime("+1 day"));
$list =$this->model->where($where)->whereTime('todotime', 'between', [$day.' 00:00:00', $day.' 23:59:59'])->select();

后天到7日内

$day1 = date('Y-m-d',strtotime("+2 day"));
$day2 = date('Y-m-d',strtotime("+7 day"));
$list =$this->model->where($where)->whereTime('todotime', 'between', [$day1.' 00:00:00', $day2.' 23:59:59'])->select();

更久以后

$day2 = date('Y-m-d',strtotime("+7 day"));
$list =$this->model->where($where)->whereTime('todotime', '>', $day2.' 23:59:59')->select();

无计划日期的 (status=1 && todotime为空)

$where=[
          "userid"=>$this->uid,
          "status"=>1,
      ];
$list =$this->model->where($where)->whereNull('todotime')->select();

已完成的 (status=2)

$where=[
          "userid"=>$this->uid,
          "status"=>2,
      ];
$list =$this->model->where($where)->select();

已放弃的 (status=3)

$where=[
          "userid"=>$this->uid,
          "status"=>3,
      ];
$list =$this->model->where($where)->select();

安全优化

修改和删除限制只能操作当前用户自己的数据

修改数据

着重分析下update和save的区别(https://www.kancloud.cn/manual/thinkphp5/135189)

update方法:allowField无效,不会更新时间

$res = $this->model
    // ->allowField(true) //无效
    ->where(["id"=>$id,"userid"=> $this->uid])
    ->update($data);

save方法:isUpdate(true)

if($info = $this->model->get(['id'=>$id,'userid'=>$this->uid])){
    $res = $info
    ->allowField(true)
    ->isUpdate(true)
    ->save($data);
}
else{
    $this->error($type."失败,未找到数据");
}

删除数据

$type = "删除".$id."数据";
if($info = $this->model->get(['id'=>$id,'userid'=>$this->uid])){        
    $res = $info->delete();
}
else{
    $this->error($type."失败");
}

if($res){
    $this->success($type."成功");
}
else{
    $this->error($type."失败");
}

第五节 小程序

小程序准备工作

  • 1.新建项目
  • 2.引入UI库(vant)
    https://youzan.github.io/vant-weapp/#/quickstart
  • 3.调试ui库看是否正常引入

小程序界面

先简单搭建下界面,不需要太完善

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9MwxdUK-1641880230872)(https://cdn.jsdelivr.net/gh/978165754/picgo@master/blog/20211017152953.png)]


<van-cell icon="todo-list-o"  border="{{ false }}">
  <view slot="title">
    <view class="van-cell-text">收集箱view>
    
    
  view>
  <van-icon  slot="right-icon" class="mr-10" name="clock-o"/>
  <van-icon slot="right-icon"  name="ellipsis" />
van-cell>

<van-cell-group  border="{{ false }}">
  <van-field
    value="{{ sms }}"
    center
    clearable
    label=""
    placeholder="+ 添加任务到收集箱,回车即可保存"
    border="{{ false }}"
    use-button-slot
    class="ccc"
  >
   <view slot="right-icon">
    <van-icon name="clock-o" class="mr-10"/>
    <van-icon name="ellipsis" />
  view>
  van-field>
van-cell-group>

<van-collapse value="{{ activeNames }}" bind:change="onChange"  border="{{ false }}">
  <van-collapse-item name="1"  border="{{ false }}">
    <view slot="title">已过期<text class="smalltext">4text>view>
    

    
    <van-cell border="{{ true }}">
      <view slot="title">
        <van-checkbox value="{{ checked }}" bind:change="onChange">
          复选框
        van-checkbox>
        
      view>
     <view slot="right-icon">
        <van-tag round type="success">标签van-tag>
        <text class="mr-10 ml-10">10-18text>
        <van-icon name="ellipsis" />
      view>
    van-cell>
    
    <van-cell border="{{ true }}">
      <view slot="title">
        <van-checkbox value="{{ true }}" bind:change="onChange">
          复选框
        van-checkbox>
        
      view>
      <view slot="right-icon" style="font-size: 28rpx;">
        10-18       
      view>
      <van-icon slot="right-icon"  name="ellipsis" />
    van-cell>

  van-collapse-item>
van-collapse>

app.json引入组件

    "van-button": "@vant/weapp/button/index",
    "van-field": "@vant/weapp/field/index",
    "van-cell": "@vant/weapp/cell/index",
    "van-cell-group": "@vant/weapp/cell-group/index",
    "van-tag": "@vant/weapp/tag/index",
    "van-icon": "@vant/weapp/icon/index",
    "van-collapse": "@vant/weapp/collapse/index",
    "van-collapse-item": "@vant/weapp/collapse-item/index",
    "van-checkbox": "@vant/weapp/checkbox/index",
    "van-checkbox-group": "@vant/weapp/checkbox-group/index"

第六节 小程序对接api

小程序开发文档:https://developers.weixin.qq.com/miniprogram/dev/framework/
申请测试号:https://developers.weixin.qq.com/miniprogram/dev/devtools/sandbox.html

登录

1.登录授权,获取openid
wx.login ( https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html )
获取用户信息

let u = wx.getStorageSync('userInfo')
    if(u){
      console.log(1,u);
      this.setData({
        userInfo: u,
        hasUserInfo:true
      })

      this.login(u)
    }
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
  getUserProfile(e) {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        console.log("用户信息",res);
        wx.setStorageSync('userInfo', res.userInfo)
        this.login(res.userInfo)
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
  },

2.根据code获取openid,并且对接登录接口

login(u){
    wx.login({
      success (res) {
        console.log(res);
        if (res.code) {
          //发起网络请求
          wx.request({
            url: 'http://todo.tt/api/xcxuser/getopenid',
            data: {
              code: res.code,
              nickName:u.nickName
            },
            success :(res)=>{
              console.log(res);
            },
            fail:(err)=>{
              console.log(err);
            }
          })
          
        } else {
          console.log('登录失败!' + res.errMsg)
        }
      }
    })
  }
})

接口:


  public function getopenid($code)
  {
    $url = "https://api.weixin.qq.com/sns/jscode2session?appid=$this->AppID&secret=$this->AppSecret&js_code=$code&grant_type=authorization_code";

    $res = Http::get($url);

    $this->success('成功',['r'=>$this->request->param(),"res"=>$res]);
  }

全局变量 token

globalData: {
    userInfo: null,
    token:null,
    apidomain:'http://todo.tt/api'
}

封装request方法

request(url,data,callback){
  wx.request({
    url: gb.apidomain + url,
    data: data,
    dataType: 'json',
    enableCache: true,
    enableHttp2: true,
    enableQuic: true,
    header: {
      token:gb.token
    },
    method: "POST",
    // responseType: 'json',
    success: (result) => {
      callback(result)
    },
    fail: (res) => {
      console.error(res)
    },
    complete: (res) => {},
  })
}

新增

bind:confirm=“confirm_add”

  confirm_add(event){
    // console.log(event.detail);
    let task = event.detail
    this.add(task)
  },
  add(task){
    if(task != ''){

      this.request('/xcxtodo/addEdit',{
        task:task
      },(res)=>{
        console.log(res); 
        wx.showToast({
          title: res.data.msg,
        })       
      })
    }
  },

查询和显示

引入util.js来格式化时间

var util = require('../../utils/util')
const formatTime = (time, option) => {
  const date = new Date(time)
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const week = date.getDay()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()
  
  //获取 年月日
  if (option == 'YY-MM-DD') return [year, month, day].map(formatNumber).join('-')

  //获取 月日
  if (option == 'MM-DD') return [month, day].map(formatNumber).join('-')

  //获取 年月
  if (option == 'YY-MM') return [year, month].map(formatNumber).join('-')

  //获取 年
  if (option == 'YY') return [year].map(formatNumber).toString()

  //获取 月
  if (option == 'MM') return  [mont].map(formatNumber).toString()

  //获取 日
  if (option == 'DD') return [day].map(formatNumber).toString()

  //获取 年月日 周一 至 周日
  if (option == 'YY-MM-DD Week')  return [year, month, day].map(formatNumber).join('-') + ' ' + getWeek(week)

  //获取 月日 周一 至 周日
  if (option == 'MM-DD Week')  return [month, day].map(formatNumber).join('-') + ' ' + getWeek(week)

  //获取 周一 至 周日
  if (option == 'Week')  return getWeek(week)

  //获取 时分秒
  if (option == 'hh-mm-ss') return [hour, minute, second].map(formatNumber).join(':')

  //获取 时分
  if (option == 'hh-mm') return [hour, minute].map(formatNumber).join(':')

  //获取 分秒
  if (option == 'mm-dd') return [minute, second].map(formatNumber).join(':')

  //获取 时
  if (option == 'hh')  return [hour].map(formatNumber).toString()

  //获取 分
  if (option == 'mm')  return [minute].map(formatNumber).toString()

  //获取 秒
  if (option == 'ss') return [second].map(formatNumber).toString()

  //默认 时分秒 年月日
  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : '0' + n
}

const getWeek = n => {
  switch(n) {
    case 1:
      return '星期一'
    case 2:
      return '星期二'
    case 3:
      return '星期三'
    case 4:
      return '星期四'
    case 5:
      return '星期五'
    case 6:
      return '星期六'
    case 7:
      return '星期日'
  }
}

module.exports = {
  formatTime: formatTime
}

获取数据,并格式化处理

getbyday(opt = {}){

    this.request('/xcxtodo/getbyday',opt,(res)=>{
  
      let datalist = res.data.data
      for (const key in datalist) {
        if (Object.hasOwnProperty.call(datalist, key)) {
          const element = datalist[key];
          element.forEach((ele,ind) => {              
            if(ele.tags){  
              datalist[key][ind].tagsnum = ele.tags.split(',').length
            }
            if(ele.todotime){  
              datalist[key][ind].todotime = util.formatTime(ele.todotime*1000,'MM-DD')
            }
          });
        }
      }
  
      this.setData({
        datalist
      })
    })
  
},

渲染数据

  groups:{
      timeout:"已过期",
      today:"今天",
      tomorrow:"明天",
      seven:"7天",
      langtime:"更久以后",
      notime:"无日期",
      timeend:"已完成",
    },
    activeNames: ['timeout','today'],
  <block wx:for="{{groups}}" wx:if="{{datalist[ind].length>0}}" wx:key="ind" wx:for-item="itm" wx:for-index="ind">
    <van-collapse-item name="{{ind}}">
      <view slot="title">{{itm}}<text class="smalltext">{{datalist[ind].length}}text>view>
      
      <block wx:for="{{datalist[ind]}}" wx:key="id">
        <van-cell>
          <view slot="title">
            <van-checkbox value="{{ checked }}" bind:change="onChange">{{item.task}}van-checkbox>
            
          view>
          <view slot="right-icon">
            <van-tag round type="success" wx:if="{{item.tags_number}}">{{item.tags_number}}van-tag>
            <text class="mr-10 ml-10" wx:if="{{item.todotime}}">{{item.todotime}}text>
            <van-icon name="ellipsis" />
          view>
        van-cell>
      block>
      
    van-collapse-item>
 
  block>

优化add方法:新增完数据,重新渲染数据

  const { data } = res
  if(data.code === 1){
    this.getbyday()
  }

修改

状态修改为已处理或待处理

修改的方法:

edit(id,data){
    const param={...data,id}
    this.request('/xcxtodo/addEdit',param,(res)=>{
      // console.log(res); 
      const { data } = res
      if(data.code === 1){
        this.getbyday()
      }
      wx.showToast({
        title: data.msg,
      })       
    })
  },

getbyday方法优化(格式化完成时间和选中状态)

if(ele.donetime){
  datalist[key][k]["donetime"] = util.formatTime(ele.donetime*1000,'MM-DD')
  datalist[key][k]["checked"] = true
}

增加选中状态,增加data数据绑定

<van-checkbox value="{{ item.checked }}" data-id="{{item.id}}" data-ind="{{ind}}" data-index="{{index}}" bind:change="onCheckChange">{{item.task}}van-checkbox>            

点击checkbox选项时切换状态

onCheckChange(event){
    console.log(event);
    const {index,ind,id} = event.currentTarget.dataset
    const {datalist} = this.data
    datalist[ind][index].checked =  event.detail
    this.edit(id,{
      status:event.detail===true?2:1
    })
    this.setData({
      datalist
    });
}

删除

1.首先加一个点击右侧按钮弹出编辑项

<van-icon name="ellipsis" bind:click="showEditPopup" data-ind="{{ind}}"  data-index="{{index}}" />

2.弹出层放在右侧,里面放几个选项

<van-popup show="{{ showPopup }}" position="right" custom-style="height: 100%" bind:close="onClosePopup">
  <van-cell title="删除" icon="delete-o" bind:click="delete"/>
  <van-cell title="标签" icon="location-o" />
  <van-cell title="改日期" icon="clock-o" />
van-popup>

3.写对应的js方法

showEditPopup(event){
    this.setData({
      showPopup : true
    })
    
    const {index,ind} = event.currentTarget.dataset
    const {datalist} = this.data
    this.setData({
      editItem:datalist[ind][index]
    })

},

onClosePopup(){
  this.setData({
    showPopup : false,
    editItem:null
  })
},
delete(){
    console.log('编辑的是',this.data.editItem);
    const {id} = this.data.editItem

    this.request('/xcxtodo/delete',{id},(res)=>{
      // console.log(res); 
      const { data } = res
      if(data.code === 1){
        this.getbyday()
        this.onClosePopup()
      }
      wx.showToast({
        title: data.msg,
      })       
    })
}

后续内容将在个人博客上更新:https://maoshu.fun/posts/45372c3c.html

你可能感兴趣的:(fastadmin,小程序,微信小程序,小程序,前端)