Vue2.6+VueCli4.3+CubeUI 完成小D课堂移动端

  • 0. 项目效果
  • 1. 搭建项目架构
  • 2. 项目目录结构创建
    • 2.1 分析前端需求
    • 2.2 介绍目录结构
  • 3. 基于浏览器和 nodeJS 的 http 客户端 Axios 模块
    • 3.1 什么是 Axios
    • 3.2 GET 请求
    • 3.3 POST 请求
    • 3.4 创建实例
  • 4. Axios 封装通用后端请求 API 模块
  • 5. Vue-Router 开发路由
    • 5.1 vue-router 介绍
    • 5.2 vue-router 常见 API
    • 5.3 路由对象属性
      • 5.3.1 $route.path
      • 5.3.2 $route.params
      • 5.3.3 $route.query
  • 6. 通用底部选项卡 CommonsFooter 开发
    • 6.1 查看 cube-ui 文档
    • 6.2 cube-tab-bar 组件
    • 6.3 底部选项卡 cube-tab-bar
  • 7. 首页 Home 模块开发
    • 7.1 拆分子组件
    • 7.2 template 开发
    • 7.3 await async 知识点
  • 8. 首页轮播图 Banner 模块开发
  • 9. 首页视频列表 VideoList 模块开发
    • 9.1 router-link 讲解
  • 10. 视频详情 CourseDetail 模块开发
  • 11. 视频详情页 Header 模块开发
  • 12. 视频详情页 Course 模块开发
  • 13. 视频详情页 Tab 模块开发
    • 13.1 什么是 vue 动态组件
    • 13.2 组件的过渡
  • 14. 视频详情页 summary 子组件开发
  • 15. 视频详情页 Catalog 目录子组件开发
  • 16. 视频详情页 footer 立刻购买按钮开发
  • 17. 用户模块注册功能开发
    • 17.1 Cube-UI 的 form 表单
  • 18. 用户模块注登陆功能开发
  • 19. Vuex 状态管理
    • 19.1 vuex 是什么?
    • 19.2 开发 store 中 index.js
    • 19.3 整合 Login 登陆存储 token
  • 20. 用户模块个人中心开发
  • 21. 路由拦截功能开发
    • 21. 前置守卫
  • 22. 下单模块开发
  • 23. 订单模块开发
  • 24. 前后端项目云服务器生产环境部署核心知识
    • 24.1 应用部署到公网访问需要的知识
      • 24.1.1 http 请求基本流程
      • 24.1.2 域名和 IP 的关系,DNS 作用
      • 24.1.3 什么是 cname 和 a 记录
      • 24.1.4 购买服务器,阿里云,腾讯云,亚马逊云 aws
      • 24.1.5 购买域名,备案
      • 24.1.6 安装项目依赖的基本环境
      • 24.1.7 配置域名解析到服务器
    • 24.2 阿里云服务器远程登陆和常用工具
      • 24.2.1 控制台修改阿里云远程连接密码
      • 24.2.2 windows 工具
      • 24.2.3 苹果系统 MAC
      • 24.2.4 linux 图形操作工具 (用于远程连接上传文件)
  • 25. 前端部署线上Linux云服务器
    • 25.1 前端项目总体部署架构和阿里云域名解析A记录配置

0. 项目效果


1. 搭建项目架构

  • 创建 vue 项目

    • vue create iclass-web
  • 选择 feature 模式,安装 vuex/vue-router
    • 安装 axios

      • npm install axis --save
    • npm install <package_name> --save
      • 表示将这个包名及对应的版本添加到 package.jsondependencies
    • npm install <package_name> --save-dev
      • 表示将这个包名及对应的版本添加到 package.jsondevDependencies
  • 添加 cube-ui 依赖
    • vue add cube-ui

2. 项目目录结构创建

2.1 分析前端需求

  • 底部导航
  • 首页 Banner
  • 首页视频列表
  • 视频详情模块
  • 注册模块
  • 登陆模块
  • 个人信息模块
  • 下单模块
  • 订单列表模块

2.2 介绍目录结构

  • 创建新目录

    • api/router/views
    • views
      • CourseDetail
      • Home
      • Register
      • Login
      • Order
      • Pay
      • Personal

3. 基于浏览器和 nodeJS 的 http 客户端 Axios 模块

3.1 什么是 Axios

  • 基于 promise 用于浏览器nodeJShttp 客户端

    • 支持浏览器nodeJS
    • 支持 Promise API
    • 支持拦截请求和响应
    • 支持转换请求和响应数据
    • JSON 数据的自动转换
    • 客户端支持以防止 XSRF
  • 文档地址:<http://www.axios-js.com/zh-cn/docs/
  • 安装 Axios
    • npm install axios

3.2 GET 请求

// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345').then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});// 上面的请求也可以这样做
axios.get('/user', {params: {ID: 12345}}).then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});

3.3 POST 请求

axios.post('/user', {firstName: 'Fred',lastName: 'Flintstone'}).then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});

3.4 创建实例

  • 可以使用自定义配置新建一个 axios 实例
  • axios.create([config])
const instance = axios.create({baseURL: 'https://some-domain.com/api/',timeout: 1000,headers: {'X-Custom-Header': 'foobar'}
});

4. Axios 封装通用后端请求 API 模块

  • getData.js
