3.前台登录注册kft_vue+后台api登录注册接口kft_koa_api

200 阅读3分钟

kft_vue

  1. 创建kft_vue项目: $ vue create kft_vue;
  2. 选择配置:Manully select features => Babel,Router,Vuex,CSS Pre-processors,Linter => Y => Stylus(预处理器) => ...error => save(校验) => dedicated (单个文件存储) => N(不保存预设)
  3. 运行项目:$ cd kft_vue && yarn serve
  4. vscode设置code命令:>code;命令行使用vscode打开文件:$ code .
  5. kft_vue各文件作用:
    1. src/assets 资源文件,可打包
    2. src/components 公共组件
    3. src/views 页面级组件
    4. src/App.vue 入口文件
    5. src/main.js 主入口
    6. src/router.js 路由
    7. src/store.js vue
    8. .browserslistrc.js 浏览器兼容
    9. .eslintrc.js eslint配置
  6. 安装axios和cube-ui
    1. $ yarn add axios
    2. $ vue add cube-ui
  7. 文档
    1. cube-ui官网
    2. node版本管理工具n 3. $ n list 查看可用版本
    3. vue官网
  8. vscode安装vue快捷工具插件:vue 2 Snippets
  9. 开始写项目
  10. 首页轮播图
    1. src/views/Home.vue
    2. slider组件-轮播图组件
    3. Home.vue
<template>
  <div>
    <!-- <HomeHead></HomeHead> -->
    <cube-slide :data="items">
      <cube-slide-item v-for="(item,index) in items" :key="index+'banner'">
        <a :href="item.url">
          <img :src="item.image" alt />
        </a>
      </cube-slide-item>
      <template slot="dots" slot-scope="props">
        <span
          v-for="(item,index) in props.dots"
          :key="index"
          :class="{activate:props.current === index}"
        ></span>
      </template>
    </cube-slide>
  </div>
</template>
<script>
import Loin from "@/assets/Lion.jpg";
// import HomeHead from "@/components/HomeHead";
export default {
  components: {
    // HomeHead
  },
  data() {
    return {
      Loin,
      items: [
        {
          url: "#",
          image: Loin
        },
        {
          url: "#",
          image: Loin
        }
      ]
    };
  }
};
</script>
// scoped 只在当前组件下可用
<style lang="stylus" scoped>
img
  width 100%
span 
  width 9px
  height 9px
  background rgba(216,216,216,1)
  border-radius: 50%
  margin 6px 4.5px
.activate
  background #6DB3EA
</style>
  1. 头部: button
  • src/components/HomeHead.vue
  1. 划入:drawer list是二维数组 drawer抽屉
<template>
  <div class="head">
    <img src="../assets/images/Snow.jpg" alt />
    <cube-button icon="cubeic-more" @click="showDrawer"></cube-button>
    <cube-drawer
      ref="drawer"
      title="请选择"
      @cancel="cancelHandler"
      @select="selectHandler"
      :data="list"
    ></cube-drawer>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [
        [
          {
            text: "全部课程",
            value: 0
          },
          {
            text: "vue课程",
            value: 1
          },
          {
            text: "react课程",
            value: 2
          }
        ]
      ]
    };
  },
  methods: {
    showDrawer() {
      if (!this.isShowDrawer) {
        this.$refs.drawer.show();
        this.isShowDrawer = true;
      } else {
        this.$refs.drawer.hide();
        this.isShowDrawer = false;
      }
    },
    cancelHandler() {
      // 修复点两次显示bug:修改标志位
      this.isShowDrawer = false;
    },
    selectHandler(arg1, arg2, arg3) {
      // value arg1 ; index arg2 ; text arg3.
      this.isShowDrawer = false;
    }
  }
};
</script>
<style lang="stylus" scoped>
.head
  height 46px
  background #000
  display flex
  flex-direction row 
  justify-content space-between
  align-items center
  img 
    height 28px
    width auto
    margin 9px 13px
  button
    background-color #000
    width 45px
    height 46px
.cube-drawer
  margin-top 46px
