本文共 20235 字,大约阅读时间需要 67 分钟。
创建
vue 项目 vue create iclass-web
feature 模式
,安装 vuex/vue-router
axios
npm install axis --save
npm install <package_name> --save
package.json
的 dependencies
npm install <package_name> --save-dev
package.json
的 devDependencies
cube-ui
依赖 vue add cube-ui
底部导航
首页 Banner
首页视频列表
视频详情模块
注册模块
登陆模块
个人信息模块
下单模块
订单列表模块
api/router/views
views
CourseDetail
Home
Register
Login
Order
Pay
Personal
promise
用于浏览器
和 nodeJS
的 http 客户端
浏览器
和 nodeJS
Promise API
拦截请求和响应
转换请求和响应数据
JSON 数据的自动转换
防止 XSRF
安装 Axios
npm install axios
// 为给定 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); });
axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
自定义配置
新建一个 axios 实例
axios.create([config])
const instance = axios.create({ baseURL: 'https://some-domain.com/api/', timeout: 1000, headers: { 'X-Custom-Header': 'foobar'}});
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})// 导出 serviceexport default service
路由管理器
,和 Vue.js 的核心深度集成
,让构建单页面应用
变得易如反掌官方文档
:文档
:router.path
获取当前的路由
router.go(n)
整数
,表示在 history
记录中向前或者向后退多少步
window.history.go(n)
方法router.push(path)
不同的 path 路径
history
栈添加一个新的记录
当用户点击浏览器后退按钮时,则回到之前的 URL
string
字符串
,对应当前路由的路径
,总是解析为绝对路径
,如:"/foo/bar"Object
key/value
对象,包含了动态片段和全匹配片段
,如果没有路由参数,就是一个空对象
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})// 导出 routerexport default router
文档
:底部选项卡
组件template 开发
Mac
格式化代码:shift + option + F
windows
格式化代码:shift + alt + F
script 开发
图标
: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%90created(){ //默认路由选择器,比如刷新页面,需要重新进到当前路由 this.selectedLabelSlots = this.$route.path}
配置样式
Home
banner
videoList
指令属性
里面取data
里面的数据是不用加{ {}}
html
标签内容体中间则需要加{ {}}
async
用于声明一个 function
是异步
的await
用于等待一个异步方法执行完成
(发起请求,如果查询数据库,发起 http 等)// 导入组件模块import HomeBanner from "./Component/Banner";import VideoList from "./Component/VideoList";import CommonFooter from "@/components/CommonFooter";// 导入接口APIimport { 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() } };
cube-slide
讲解 路径跳转
template
{ { item.title }}¥ { { item.price / 100 }}
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 { //设置超过两行隐藏 start display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; word-break: break-all; //设置超过两行隐藏 end font-size: 11px; height: 26px; line-height: 13px; margin-top: 10px; color: #2b333b;}//价格.price { margin-top: 8px; font-size: 12px; color: #d93f30;}
拆分组件
CourseDetail.vue
Header.vue
Course.vue
Tab.vue
Summary.vue
Calalog
开发 CourseDetail
的 template
CourseDetail
的 script
//引入组件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:{ // 获取视频详情 // 传入 vid async 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;}
代码
{ {videoInfo.title}}
代码
综合评分:{ { videoInfo.point }}
价格:¥ { { videoInfo.price/100 }}
不同组件之间进行动态切换
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:"目录" } ] } }}
template
script
export default { props:{ videoInfo:{ type:Object, required:true } }}
style
.summary { width:100%; padding-bottom:50px; margin:15px 0;}
template
第{ {ind +1}}章 { {item.title}}
- { {ind+1}}-{ {subind+1}} { {item.title}}
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; }}
CourseDetail.vue
... ...
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;}
登录 注册
状态管理工具
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: { }})
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(); } } ); } }
立刻登录
{ { info.name }}
前置守卫文档
router
里面配置需要登陆的路由
meta: {requiresAuth: true}
main.js
里面配置路由拦截
// 路由拦截,拦截全部路由,每次操作路由都是被拦截进行判断// 路由前置守卫router.beforeEach((to,from,next)=>{ // 从本地存储中拿token const token = localStorage.getItem("token") // 筛选需要传token的路由,匹配route里面需要登陆的路径,如果匹配到的就是true if( to.matched.some(record => record.meta.requiresAuth)){ // 如果登陆了,执行下一步,没有登陆,跳转到登陆路由 // 根据token是否有,判断是否需要调到登录页面 if(token){ next() }else{ next({ path:'/login'}) } }else{ next() }})
商品信息
{ { videoinfo.title }}
¥: { { (videoinfo.price / 100).toFixed(2) }}
{ { item.video_title }}
{ { (item.total_fee / 100).toFixed(2) }}
暂未购买课程
客户端通过发起域名资源请求 -> DNS 解析获得IP -> 寻找服务器获得资源
DNS
Domain Name Server 域名服务器
计算机之间只能互相认识 IP 地址
域名解析
专门的域名解析服务器
来完成DNS 就是进行域名解析的服务器
a 记录
设置域名
并指向到自己的目标主机地址
上通过域名找到服务器
(也叫 ip 指向域名配置
)cname
设置别名
用 open1024.com 来代替访问 xdclass.net
了http://www.xdclass.net --> xdclass.net
阿里云
腾讯云
亚马逊云
阿里云备案地址
putty
xshell
security
参考资料
终端登陆
ssh root@ip
回车后输入密码pwd
(查看当前文件夹的路径)vim nginx.conf
(编辑)../sbin/nginx -s reload
Mac: filezilla
windows: winscp
资料
转载地址:http://ccqwi.baihongyu.com/