import axios from '../request'//注册接口
export const registerApi = (phone, pwd , name)=> axios.post("/api/v1/pri/user/register",{"phone":phone,"pwd":pwd,"name":name
})//登录接口
export const loginApi = (phone, pwd) => axios.post("/api/v1/pri/user/login",{phone,pwd
})//轮播图接口
export const getBanner = () => axios.get("/api/v1/pub/video/list_banner")//视频列表接口
export const getVideoList = ()=> axios.get("/api/v1/pub/video/list")//视频详情
export const getVideoDetail = (vid)=> axios.get("/api/v1/pub/video/find_detail_by_id?",{params: {video_id:vid}
})//下单接口
export const saveOrder = (token, vid)=>axios.post("/api/v1/pri/order/save",{"video_id":vid
},{headers:{"token":token}
})//订单列表
export const getOrderList = (token)=>axios.get("/api/v1/pri/order/list",{params:{"token":token}
})//用户信息接口
export const getUserInfo = (token)=>axios.get("/api/v1/pri/user/find_by_token",{params:{"token":token}
})
  • request.js
// 导入 axios 模块
import axios from 'axios'// 创建 axios 实例
const service = axios.create({// url = baseURL + request url// 根目录baseURL: 'xxxx',// 配置请求超时时间timeout: 5000
})// 导出 service
export default service

5. Vue-Router 开发路由

5.1 vue-router 介绍

  • Vue.js 官方的路由管理器,和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌
  • 官方文档:https://router.vuejs.org/zh/

5.2 vue-router 常见 API

  • 文档:https://router.vuejs.org/zh/api/
  • router.path
    • 获取当前的路由
  • router.go(n)
    • 这个方法的参数是一个整数,表示在 history 记录中向前或者向后退多少步
    • 类似 window.history.go(n) 方法
  • router.push(path)
    • 导航到不同的 path 路径
    • 这个方法会向 history 栈添加一个新的记录
    • 所以当用户点击浏览器后退按钮时,则回到之前的 URL

5.3 路由对象属性

5.3.1 $route.path
  • 类型:string
  • 字符串,对应当前路由的路径,总是解析为绝对路径,如:"/foo/bar"
5.3.2 $route.params
  • 类型:Object
  • 一个 key/value 对象,包含了动态片段和全匹配片段如果没有路由参数,就是一个空对象
5.3.3 $route.query
  • 类型:Object
  • 一个 key/value 对象,表示 URL 查询参数
  • 例如,对于路径 /foo?user=1,则有 $route.query.user == 1
  • 如果没有查询参数,则是个空对象