</style>
  1. 课程列表
    1. src/components/CourseList.vue
    2. 在src/views/Home.vue引入CourseList并使用
    3. CourseList.vue
      <template>
        <div>
          <div class="list-title">
            <i class="iconfont zf-kecheng"></i>
            <span>课程列表</span>
          </div>
          <ul class>
            <li v-for="(item,index) in courseList" :key="index" class="course-item">
              <img :src="item.img" alt />
              <div class="course-info">
                <p class="course-title">{{item.title}}</p>
                <div class="course-more">
                  <span class="course-price">价格:{{item.price}}</span>
                  <span class="course-learn-count">学习人数:{{item.learnCount}}</span>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </template>
      <script>
      import webpack from "@/assets/images/Yosemite 4.jpg";
      export default {
        data() {
          return {
            webpack,
            courseList: [
              {
                title: "Webpack4.0",
                img: webpack,
                price: 9200,
                learnCount: 666
              }
            ]
          };
        }
      };
      </script>
      <style lang="stylus" scoped>
      .list-title
        text-align left 
        margin 8px
        i
          font-size 22px
          margin-right 3px
        span
          font-size 15px
          font-weight 600
          color #292929
      ul
        margin 0 5px
      .course-item
        display flex
        flex-direction row
        margin 8px
        border-radius 10px
        box-shadow 1px 1px 1px 1px rgba(0,0,0,0.5)
        overflow hidden
        img 
          height 67px
        .course-title
          text-align left 
          font-size 15px
          font-weight 600
          margin-top 5px
          margin-bottom 13px
        .course-info
          margin 5px 10px  
        .course-more
          span 
            margin-right 10px
        .course-price
          font-size 12px
        .course-learn-count
          font-size 12px
      </style>
      
  2. tabBar
    1. cube-ui:tab-bar文档
    2. tabBar组件:首页+个人中心
    3. src/App.vue
    4. App.vue
<template>
  <div id="app">
    <div class="container">
      <router-view />
    </div>
    <cube-tab-bar v-model="selectedLabelDefault" @click="clickHandler" :data="tabs"></cube-tab-bar>
  </div>
</template>
<script>
export default {
  data() {
    return {
      selectedLabelDefault: "/",
      tabs: [
        {
          label: "首页",
          icon: "cubeic-home",
          value: "/"
        },
        {
          label: "个人中心",
          icon: "cubeic-person",
          value: "/profile"
        }
      ]
    };
  },
  methods: {
    clickHandler(label) {
      this.$router.push(label);
    }
  }
};
</script>

<style lang="stylus">
#app
  font-family 'Avenir', Helvetica, Arial, sans-serif
  -webkit-font-smoothing antialiased
  -moz-osx-font-smoothing grayscale
  text-align center
  color #2c3e50
  display flex
  flex-direction column
  justify-tontent bettween 
  height 100vh
.container
  flex 1
  overflow scroll
.cube-tab-bar
  border-top 1px solid #ebebeb
  div
    font-size 14px
    line-height 16px
</style>
  1. 个人中心页面
    1. src/views/Profile.vue
    2. Profile.vue
<template>
  <div class="person">
    <div class="user-info">
      <img class="user-avator" :src="avator" alt />
    </div>
    <ul class="bottom-ul content">
      <li>
        <i class="cubeic-danger"></i>
        <span>常见问题</span>
      </li>
      <li>
        <i class="cubeic-close"></i>
        <span>退出登录</span>
      </li>
    </ul>
  </div>
</template>
<script>
import avator from "@/assets/images/avator.png";
export default {
  data() {
    return {
      avator
    };
  }
};
</script>
<style lang="stylus" scoped>
.person
  .user-info
    background #1B8DE5 url("../assets/images/user_bg.png") center center  no-repeat
    background-size cover
    height 203px
    display flex
    flex-direction column
    justify-content  center
    align-items   center
    .user-avator
      background-image url("../assets/images/avator.png")
      height 53px
      width  53px
      background-color #fff
      background-size cover
      border-radius 50%
  .bottom-ul
    line-height 32px
    font-size 14px
    color #777
    border-bottom 1px solid #ebebeb
    li 
      padding-left  10px;
      text-align left 
    span 
      padding-left 5px
</style>
  1. 覆盖tabBar
    1. 可以通过绝对定位+z-index+background覆盖tabBar
  2. 覆盖tabBar的返回按钮组件
    1. src/components/BackHead.vue
    2. BackHead.vue
<template>
  <div class="head">
    <i @click="goBack" class="cubeic-back coustom-icon"></i>
  </div>
</template>
<script>
export default {
  methods:{
    goBack(){
      console.log('back');
      this.$router.go('-1');
    }
  }
}
</script>
<style lang="stylus" scoped>
.head
  background #000
  height 46px
  display flex
  flex-direction row
  justify-content between
  align-items center
.coustom-icon
  color #fff
  font-size 30px
  margin-left 5px
</style>

kft_koa_api

  1. 后台初始化:
    1. koa脚手架的生成器:$ cnpm install -g koa-generator
    2. $ koa2 kft_koa_api
    3. $ cd kft_koa_api && yarn$ cd kft_koa_api && cnpm i
  2. $ nrm use cnpm 切换源
  3. kft_koa_api文件作用
    1. bin/www,网站默认端口、服务...,网站文件
    2. public文件夹:资源
    3. routes:后端路由
    4. views:放置模板文件,前后端不分离的时候主要使用文件夹
    5. app.js:主要的后端文件,引入的配置文件
  4. nodemon 改变代码无需重启,自动修改
  5. pm2:部署环境下使用 $ pm2 start bin/www --watch 后台运行,静默重启
  6. 登录注册接口
    1. routes/account.js
      const router = require('koa-router')()
      router.post('/login', async (ctx, next) => {
        console.log('收到前端请求');
        ctx.body = {
          code: 0,
          msg: '登录成功'
        }
      })
      module.exports = router
      
    2. app.js
    + const account = require('./routes/account')
    + app.use(account.routes(), account.allowedMethods())
    
  7. $ cnpm i mongoose 安装mongo数据库
  8. 校验规则
    1. util/validate/validator.js
    2. 函数编程的函数库 $ yarn add ramda


const R = require('ramda');
const pattern = {
  // http://emailregex.com/
  email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
  url: new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i'),
  hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i,
};
// 新建一个校验类
class Validator {
  constructor(query) {
    // 传过来的对象挂载在当前构造函数
    this.query = query;
    // 校验规则
    this.rules = [];
  }
  check(name, type, errorMsg, params) {
    this.rules.push({
      name,
      type,
      errorMsg,
      // 赋予一个默认值
      params: params || {}
    })
  }
  validate() {
    // 创建拆分规则
    const groupByName = R.groupBy(rule => rule.name);
    // 安装规则进行拆分
    const groupList = groupByName(this.rules);
    Object.keys(groupList).map(key => {
      // 拿到所有的校验规则
      const rules = groupList[key];
      // 找到是否必填
      const requiredRule = rules.find(rule => rule.type === "required");
      // 拿到当前key的值
      const value = this.query[key];
      // 如果是必填
      if (requiredRule) {
        // 没有值抛出错误
        if (value === undefined) {
          throw new Error(requiredRule.errorMsg);
        }
      } else {
        // 不是必填项并且value===undefined
        if (value === undefined) {
          // 性能优化
          return
        }
      }
      // 获取其他规则
      const otherRules = rules.filter(rule => rule.type !== "required");
      for (let i = 0; i < otherRules.length; i++) {
        const rule = otherRules[i];
        // 如果是正则
        if (rule.type instanceof RegExp) {
          // 没有通过验证
          if (!rule.type.test(value)) {
            throw new Error(rule.errorMsg);
          }
        }
        switch (rule.type) {
          case 'email':
            if (!pattern.email.test(value)) {
              throw new Error(rule.errorMsg)
            }
            break;
          case 'len': {
            const { min, max } = rule.params;
            if (min && value.length < min) {
              throw new Error(rule.errorMsg)
            }
            if (max && value.length > max) {
              throw new Error(rule.errorMsg)
            }
          }

        }
      }
    })
  }
}
// const query = { email: '11@zhufeng.com', password: '2222' };
// const validatorInstance = new Validator(query);
// validatorInstance.check('email', 'required', '邮箱必填');
// validatorInstance.check('email', 'email', '邮箱格式不正确');
// validatorInstance.check('password', 'required', '密码必填');
// validatorInstance.check('password', 'len', '密码长度不正确');
// validatorInstance.validate();
// console.log('校验成功');
module.exports = Validator;
  1. codeIgniter,php框架,全是坑,能提高代码水平;thinkPHP,php框架,全是封装,不用写底层代码;
  2. controller 具体操作;lib/db/ 数据库;model 数据库的增删改查操作;util 工具
  3. 登录注册:routes/account.js => controller/account/login或controller/account/register => model/account.js=>lib/db/user.js
  4. 错误打印 app.js
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx);
  ctx.body = {
    msg: err.message
  }
  ctx.status = 200;
  ctx.res.end(JSON.stringify(ctx.body));
});
  1. 异常处理 lib/exceptions/logicException.js
  • 通过类继承区分开登录还是注册问题...
  1. 启动mongodb数据库
  2. Mac: 1.. $ brew services list 当前启动的服务 $ brew services start mongodb 启动mongodb数据库
  3. ssh root@149.28.132.207 进入linux服务器
    1. $ systemctl status mongod 查找mongod服务
    2. $ systemctl stop mongod 关闭mongod服务
    3. $ systemctl start mongod 关闭mongod服务