// 引用 vue,vue-router 模块
import Vue from 'vue'
import VueRouter from 'vue-router'// 引入组件模块
import Home from '../views/Home/Home.vue'
import CourseDetail from '../views/CourseDetail/CourseDetail.vue'
import Login from '../views/Login/Login.vue'
import Order from '../views/Order/Order.vue'
import Pay from '../views/Pay/Pay.vue'
import Personal from '../views/Personal/Personal.vue'
import Register from '../views/Register/Register.vue'// 使用 VueRouter
Vue.use(VueRouter)// 单独提出 routes
// 定义路由关系
const routes = [{path: "/",name: "Home",component: Home},{path: "/coursedetail",name: "CourseDetail",//按需加载component: () => import("../views/CourseDetail/CourseDetail.vue")//component:CourseDetail},{path: "/login",name: "Login",component: Login},{path: "/order",name: "Order",component: Order,meta: { requiresAuth: true }}, {path: "/pay",name: "Pay",component: Pay,meta: { requiresAuth: true }}, {path: "/personal",name: "Personal",component: Personal,meta: { requiresAuth: true }}, {path: "/register",name: "Register",component: Register}
]// 创建路由实例
const router = new VueRouter({// 定义路由关系routes
})// 导出 router
export default router

6. 通用底部选项卡 CommonsFooter 开发

6.1 查看 cube-ui 文档

  • 文档:https://didi.github.io/cube-ui/#/zh-CN/docs/quick-start

6.2 cube-tab-bar 组件

  • 底部选项卡组件
  • https://didi.github.io/cube-ui/#/zh-CN/docs/tab-bar

6.3 底部选项卡 cube-tab-bar

  • template 开发

    • Mac 格式化代码:shift + option + F
    • windows 格式化代码:shift + alt + F
<template><div class="tab"><cube-tab-bar v-model="selectedLabelSlots" @click="changHandler"><!-- 具体插槽 --><cube-tabv-for="(item) in tabs":icon="item.icon":label="item.label":key="item.path":value="item.path"></cube-tab></cube-tab-bar></div>
</template>
  • script 开发

    • 图标:https://didi.github.io/cube-ui/#/zh-CN/docs/style
export default {data() {return {// 默认选中首页selectedLabelSlots: "/",tabs: [{label: "首页",icon: "cubeic-home",path: "/"},{label: "我的订单",icon: "cubeic-like",path: "/order"},{label: "个人中心",icon: "cubeic-person",path: "/personal"}]};}
};
  • 开发方法
methods: {changHandler(path){//this.$route.path是当前路径if(path !== this.$route.path){// 当点击的时,传入对应路径// 如果页面当前路径不是点击对应的路径// 则将点击的路径推到路由中this.$router.push(path)}}
},//vue实例生命周期 created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图//vue实例生命周期 mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行额外的操作    //https://cn.vuejs.org/v2/guide/instance.html#%E5%AE%9E%E4%BE%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90
created(){//默认路由选择器,比如刷新页面,需要重新进到当前路由this.selectedLabelSlots = this.$route.path
}
  • 配置样式
<!--SCSS是一种CSS预处理语言, scoped 是指这个scss样式 只作用于当前组件-->
<style lang="scss" scoped>
.tab {position: fixed;bottom: 0;z-index: 999;background-color:#fff;width: 100%;border-top: 1px solid rgba($color: #000000, $alpha: 0.1);
}
.cube-tab_active {color: #3bb149;
}
</style>

7. 首页 Home 模块开发

7.1 拆分子组件

  • Home
  • banner
  • videoList
  • 注意点
    • 指令属性里面取data里面的数据是不用加{{}}
    • html标签内容体中间则需要加{{}}

7.2 template 开发

<template><div><!-- 轮播图组件 --><home-banner :banners="banners"></home-banner><!-- 视频列表组件 --><video-list :courselist="courselist"></video-list><!-- 底部导航栏组件 --><common-footer></common-footer></div>
</template>

7.3 await async 知识点

  • async 用于声明一个 function异步
  • await 用于等待一个异步方法执行完成(发起请求,如果查询数据库,发起 http 等)
  • 参考文章
    • https://www.cnblogs.com/jsgoshu/p/11444404.html
    • https://segmentfault.com/a/1190000007535316
// 导入组件模块
import HomeBanner from "./Component/Banner";
import VideoList from "./Component/VideoList";
import CommonFooter from "@/components/CommonFooter";// 导入接口API
import { getBanner, getVideoList } from "@/api/getData.js";export default {//注册组件components: {HomeBanner,VideoList,CommonFooter,},//声明数据源data() {return {// 轮播图与视频列表数据都是数组形式banners: [],videoList: [],};},// 定义方法methods: {// 异步调用 getBanner 接口// 获取轮播图数据async getBannerData() {// 捕获异常try {// 等待异步方法执行完成const result = await getBanner();console.log(result);console.log(result.data.code == 0)if (result.data.code == 0) {this.banners = result.data.data;}}catch(error){console.lo(error)}},//获取视频列表async getVList(){try{const result = await getVideoList();if (result.data.code == 0) {this.videoList = result.data.data;}}catch(error){console.lo(error)}}},mounted(){//当页面渲染完成时调用方法获取数据this.getBannerData();this.getVList()}};

8. 首页轮播图 Banner 模块开发

  • 轮播图组件 cube-slide 讲解

    • 文档:https://didi.github.io/cube-ui/#/zh-CN/docs/slide#cube-Slide-anchor
  • 代码编写
<template><div><cube-slide :data="banners"><cube-slide-itemv-for="(item, index) in banners":key="index"><a :href="item.url"><img :src="item.img"  style="width:100%"/></a></cube-slide-item></cube-slide></div>
</template><script>
export default {//获取父组件传递过来的值props:{banners:{type:Array,required:true}}
};
</script><style lang="scss" scoped>
</style>

9. 首页视频列表 VideoList 模块开发

9.1 router-link 讲解

  • 用于路径跳转
  • 文档:https://router.vuejs.org/zh/api/#router-link-props
  • template
<template><div class="list-content"><div class="list"><!-- 遍历视频 --><!-- 跳转到视频详情页,需要传入对应的 ID --><router-link:key="item.id":to="{ path: '/coursedetail', query: { video_id: item.id } }"class="course"v-for="item in videoList"><div class="item_img"><img :src="item.cover_img" /></div><div class="video_info"><div class="c_title">{{ item.title }}</div><div class="price">¥ {{ item.price / 100 }}</div></div></router-link></div></div>
</template>
  • script
export default {// 获取父组件传递过来的值props: {videoList: {type: Array,required: true,},},
};
  • style
//列表包裹层边距
.list-content {margin-top: 20px;padding: 0 13px;
}
//视频包括层
.list {display: flex; //设置flex布局flex-wrap: wrap; //换行排列justify-content: space-between; //两端对齐padding-bottom: 55px;
}
//视频个体层
.course {width: 48%;margin-bottom: 17px;
}
//视频图片
.item_img {font-size: 0; //消除图片元素产生的间隙box-shadow: 0 4px 11px 0 rgba(43, 51, 59, 0.6); //设置图片阴影,rgba前三个参数是颜色编码,最后一个是透明度border-radius: 8px; //设置图片圆角img {width: 100%;border-radius: 8px;}
}
.c_title {//设置超过两行隐藏 startdisplay: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;overflow: hidden;word-break: break-all;//设置超过两行隐藏 endfont-size: 11px;height: 26px;line-height: 13px;margin-top: 10px;color: #2b333b;
}
//价格
.price {margin-top: 8px;font-size: 12px;color: #d93f30;
}

10. 视频详情 CourseDetail 模块开发

  • 拆分组件

    • CourseDetail.vue

      • Header.vue
      • Course.vue
      • Tab.vue
        • Summary.vue
        • Calalog
  • 开发 CourseDetailtemplate

<template><div><!--顶部返回组件--><detail-header :videoInfo="videoInfo"></detail-header><!--视频介绍组件--><detail-course :videoInfo="videoInfo"></detail-course><!--视频tab简介组件--><detail-tab :videoInfo="videoInfo" :chapterList="chapterList"></detail-tab> <!--底部立刻购买--><footer><router-link :to="{path:'/pay',query:{video_id:this.$route.query.video_id}}" class="user_buy"><button>立刻购买</button>            </router-link></footer> </div>
</template>
  • 开发 CourseDetailscript
//引入组件
import DetailHeader from './Components/Header'
import DetailCourse from './Components/Course'
import DetailTab from './Components/Tab'import { getVideoDetail } from "@/api/getData.js";export default {//注册组件components:{DetailHeader,DetailCourse,DetailTab},data(){return {//视频信息数据videoInfo:{},//章集数据chapterList:[]}},methods:{// 获取视频详情// 传入 vidasync getDetail(vid){try{const result =  await getVideoDetail(vid)if(result.data.code == 0){this.videoInfo = result.data.data;this.chapterList = result.data.data.chapter_list;}}catch(error){console.log(error)}}},mounted(){//渲染完成后拿数据console.log(this.$route.query.video_id)this.getDetail(this.$route.query.video_id);}
}
  • 配置样式
//底部
footer {// fixed固定在底部position: fixed;bottom: 0;width: 100%;padding: 8px 0;background-color: #fff;z-index: 999;box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.05);
}
//设置购买按钮样式
button {display: block;color: #fff;margin: 0 auto;background-color: #d93f30;height: 34px;line-height: 34px;border-radius: 17px;width: 80%;border: none;font-size: 15px;text-align: center;
}

11. 视频详情页 Header 模块开发

  • 代码
<template><div><header><div class="header"><!-- 返回箭头 --><!-- 可以返回上一页 --><span @click="$router.back(-1)"> <i class="cubeic-back"></i> </span><div class="title">{{videoInfo.title}}</div></div></header></div>
</template><script>
export default {// 获取父组件中的数据props:{videoInfo:{type:Object,required:true}}}
</script><style lang="scss" scoped>
.header {display: flex;//flex左右布局background-color: #07111b;padding: 10px 20px;color: #fff;
}
// 返回箭头
.cubeic-back {color: #fff;margin-right:5px;
}
//视频标题
.title {font-size: 16px;width: 80%;//超出省略text-overflow: ellipsis;overflow: hidden;white-space: nowrap;
}
</style>

12. 视频详情页 Course 模块开发

  • 代码
<template><div class="c_wrapper"><!-- 视频信息缩略层 --><div class="course"><div class="l_img"><img :src="videoInfo.cover_img" :title="videoInfo.title"></div><div class="r_txt"><div class="txt"><span>综合评分:</span><p>{{ videoInfo.point }}</p></div><div class="txt"><span>价格:</span><p>¥ {{ videoInfo.price/100 }}</p></div></div></div></div>
</template><script>export default {// 从父组件获取视频信息props: {videoInfo: {type: Object,required: true}}}
</script><style lang="scss" scoped>
//包裹层
.c_wrapper {padding: 0 14px;
}
//视频信息包裹层
.course {margin:14px 0;display:flex;//设置flex,左右布局
}
//视频左边图片层
.l_img {height:88px;margin-right:14px;& img {height:100%;border-radius:15px;}
}
// 视频右边文字包裹层
.r_txt {padding:6px 0;font-size:12px;flex:1;//设置1可自动伸缩占用剩余空间
}
//每行文字层(综合评分、价格)
.txt {// 设置flex让文字两端对齐display:flex;justify-content:space-between;line-height:16px;& p {text-align:center;width:40%;color:#3bb149;}   & i {color:#666;font-weight:bolder;width:60%;& span {color:#2b333b;font-size:12px;}}
}</style>

13. 视频详情页 Tab 模块开发

13.1 什么是 vue 动态组件

  • 不同组件之间进行动态切换
  • https://cn.vuejs.org/v2/guide/components-dynamic-async.html

13.2 组件的过渡

  • https://cn.vuejs.org/v2/guide/transitioning.html

  • template

<template><div><cube-tab-bar v-model="selectedLabel" show-slider><cube-tab v-for="item in tabs" :label="item.label" :key="item.label"></cube-tab></cube-tab-bar><component :videoInfo="videoInfo" :chapterList="chapterList" :is='selectedLabel==="简介"?"Summary":"Catalog" '></component></div></template>
  • script
import Summary from './Summary'
import Catalog from './Catalog'export default {components:{Summary,Catalog},// 获取父组件的数据props:{videoInfo:{type:Object,required:true},chapterList:{type:Array,required:true}},data(){return{selectedLabel:"简介",tabs:[{label:"简介"},{label:"目录"}]}}
}

14. 视频详情页 summary 子组件开发

  • template
<template><div> <img class="summary" :src="videoInfo.summary"/> </div>
</template>
  • script
export default {props:{videoInfo:{type:Object,required:true}}
}
  • style
.summary {width:100%;padding-bottom:50px;margin:15px 0;
}

15. 视频详情页 Catalog 目录子组件开发

  • template
<template><div class="cate_box"><div><!-- 双重for循环  1. 先遍历章的id  2. 后遍历节的id --><ul class="content" v-for="(item, ind) in chapterList" :key="item.id"><h1> 第{{ind +1}}章 &nbsp;{{item.title}} </h1><!-- 拿到对应章的id里面的节的id --><li class="sub_cate" v-for="(item,subind) in chapterList[ind].episode_list" :key="item.id"><span class="sub_title">{{ind+1}}-{{subind+1}} &nbsp;{{item.title}}  </span>    </li></ul></div></div></template>
  • script
export default {//从父组获取章集信息props:{chapterList:{type:Array,required:true}}}
  • style
// 目录包裹层设置边距
.cate_box {padding: 0 15px 50px;background-color: #fff;margin: 15px 0;
}//每一章包裹层
.content {padding: 10px;// 章标题& h1 {font-size: 16px;width: 100%;margin-bottom: 15px;font-weight: bolder;// 设置章标题过长,超过行宽度省略隐藏text-overflow: ellipsis;overflow: hidden;white-space: nowrap;}
}//集包裹层
.sub_cate {font-size: 12px;padding: 10px 0;//集标题.sub_title {// 设置集标题过长,超过行宽度省略隐藏display: block;text-overflow: ellipsis;overflow: hidden;white-space: nowrap;}
}

16. 视频详情页 footer 立刻购买按钮开发

  • CourseDetail.vue
...
<!--底部立刻购买-->
<footer><!-- 点击跳转链接 --><!-- 传入对应的id --><router-link:to="{ path: '/pay', query: { video_id: this.$route.query.video_id } }"class="user_buy"><button>立刻购买</button></router-link>
</footer>
...
<style lang="scss" scoped>
//底部
footer {// fixed固定在底部position: fixed;bottom: 0;width: 100%;padding: 8px 0;background-color: #fff;z-index: 999;box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.05);
}
//设置购买按钮样式
button {display: block;color: #fff;margin: 0 auto;background-color: #d93f30;height: 34px;line-height: 34px;border-radius: 17px;width: 80%;border: none;font-size: 15px;text-align: center;
}
</style>

17. 用户模块注册功能开发

17.1 Cube-UI 的 form 表单

  • https://didi.github.io/cube-ui/#/zh-CN/docs/form
  • template
<template><div class="main"><cube-form :model="model" @submit="submitHandler"><cube-form-group><!--名称--><cube-form-item :field="fields[0]"></cube-form-item><!--手机号--><cube-form-item :field="fields[1]"></cube-form-item><!--密码--><cube-form-item :field="fields[2]"></cube-form-item></cube-form-group><cube-form-group><cube-button type="submit">注册</cube-button></cube-form-group></cube-form><!-- 跳转到登陆链接 --><router-link to="/login" class="reg">登录</router-link></div>
</template>
  • script
//注册接口
import { registerApi } from "@/api/getData.js";
export default {data() {return {model: {phoneValue: "",pwdValue: "",nameValue: "",},// 校验规则部分fields: [{type: "input",modelKey: "nameValue",label: "名称",props: {// 属性placeholder: "请输入名称",},rules: {// 规则required: true,notWhitespace: true,},messages: {// 错误信息required: "名称不能为空",notWhitespace: "名称不能为空白符",},},{type: "input",modelKey: "phoneValue",label: "手机号",props: {placeholder: "请输入手机",},rules: {required: true,len: 11,pattern: /^1[3456789]\d{9}$/,},messages: {pattern: "请输入正确的手机号",},},{type: "input",modelKey: "pwdValue",label: "密码",props: {placeholder: "请输入密码",type: "password",eye: {open: false,},},rules: {pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/,},messages: {pattern: "密码至少8-16个字符,包含大小字母和数字",},},],};},methods: {// 传入 model 参数submitHandler(e, model) {// preventDefault 方法// 取消事件的默认动作(阻止冒泡)e.preventDefault();//调用注册接口registerApi(model.phoneValue, model.pwdValue, model.nameValue).then((res) => {if (res.data.code === 0) {// Toast组件主要用于非模态信息提醒,无需用户交互// time 字段决定了 Toast 显示的时间,如果设置为 0,则不会消失,需要手动调用组件的 hide 方法const toast = this.$createToast({txt: "注册成功",type: "correct",time: 1500,onTimeout: () => {this.$router.push({ path: "login" });},});toast.show();}});},},
};
  • style
.main {padding: 50px 5% 0;text-align: center;
}
//注册
.cube-btn {margin-top: 20px;
}
// 登录
.reg {display: inline-block;margin-top: 30px;font-size: 18px;
}

18. 用户模块注登陆功能开发

<template><div class="main"><cube-form :model="model" @submit="submitHandler"><cube-form-group><!--手机号--><cube-form-item :field="fields[0]"></cube-form-item><!--密码--><cube-form-item :field="fields[1]"></cube-form-item></cube-form-group><cube-form-group><cube-button type="submit">登录</cube-button></cube-form-group></cube-form><router-link to="/register" class="reg">注册</router-link> </div>
</template><script>
//登录接口
import { loginApi } from "@/api/getData.js";
export default {data() {return {model: {phoneValue: "",pwdValue: ""},fields: [{type: "input",modelKey: "phoneValue",label: "手机号",props: {placeholder: "请输入手机"},rules: {required: true,len: 11,pattern: /^1[3456789]\d{9}$/,},messages: {required: "手机号不能为空",pattern: "请输入正确的手机号",}},{type: "input",modelKey: "pwdValue",label: "密码",props: {placeholder: "请输入密码",type: "password",eye: {open: false}},rules: {required: true,pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/},messages: {required: "密码不能为空",pattern: "密码至少8-16个字符,包含大小字母和数字"}}]};},methods: {submitHandler(e, model) {e.preventDefault();//调用注册接口loginApi(model.phoneValue, model.pwdValue).then(res => {if (res.data.code === 0) {// 登录成功,跳转到个人中心// 拿到 token,存储到本地localStorage.setItem('token',res.data.data)this.$store.dispatch('setToken',res.data.data)// 跳转页面, 根据业务需要this.$router.push({path:'/personal'})}else{const toast = this.$createToast({txt: "登录失败",type: "error",time: 1500});toast.show();}});}}
};
</script>
<style lang="scss" scoped>
.main {padding: 50px 5% 0;text-align: center;
}
// 登录
.cube-btn {margin-top: 20px;
}
//注册
.reg {display: inline-block;margin-top: 30px;font-size: 18px;
}
</style>

19. Vuex 状态管理

19.1 vuex 是什么?

  • vuex 是适用于在 Vue 项目开发时使用的状态管理工具
  • 官方文档:https://vuex.vuejs.org/zh/

19.2 开发 store 中 index.js

import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {// 拿到 token 值token: localStorage.getItem("token") || ''},// 同步修改state里面的值mutations: {SET_TOKEN:(state, token)=>{state.token = token}},// 异步调用mutations里面的方法// contxt.commit 利用上下文触发mutations某个方法// vue代码里面 this.$store.dispatch触发action里面的定义的方法actions: {setToken(context,token){context.commit('SET_TOKEN',token)},clearToken(context){context.commit('SET_TOKEN','')}},modules: {}
})

19.3 整合 Login 登陆存储 token

  • Login.vue
methods: {submitHandler(e, model) {e.preventDefault();//调用登陆接口loginApi(model.phoneValue, model.pwdValue).then(res => {if (res.data.code === 0) {// 登录成功,跳转到个人中心// 拿到 token,存储到本地localStorage.setItem('token',res.data.data)// 触发 Vuex 定义的方法this.$store.dispatch('setToken',res.data.data)// 跳转页面, 根据业务需要this.$router.push({path:'/personal'})}else{const toast = this.$createToast({txt: "登录失败",type: "error",time: 1500});toast.show();}});}}

20. 用户模块个人中心开发

<template><div><div class="container"><div class="p_top"><div><!-- 如果用户头像没设置,就设置默认图片 --><img :src="info.head_img || defaultHeadImg" alt="头像" /><!-- 判断用户是否登陆 token为空即未登陆,显示立即登陆,否则显示用户名--><router-link to="/login" v-if="getToken === ''"><p>立刻登录</p></router-link><p v-else>{{ info.name }}</p></div></div><!-- 判断用户token不等于空,才会退出登陆 --><button v-if="getToken !== ''" class="green" @click="signOut">退出登录</button></div><common-footer></common-footer></div>
</template><script>
import CommonFooter from "@/components/CommonFooter";
import { getUserInfo } from "@/api/getData.js";
import defaultHeadImg from "@/assets/logo.png";export default {components: {CommonFooter,},data() {return {info: {},defaultHeadImg: defaultHeadImg,};},// 通过数据源获取token// 缓存功能computed: {getToken() {return this.$store.state.token;},},methods: {//获取用户信息async getInfo() {try {// 传入 tokenconst result = await getUserInfo(this.getToken);if (result.data.code === 0) {this.info = result.data.data;}} catch (error) {console.log(error);}},//退出登录async signOut() {//清除tokenawait this.$store.dispatch("clearToken");// 本地存储里清除tokenlocalStorage.removeItem("token");//刷新页面location.reload();},},mounted() {// 如果有token,就获取用户信息if (this.getToken) {this.getInfo();}},
};
</script><style lang="scss" scoped>
.container {// 顶部头像区域.p_top {width: 100%;display: flex;flex-direction: column;align-items: center;padding: 20px 0;background-color: #2c3f54;div {text-align: center;img {width: 60px;height: 60px;border-radius: 50px;}p {font-size: 16px;color: #fff;margin-top: 10px;}}}
}
// 退出登录
.green {display: block;background-color: #3bb149;border: none;outline: none;width: 80%;height: 40px;margin: 20px auto 0;color: #fff;border-radius: 20px;
}
</style>

21. 路由拦截功能开发

21. 前置守卫

  • 前置守卫文档

    • https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
  • router 里面配置需要登陆的路由
    • meta: {requiresAuth: true}
  • main.js 里面配置路由拦截
// 路由拦截,拦截全部路由,每次操作路由都是被拦截进行判断
// 路由前置守卫
router.beforeEach((to,from,next)=>{// 从本地存储中拿tokenconst token = localStorage.getItem("token")// 筛选需要传token的路由,匹配route里面需要登陆的路径,如果匹配到的就是trueif( to.matched.some(record => record.meta.requiresAuth)){// 如果登陆了,执行下一步,没有登陆,跳转到登陆路由// 根据token是否有,判断是否需要调到登录页面if(token){next()}else{next({path:'/login'})}}else{next()}
})

22. 下单模块开发

<template><div><!--视频信息--><div class="info"><p class="info_title">商品信息</p><div class="box"><div class="imgdiv"><img alt="课程照片" :src="videoinfo.cover_img" /></div><div class="textdiv"><p class="c_title">{{ videoinfo.title }}</p><p class="price">¥:&nbsp;&nbsp; {{ (videoinfo.price / 100).toFixed(2) }}</p></div></div></div><!--顶部支付--><div class="footer"><p class="money">实付:&nbsp;&nbsp; {{ (videoinfo.price / 100).toFixed(2) }}</p><p class="submit" @click="pay">立刻支付</p></div></div>
</template><script>
import { getVideoDetail, saveOrder } from "@/api/getData.js";export default {data() {return {videoinfo: {},};},methods: {//获取视频详情async getDetail(vid) {try {const result = await getVideoDetail(vid);if (result.data.code == 0) {this.videoinfo = result.data.data;}} catch (error) {console.log(error);}},//下单async pay() {try {// 下单接口// 传入token,video_idconst result = await saveOrder(this.$store.state.token,this.$route.query.video_id);if (result.data.code == 0) {const toast = this.$createToast({txt: "购买成功",type: "correct",time: 2000,// 跳转到订单页onTimeout: () => {this.$router.push({ path: "order" });},});toast.show();} else {const toast = this.$createToast({txt: "下单失败",type: "error",time: 1500,});toast.show();}} catch (error) {console.log(error);}},},mounted() {this.getDetail(this.$route.query.video_id);},
};
</script><style lang="scss" scoped>
// 视频标题
.info_title {padding: 10px 20px;background-color: #fff;border-bottom: 1px solid #d9dde1;
}.box {background-color: #fff;box-sizing: border-box;padding: 20px;display: flex;margin-bottom: 15px;.imgdiv {width: 105px;height: 59px;flex-shrink: 0;img {width: 100%;height: 100%;}}.textdiv {margin-left: 20px;height: 59px;flex-grow: 1;display: flex;flex-direction: column;justify-content: space-between;.price {flex-shrink: 0;}}
}
//底部
.footer {position: fixed;bottom: 0;width: 100%;height: 50px;background-color: #fff;display: flex;justify-content: space-between;box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 0.1);font-size: 16px;.money {height: 50px;line-height: 50px;flex: 2;text-align: center;background-color: #fff;}.submit {height: 50px;line-height: 50px;flex: 1;text-align: center;background-color: #ff2d50;color: #fff;}
}
</style>

23. 订单模块开发

<template><div class="main"><!--订单列表--><!-- 如果有订单,就显示 --><div class="list" v-if="orders.length > 0"><div class="box" v-for="(item, index) of orders" :key="index"><router-link:to="{ path: '/coursedetail', query: { video_id: item.video_id } }"><div class="smallbox"><div class="imgdiv"><img :src="item.video_img" alt="小滴课堂课程图片" /></div><div class="textdiv"><p class="title">{{ item.video_title }}</p><p class="price">{{ (item.total_fee / 100).toFixed(2) }}</p></div></div></router-link></div></div><!-- 没有订单就显示 --><div class="no_order" v-else><p>暂未购买课程</p></div><!--底部导航--><common-footer></common-footer></div>
</template>
<script>
import CommonFooter from "@/components/CommonFooter";
import { getOrderList } from "@/api/getData.js";export default {components: {CommonFooter,},data() {return {orders: [],};},methods: {//获取订单列表async getOrderList() {try {const result = await getOrderList(this.$store.state.token);if (result.data.code == 0) {this.orders = result.data.data || [];}} catch (error) {console.log(error);}},},mounted() {this.getOrderList();},
};
</script><style lang="scss" scoped>
.list {padding: 0 20px;
}// 视频个体
.box {padding: 20px 0;background-color: #fff;border-bottom: 1px solid #ddd;// 标题.title {font-size: 14px;margin-bottom: 15px;}// 订单详情.smallbox {//flex左右排列,两端对齐display: flex;justify-content: space-between;.imgdiv {width: 90px;height: 69px;flex-shrink: 0;img {width: 100%;height: 100%;border-radius: 10px;}}.textdiv {width: 100%;p {width: 96%;margin-top: 10px;padding-left: 20px;}}}
}.no_order {margin-top: 50px;text-align: center;
}
</style>

24. 前后端项目云服务器生产环境部署核心知识

24.1 应用部署到公网访问需要的知识

24.1.1 http 请求基本流程
  • 客户端通过发起域名资源请求 -> DNS 解析获得IP -> 寻找服务器获得资源
24.1.2 域名和 IP 的关系,DNS 作用
  • DNS

    • Domain Name Server 域名服务器
    • 域名虽然便于人们记忆,但网络中的计算机之间只能互相认识 IP 地址
    • 它们之间的转换工作称为域名解析
      • 域名解析需要由专门的域名解析服务器来完成
      • DNS 就是进行域名解析的服务器
24.1.3 什么是 cname 和 a 记录
  • a 记录

    • 用户可以在此设置域名指向到自己的目标主机地址
    • 从而实现通过域名找到服务器(也叫 ip 指向域名配置
  • cname
    • 别名指向,可以为一个主机设置别名
    • 比如设置 open1024.com,用来指向一个主机 xdclass.net,那么以后就可以用 open1024.com 来代替访问 xdclass.net
    • http://www.xdclass.net --> xdclass.net
24.1.4 购买服务器,阿里云,腾讯云,亚马逊云 aws
  • 阿里云 https://www.aliyun.com/
  • 腾讯云 https://cloud.tencent.com/
  • 亚马逊云 https://aws.amazon.com/
24.1.5 购买域名,备案
  • 阿里云备案地址

    • https://beian.aliyun.com/
24.1.6 安装项目依赖的基本环境
24.1.7 配置域名解析到服务器

24.2 阿里云服务器远程登陆和常用工具

24.2.1 控制台修改阿里云远程连接密码
24.2.2 windows 工具
  • putty
  • xshell
  • security
  • 参考资料
    • https://jingyan.baidu.com/article/e75057f210c6dcebc91a89dd.html
    • https://www.jb51.net/softjc/88235/html
24.2.3 苹果系统 MAC
  • 终端登陆

    • ssh root@ip 回车后输入密码
    • cd / (根路径)
    • cd software/
    • cd 项目路径
    • pwd(查看当前文件夹的路径)
    • vim nginx.conf (编辑)
    • ../sbin/nginx -s reload
24.2.4 linux 图形操作工具 (用于远程连接上传文件)
  • Mac: filezilla
  • windows: winscp
  • 资料
    • https://jingyan.baidu.com/article/ed2a5d1f346fd409f6be179a.html

25. 前端部署线上Linux云服务器

25.1 前端项目总体部署架构和阿里云域名解析A记录配置

Vue2.6+VueCli4.3+CubeUI 完成小D课堂移动端相关推荐

  1. 【饿了么】—— Vue2.0高仿饿了么核心模块移动端Web App项目爬坑(一)

    [饿了么]-- Vue2.0高仿饿了么核心模块&移动端Web App项目爬坑(一) 前言:学习Vue.js高仿饿了么课程过程中,总结了这个Web App项目从准备到开发完毕自己觉得很重要的知识 ...

  2. 智慧校园管理系统带原生移动端小程序包含家长端和教师端

    系统技术栈说明: 1.使用springboot框架 Java+vue2 2.数据库:MySQL5.7 3.移动端小程序使用小程序原生语言开发 4.电子班牌固件安卓7.1:使用Java Android原 ...

  3. java webpack web项目_官方出品,微信小程序和 Web 端同构解决方案——kbone

    介绍 最近在琢磨一些小程序开发和移动web开发,偶然间在Github上看到了这样一个项目--kbone,一个致力于微信小程序和 Web 端同构的解决方案.微信小程序的底层模型和 Web 端不同,我们想 ...

  4. 浅谈对腾讯云微信小程序解决方案服务端的理解(主要针对信道服务)

    浅谈对腾讯云微信小程序解决方案服务端的理解(主要针对信道服务) 参考文章: (1)浅谈对腾讯云微信小程序解决方案服务端的理解(主要针对信道服务) (2)https://www.cnblogs.com/ ...

  5. 当当elastic-job docker快速部署_[小Z课堂]-docker 快速部署 elasticsearch 和 kibana,一键部署...

    各位小伙伴,小Z课堂来袭,每天只需看三分钟,你就能用docker 快速部署各种环境.今天就用docker 来部署 elasticsearch 和 kibana.docker的入门请上度娘学习,这里直接 ...

  6. 10分钟上线 - 利用函数计算构建微信小程序的Server端

    摘要: 阿里云函数计算是一个事件驱动的全托管计算服务.通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传.微信小程序是一种不需要下载安装即可使用的应用,它可以在微信内被便捷地获取和传播. 当 ...

  7. axure 小程序 lib_【kboneui】打通 H5/微信小程序,多端UI库

    前言 有了UI库,便捷性提高很多.今日早读文章由腾讯@binnie投稿分享. 正文从这开始-- kbone-ui 的方式是以小程序内置组件和拓展组件为对齐目标, 使用 weui 样式提供 H5 和 小 ...

  8. [yishen] 小慕读书web端学习笔记

    课程常用链接 [前奏-课程]快速入门Web阅读器开发 [小慕读书web端]Vue 实战商业级读书Web APP 全面提升技能 [epub图书免费下载站点 · 中文书]http://www.ziliao ...

  9. 海量数据大课学习笔记(2)-不在其位要谋其政,技术Leader能力模型提升-小滴课堂

    文章目录 前言 第1集 互联网大厂里 技术Leader的能力模型-不单写代码 第2集 技术人的产品运营能力提升-竞品分析 第3集 跳出自己的技术思维模型-上司给你团队安排任务 前言 小滴课堂,旨在让编 ...

最新文章

  1. linux mint 修改dns,如何在Ubuntu和LinuxMint中刷新DNS缓存
  2. 文件系统类型是ntfs无法确定卷版本和状态_硬盘写到一半时断电,文件系统里会发什么?...
  3. scrum项目管理_Scrum,用于初创企业(或针对该项目的任何项目)
  4. linux制作共享服务器,Linux如何制作一个简单的共享服务器
  5. python 绘制折线图-怎样用python绘制折线图
  6. moldflow2019安装教程
  7. python是用来初始化_python的初始化运行了哪些?
  8. Visual Studio 2008 十大新功能
  9. KVM 虚拟机 调整内存与CPU
  10. 迈特斯机器人_WIE-R红外热像视频内窥镜
  11. 图书馆占座系统-产品需求规格说明书
  12. JSP九大内置对象以及作用
  13. Python词频统计与杨辉三角
  14. 民情二维码:居民诉求一个码收集
  15. 我可以利用计算机查找资料,《信息检索》复习题库 (1)
  16. vue3 项目搭建以及使用
  17. 3. 投票 案例项目(合集)
  18. Google将IP标记为中国,影响表现以及解决方案
  19. Linux权限(下)
  20. XUPT第三届新生算法赛

热门文章

  1. 回溯法之马的遍历问题(递归)
  2. 巴萨罗那:Deco来,Davis走!
  3. mysql innodb默认的锁_Mysql InnoDB锁
  4. 长文干货助UI设计师拿高薪
  5. 杰理之关掉混响的效果【篇】
  6. OpenCV图片压缩保存
  7. 和黄医药获加拿大养老基金投资公司1亿美元股权投资
  8. CF922A Cloning Toys--题解报告
  9. 关于联想小新系列Fn+Q切换模式失效的解决方案
  10. 基于YOLOv4的车辆检测 MATLAB实现