本文目录
  1. vue-cli脚手架创建项目
  2. ESLint
    1. vscode的eslint插件
  3. vant组件库
    1. 组件库样式的定制
  4. 移动端适配
  5. 封装axios
    1. 补充
  6. try和catch
  7. 路由
  8. 时间处理
  9. 自定义指令-封装
  10. 图片防盗链
  11. 给组件加原生事件
  12. 阻止事件冒泡
  13. 防抖和节流
  14. 数组去重
  15. 文件上传-隐藏域
  16. api接口分文件
  17. 大整数问题处理
  18. 持久化存储方式
  19. 抽离组件注册
  20. 组件缓存
  21. 头像不更新问题
  22. 登录未遂的处理
  23. 跨域问题
    1. cors方式
    2. jsonp方式
    3. 代理转发
  24. Hbuilder打包app
    1. 目标
    2. 分类
    3. HBuilder开发版
    4. 创建5+App项目
    5. 准备打包
    6. 云打包
    7. 运行
    8. iOS问题
  25. 李老师经验分享

分类: vue | 标签: vue 移动端

黑马头条-vue移动端项目笔记整理

发表于: 2022-06-30 18:10:28 | 字数统计: 7k | 阅读时长预计: 35分钟

b站教学视频 Vue2_项目_黑马头条-移动端项目

视频里配套资料: http://m6z.cn/6guyjV
最新的接口基地址: http://geek.itheima.net/

只记录自己get到的一些知识,详细的老师的配套的笔记里面都有

vue-cli脚手架创建项目

  • 安装vue-cli脚手架
npm install -g @vue/cli
  • 创建项目 采用自定义方式去创建项目
vue create hmtt

上下箭头切换, 回车确认, 空格选中

? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
> Manually select features  

手动选择特性: Babel, Router, Vuex, CSS Pre-processors, Linter

? Please pick a preset: Manually select features
? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
 (*) Vuex
>(*) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing  

版本Vue2.x

? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 2.x
  3.x (Preview) 

路由是否使用history模式:不采用

image-20220630153712490

css 预处理器: 使用less

image-20220630153737945

eslint语法风格:Standard (一定)

image-20220630153811062

检查节点:保存时检查,提交时检查 (提交时可以不选)

image-20220630153831434

存储插件配置位置:单独放在不同的文件中

image-20220630153904318

接下来,它会问你是否要保存前面的设置作为预设方案,以便后续创建其它项目时直接使用。

如果选择Y, 保存, 以后就可以一键完成以上步骤

经过长长的等待,创建完毕, 进入文件夹, 启动项目

ESLint

  1. 什么是ESLint?

    代码检查工具

  2. 为什么要使用ESLint?

    规范我们写代码的格式, 看着整洁 / 团队内成员风格统一

  3. ESLint在哪里生效?

    webpack开发服务器+ESLint配置检查

规范文档: http://www.verydoc.net/eslint/00003312.html

规范文档2: https://standardjs.com/rules-zhcn.html

规范文档3: http://eslint.cn/docs/rules/

vscode的eslint插件

ctrl+s 这个插件就会修复常见的eslint抛出的错误

  • 下载这个插件到vscode中

image-20220630154354610

注意: 一定要把脚手架工程, 作为vscode根目录, 因为eslint要使用配置文件.eslintrc

  • 一定要配置插件监测的时机, 修改ESLint插件配置

image-20220630154453277

不用管别的, 把红框的放在{}内即可

"eslint.run": "onType",
"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
}

更多的规则可以参考这里: https://www.cnblogs.com/jiaoshou/p/12218642.html

.eslintrc.js 配置文件关闭驼峰命名

rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    // 取消文件和变量的驼峰命名
    'vue/multi-word-component-names': 0,
    camelcase: 'off'
  }

vant组件库

移动端组件库

vant官网:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/quickstart

  • 下载vant组件库
yarn add vant

# Vue 2 项目,安装 Vant 2:
npm i vant@latest-v2 -S
  • 下载插件
yarn add babel-plugin-import -D
  • 在babel.config.js-添加如下配置
module.exports = {
    // ...省略了其他
    plugins: [
        ['import', {
            libraryName: 'vant',
            libraryDirectory: 'es',
            style: true
        }, 'vant']
    ]
};

一定要重启vscode和webpack开发服务器才会生效

组件库样式的定制

vant组件配置 - less文件

  • src/styles/cover.less - vant定制less变量统一在这管理
// NavBar导航
@nav-bar-background-color:#007bff;
@nav-bar-title-text-color:white;
  • vue.config.js - 注释变量, 放开引入文件路径
// 不要手动写绝对路径, 用代码来动态获取, 绝对地址
const path = require('path')
// console.log(__dirname) // 当前文件, 所在文件夹, 的绝对路径
// 盘符:/......../工程名字, 后面自己拼接 src/styles/cover.less
module.exports = {
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          // 直接覆盖变量
          // 'nav-bar-background-color': '#007bff',
          // 'nav-bar-title-text-color': 'white',
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
          hack: `true; @import "${path.resolve(__dirname, 'src/styles/cover.less')}";`
        }
      }
    }
  }
}

一定要重启webpack开发服务器, 然后观察效果

移动端适配

PC端一般都是1:1用px还原UI设计图, 靠内容撑开高度

移动端一般都是rem单位进行适配

步骤:

  • 下载amfe-flexible 根据网页宽度, 设置html的font-size
yarn add amfe-flexible
  • 到main.js引入
import "amfe-flexible"

postcss: 后处理css, 编译翻译css代码

postcss-pxtorem: 把css代码里所有px计算转换成rem

yarn add postcss postcss-pxtorem@5.1.1
  • 根目录下创建postcss.config.js文件
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      // 能够把所有元素的px单位转成Rem
      // rootValue: 转换px的基准值。
      // 编码时, 一个元素宽是75px,则换成rem之后就是2rem
      rootValue: 37.5,
      propList: ['*']
    }
  }
}

37.5 是如何得来的?

UI移动端设计图宽度375px, 而flexible.js会/10, 设置html的font-size为37.5

一般屏幕的适配方案为 1rem=屏幕宽度的十分之一

总结:

  1. 移动端适配选择哪种?

    rem + flexible.js

  2. flexible.js作用是什么?

    js代码里获取网页宽度 / 10设置html的font-size的值(px单位)

  3. 代码里px如何自动转换rem?

    postcss和postcss-pxtorem插件

封装axios

  • 下载axios
yarn add axios
  • request.js
import ajax from 'axios'
import router from '@/router'
import { Notify } from 'vant'
import { getToken } from '@/utils/token'
// 创建axios实例
const axios = ajax.create({
  baseURL: 'http://toutiao.itheima.net',
  timeout: 20000 // 超时时间为20s
})

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // 如果本地有token则携带在请求头中传给后台
  // console.log(config)
  if (getToken()?.length > 0 && config.headers.Authorization === undefined) {
    config.headers.Authorization = `Bearer ${getToken()}`
  }
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response
}, function (error) {
  // 对响应错误做点什么
  /**
   * 响应401说明token失效--》跳转到登录页面
   */
  if (error.response.status === 401) {
    router.push('/login')
    Notify({ type: 'danger', message: '登录已过期' })
  }
  return Promise.reject(error)
})

// 封装axios方法
export default ({ url, method = 'GET', data = {}, params = {}, headers = {} }) => {
  return axios({
    url,
    method,
    data,
    params,
    headers
  })
}

补充

axios库中params里的值为null, 会自动忽略此参数和值, 不发给后台

axios库中data里的值为null, 会发给后台

axios可以在响应错误的拦截器中,按之前的配置重新发起请求 axios(error.config)

  • token的续签

定义刷新token的接口方法

// 用户 - 更新token
export const refreshTokenAPI = () => request({
  url: '/v1_0/authorizations',
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${store.state.refresh_token}`
  }
})

在响应拦截器401处, 调用重新请求token的接口, 然后同步给vuex和本地

axios.interceptors.response.use(function (response) { 
  return response
}, async function (error) {
  if (error.response.status === 401) { // 身份过期
    // token续签方式1:
    // store.commit('setToken', '')
    // router.push({ path: '/login' })

    // token续签方式2: refreshToken(用户无感知)
    const res = await refreshTokenAPI()
    // 再调用一次未完成的请求啊(用户无感知)
    // error.config 就是上一次axios请求的配置对象
    // console.dir(error.config)
    // 把新的token赋予到下一次axios请求的请求头中
    error.config.headers.Authorization = 'Bearer ' + res.data.data.token
    // return到await的地方
    return axios(error.config) //重点!!!:通过axios重新发起请求
  } else {
    return Promise.reject(error)
  }
})

try和catch

  1. await用于取代then函数, 等待Promise成功结果提取在原地
  2. await无法获取Promise失败的结果, 一旦失败Promise错误直接抛出到控制台

可以利用try和catch解决上面的问题

try {
    // 可能会报错的代码(例如await)
} catch (err) {
    // try里代码报错, 捕捉到这里执行
}

路由

  1. $router和$route区别是?

    • $router下用于跳转路由

    • $route是路由信息对象

  2. 路由的push和replace方法区别?

    • push跳转后, 可以返回
    • replace跳转后, 无法返回
  3. 什么时候用$route.query 什么时候用$route.params

    • $route.params —>动态路由
    • $route.query —>问号传参
  4. 路由的懒加载

路由懒加载 - 查看文档: https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

component: Login
// 改成这个写法
component: () => import('@/views/Login.vue')
  1. 路由守卫(类似后端的拦截器)

方法1: 全局前置守卫判断

router.beforeEach((to, from, next) => {
  // 有token, 不能去登录页
  // 无token, 需要用户"权限"的才需要去登录页
  if (store.state.token.length > 0 && to.path === '/login') {
    // 证明有token-已经登录了
    next(false) // 阻止跳转原地呆着
  } else {
    next()
  }
})

方法2: 路由独享守卫(可以理解为局部的守卫,针对某个路由拦截)

{
    path: '/login',
    component: () => import(/* webpackChunkName: "Login" */ '@/views/Login'),
    beforeEnter (to, from, next) {
      if (store.state.token.length > 0) { // vuex里有token(代表登录过, 但是一定要注意过期和主动退出要先清除vuex和本地的token, 让其跳转登录页)
        return next(false)
      }
      next()
    }
},

时间处理

dayjs第三方库: https://dayjs.fenxianglu.cn/

  • 安装
npm install dayjs --save
  • 使用
var dayjs = require('dayjs')
//import dayjs from 'dayjs' // ES 2015
dayjs().format()

utils/date.js

import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' // 到指定时间需要的插件
import 'dayjs/locale/zh' // 集成中文

/**
 * .....多久之前
 * @param {*} 之前的时间
 * @returns 系统时间到之前指定时间的距离值
 */
export const timeAgo = (targetTime) => {
  // 格式化时间
  dayjs.extend(relativeTime)
  dayjs.locale('zh')
  var a = dayjs()
  var b = dayjs(targetTime)
  return a.to(b) // 返回多久之前...
}

扩展-自己写多久之前

relativeTime (val) {
      const t = new Date(val)
      const diff = Date.now() - t.getTime()

      const year = Math.floor(diff / (1000 * 3600 * 24 * 365))
      if (year) {
        return `${year}年前`
      }
      const month = Math.floor(diff / (1000 * 3600 * 24 * 30))
      if (month) {
        return `${month}月前`
      }
      const day = Math.floor(diff / (1000 * 3600 * 24))
      if (day) {
        return `${day}天前`
      }
      const hour = Math.floor(diff / (1000 * 3600))
      if (hour) {
        return `${hour}小时前`
      }
      const minute = Math.floor(diff / (1000 * 60))
      if (minute) {
        return `${minute}分钟前`
      } else {
        return '刚才'
      }
    }

自定义指令-封装

  • utils/directives.js, 定义全局自定义指令插件
import Vue from 'vue'
// 插件对象(必须有install方法, 才可以注入到Vue.use中)
export default {
  install () {
    Vue.directive('fofo', {
      inserted (el) {//插入dom的时候执行
        fn(el)
      },
      update (el) {//更新dom的时候执行
        fn(el)
      }
    })
  }
}
function fn (el) {
  if (el.nodeName === 'INPUT' || el.nodeName === 'TEXTAREA') {
    // 如果直接是input标签/textarea标签
    el.focus()
  } else {
    // 指令在van-search组件身上, 获取的是组件根标签div, 而input在标签内
    const inp = el.querySelector('input')
    const textArea = el.querySelector('textarea')
    // 如果找到了
    if (inp || textArea) {
      inp && inp.focus()
      textArea && textArea.focus()
    } else {
      // 本身也不是, 子标签里也没有
      console.error('请把v-fofo用在输入框标签上')
    }
  }
}
  • 引入到main.js注册
import diretivesObj from '@/utils/directives'

Vue.use(diretivesObj)

Vue.use相关文档: https://cn.vuejs.org/v2/api/#Vue-use

Vue.use(obj) 其实就是注册,内部会调用install方法

自定义指令的inserted何时执行?

  • 当指令所在组件, 第一次插入到真实DOM被调用

图片防盗链

在前端可以通过meta来设置referrer policy(来源策略),referrer设置成no-referrer,发送请求不会带上referrer信息,对方服务器也就无法拦截了

<!-- 解决图片403防盗链问题 -->
<meta name="referrer" content="no-referrer" />

但是如果他们做了其他判断, 我们依旧拿不到此图片

给组件加原生事件

组件默认没有click事件的时候,如果想用click事件怎么办?利用navive

<article-item
              v-for="obj in articleList"
              :key="obj.art_id"
              :obj="obj"
              @click.native="$router.push(`/article_detail?aid=${obj.art_id}`)"
              :showX="false"
              ></article-item>

阻止事件冒泡

通过.stop来阻止

<!-- 反馈按钮 -->
<van-icon name="cross" @click.stop="onCloseClick" />

防抖和节流

后面再找点资料详细看看

数组去重

利用 Set 和 Array.from实现

this.history = Array.from(new Set(this.history)) // 去重

文件上传-隐藏域

当点击的是非 input[type=’file’] 的时,不会弹出选择文件的框。此时可以设置一个隐藏的文件框,通过js来触发这个文件框的点击事件,从而实现文件上传。

<van-cell title="头像" is-link center>
    <template #default>
        <van-image round class="avatar" :src="profile.photo" 
                   <!-- js触发点击事件 -->
                   @click="$refs.iptFile.click()"/>
        <!-- file 选择框 -->
        <input
               type="file"
               ref="iptFile"
               v-show="false"
               accept="image/*"
               @change="onFileChange"
               />
    </template>
</van-cell>

<script>
export default {
  methods: {
    // 文件选择方法
    async onFileChange (ev) {
    //   console.log(ev.target.files[0])
      if (ev.target.files.length === 0) return // 防止用户未选择图片
      const fd = new FormData()
      fd.append('photo', ev.target.files[0]) // photo在表单里参数名携带
      const res = await updatePhotoAPI(fd)
      console.log(res)
      this.profile.photo = res.data.data.photo // 更新最新头像
    }
  }
}
</script>

接口

// 用户- 更新头像
// 注意: formObj的值必须是一个表单对象
// '{"a": 10, "b": 20}' // 对象格式的JSON字符串
// new FormData() // 表单对象
export const updatePhotoAPI = (formObj) => {
  return request({
    url: '/v1_0/user/photo',
    method: 'PATCH',
    data: formObj
    // 如果你的请求体内容是表单对象, 浏览器会自动携带请求头Content-Type为multipart/form-data
  })
}

api接口分文件

  • 原因: 一个api/index.js, 有几百行代码, 不便于管理
  • 解决: 分散到多个js文件里, 再引入回到统一导出
    • 分散的js文件名, 尽量和页面模块同名, 方便查找

问题1: 分文件后, 逻辑页面里都是从api/index.js导出的, 难道我们要去改逻辑代码?

解决: 在api/index.js - 中 export * from ‘分散的文件’ (模块重定向)

意思: 在api/index.js 作为入口, 从别的地方把接口倒回来同时导出给外面

export 文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/export

注意; 包括reports.js文件也从统一出口导出, 去修改src/components/ArticleItem引数据位置

处理结果 api/index.js如下

export * from './reports' // 反馈列表数据
export * from './ArticleDetail' // 文章详情相关
export * from './Home' // 首页(频道)相关, 首页文章列表
export * from './Login' // 登录相关
export * from './Search' // 搜索相关
export * from './User' / 用户相关

大整数问题处理

JS安全数字范围? 16位的一个数字,超过16位也能显示, 但是精度不准确

  1. 定义后台返回数据, 模拟大数

    后台数据库id, 生成算法是19位置

    const str = '[{"id": 1302900300041101987}, {"id": 1205340366642205763}, {"id": 7689021398237123422}]'
    
  2. 尝试用JSON.parse转换, 发现转换后的值不对

    后面3位精度错误

    console.log(JSON.parse(str))
    
  3. 原因: JS范围的安全数打印

    console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
    

    详细原因可以看这里: https://lidongxuwork.gitee.io/pages/webFront/javascript/run/0.1+0.2%E9%97%AE%E9%A2%98.html

  4. 解决方案, 可以引入第三方包叫json-bigint

    把大数转成字符串保存

    npm i json-bigint
    
    const jsonBig = require('json-bigint')({ storeAsString: true })
    console.log(jsonBig.parse(str))
    

持久化存储方式

封装本地持久化方法,方便后期统一管理。(无限套娃,哈哈,前面的axios也是这个套路)

  • 创建utils/storage.js文件, 定义4个方法
// 本地存储方式
// 如果同时有sessionStorage和localStorage, 可以封装2份
// 现在我只封装一种统一的方式
export const setStorage = (key, value) => {
  localStorage.setItem(key, value)
}
export const getStorage = (key) => {
  return localStorage.getItem(key)
}
export const removeStorage = (key) => {
  localStorage.removeItem(key)
}
export const clearStorage = () => {
  localStorage.clear()
}
  • 把所有使用本地存储的地方, 都统一换成这里定义的方法

    • 在store/index.js - vuex中使用过
    • 在search/index.vue - 搜索页面使用过

抽离组件注册

main.js代码有些多, 分散出去

  • 创建src/VantRegister.js, 把Vant注册的相关代码复制过来
import Vue from 'vue'
import { NavBar, Form, Field, Button, Tabbar, TabbarItem, Icon, Tab, Tabs, Cell, List, PullRefresh, ActionSheet, Popup, Row, Col, Badge, Search, Divider, Tag, CellGroup, Image, Dialog, DatetimePicker, Loading, Lazyload } from 'vant'
Vue.use(Lazyload)

Vue.use(Loading)
Vue.use(DatetimePicker)
Vue.use(Dialog)
Vue.use(Image)
Vue.use(CellGroup)
Vue.use(Tag)
Vue.use(Divider)
//....
  • 在main.js引入一下, 让代码执行
import './VantRegister'

组件缓存

  • 防止组件频繁创建和销毁
  • 防止网络请求重复无用执行

组件缓存, 可以实现组件的状态保持。

结合 vue 内置的 keep-alive 组件,可以实现组件的状态保持。

官方文档地址:https://cn.vuejs.org/v2/api/#keep-alive

  1. App.vue中的router-view外层套上一个keep-alive组件

    • 缓存的一级路由页面切换不被释放, 但是首页还是会重新请求数据
  2. Layout.vue中的router-view外层套上一个keep-alive组件

    • 这次Home和User页面都被缓存了(二级路由也要管)
  3. 但发现搜索页面详情页面多被缓存起来了 (多次进入不同的文章, 发现都是同一个文章详情)

  4. 对router-view使用exclude属性来区别, 哪些页面组件可以缓存

    特别注意exclude里是组件的name名字(跟路由没什么关系)

    <template>
      <div>
        <keep-alive :exclude="['ArticleDetail', 'Login', 'Search', 'SearchResult']">
          <router-view></router-view>
        </keep-alive>
      </div>
    </template>
    

头像不更新问题

User.vue被缓存了, 所以改了头像回到User页面, created里获取用户资料接口不会执行

  • 解决方案1: 把created换成activated钩子函数即可

  • 解决方案2: UserEdit.vue修改头像成功后, 更新到vuex中, User页面使用的vuex数据也受到更新

登录未遂的处理

  • 要点赞的时候, 401了, 强制跳转到登录页面了, 保存未遂地址跳转到登录页面

    要把tokens续签改掉, 否则不会跳转到登录页面

    if (error.response.status === 401) { // 身份过期
        // token续签方式1:  去登录页重新登录, token无用, 清掉-确保路由守卫if进不去
        store.commit('setToken', '')
        console.log(router.currentRoute.fullPath)
        //重点1---》将未遂的路径传到登录页
        router.push({ path: `/login?path=${router.currentRoute.fullPath}` })
    
        // token续签方式2: refreshToken(用户无感知)
        // store.commit('setToken', '')
        // const res = await refreshTokenAPI()
        // store.commit('setToken', res.data.data.token)
        // 再调用一次未完成的请求啊(用户无感知)
        // error.config 就是上一次axios请求的配置对象
        // console.dir(error.config)
        // 把新的token赋予到下一次axios请求的请求头中
        // error.config.headers.Authorization = 'Bearer ' + res.data.data.token
        // return到await的地方
        // return ajax(error.config)
    } else {
        return Promise.reject(error)
    }
    
  1. 在Login/index.vue, 登录后, 判断有未遂地址, 跳这里, 否则去/路径

    // 跳转到Layout页面
    this.$router.replace({
        //重点2 登录页进行判断 有未遂地址, 跳这里, 否则去/路径
        path: this.$route.query.path || '/layout'
    })
    

跨域问题

网页所在url的协议, 域名, 端口号, 和Ajax请求url的协议, 域名, 端口号有一个对应不上, 就发生跨域

跨域是浏览器对ajax做出的限制

常见的跨域问题解决方式

cors方式

  • 前端什么也不用做

  • 后端需要开启cors

    实际上就是在响应头添加允许跨域的源

    Access-Control-Allow-Origin: 字段和值(意思就是允许去哪些源地址去请求这个服务器)

jsonp方式

  • 需要前端和后端同时支持

    前端用script+src属性, 发送函数名给后台, 同时准备好同名的函数, 准备接收数据

    后端返回的字符串一定用方法名(数据字符串)格式返回, 到script标签中执行

    调用函数名, 并传递数据

  • 例子代码(看看就行, 不用尝试)

    <script>
      function callBackFn(data){
        // data就是'{"a": 10, "b": 20}'
      }
    </script>
    <script src="http://后台接口地址?callback=callBackFn"></script>
    <!-- 后台接口返回 'callBackFn({"a": 10, "b": 20})' -->
    

代理转发

跨域是浏览器的限制,自己搭的服务器,开启cors后请求就不限制了。

  • 如果后端jsonp也不弄, cors也不弄, 就给你个接口地址

    我们可以在本地弄个服务器, 然后用服务器请求后台服务器接口地址

    image-20220630174418046

  • 但是vuecli脚手架, 启动了一个webpack开发服务器, 它就能做代理转发

    • 而且前端和这个服务器是同源的都是8080端口
  • 需要修改webpack开发服务器的配置即可

    更多配置项参考这里: https://webpack.docschina.org/configuration/dev-server/#devserverproxy

    devServer: {
        proxy: {
          // http://c.m.163.com/nc/article/headline/T1348647853363/0-40.html
          '/api': { // 请求相对路径以/api开头的, 才会走这里的配置
            target: 'http://c.m.163.com', // 后台接口域名
            changeOrigin: true, // 改变请求来源(欺骗后台你的请求是从http://c.m.163.com)
            pathRewrite: {
              '^/api': '' // 因为真实路径中并没有/api这段, 所以要去掉这段才能拼接正确地址转发请求
            }
          }
        }
      }
    
  • axios请求的代码

    axios({
        url: '/api/nc/article/headline/T1348647853363/0-40.html'
    })
    

可以使用 http-server 这个node工具来搭建一个代理服务来解决跨域问题

第一步:  使用win +r 打开dos面板,全局安装 npm install -g http-server

第二步:  http-server 项目路径 -P 服务器地址

Hbuilder打包app

目标

  • 为何要打包APP
  • APP分为哪几种类型

分类

App有三大类型

  • 原生的App。手机有两大操作系统:苹果,安卓

    还有 windows Phone, 鸿蒙

    ios,安卓程序员 用各自的编程语言写的代码,只能在某一个平台上运行。分安卓版本和ios版本。

    • 优点:用户体验好 ;可以调用系统API(拍照,读内存…)。
    • 缺点:费钱。(大公司一般会雇佣4端程序员)
  • 纯h5网站。就是一个移动站(https://m.jd.com/)

    • 优点:省钱。就是网页。
    • 缺点:不能调用系统API;没有统一的入口,用户不知道从哪里进来,都要通过浏览器才能访问;
  • 混合开发。

    • 先做一个网站,在网站之外套个原生的壳!能同时具备原生的优点和纯h5网站的优点。
    • 在原生的App嵌入h5 页

HBuilder开发版

我们需要借助他, 帮助我们打包一个App

下载安装, 注册激活, 如果不注册激活,就不能使用它的打包功能

下载地址: https://www.dcloud.io/hbuilderx.html (下载App开发版)

先走流程, 提示你注册再注册和激活就行了

==必须注册==

==必须激活邮箱==

==必须绑定手机号==

创建5+App项目

我们要选择5+App 项目,mui也是一套前端框架,可以选择一个mui项目。

  • 普通项目。 普通H5项目, Hbuilder内置了几套模板,作用不大,同学们基本都会自己创建
  • uni-app。多端应用,一套代码,复用八端,时下最火的一个跨端框架
  • wap2App。wap项目转 App , 原来只运在手机上的wap(无线网络协议,诺基亚,爱立信时代)项目 可转app项目
  • 5+ App。利用DCloud 的 **5+ Runtime**来做原生能力提供者的 项目
  • 小程序。微信原生小程序的另外一个编辑器,比微信提供的开发者工具好用,但是现在谁还在用原生写小程序呢?
  • 快应用 。原生快应用编辑器 , 较为冷门的生态, 目前不太热闹

image-20220630175509578

准备打包

  1. 把我们vue项目打包好的dist下的一切复制到你刚才的项目-覆盖过来即可 (一定要保留manifest.json文件)

    mainfest.json是打包配置文件

    image-20220630175552604

  2. 生成APPID

    image-20220630175610718

  3. 去掉通信录权限 (因为我的HBuilder没有身份证认证, 打包不让获取用户通讯录)

    image-20220630175632784

  4. (可选), 如果上面不小心选择No了, 可以去源码处选择 - 删除

    image-20220630175650163

云打包

image-20210407171554818

如果一切正常,你将会在控制台中看到类似如下的结果:

image-20220630175836488

这就是云打包成功了, 下面会出现apk下载的所在文件夹

运行

把打包好的apk包, 发到安卓手机上 / 电脑模拟器(推荐<夜神模拟器>) 运行即可

image-20220630175858730

iOS问题

打包ios - 需要申请开发者账号(一年600元人民币): 以后打包的过程参考这个: https://blog.csdn.net/qq_34440345/article/details/99711586

也可以手机给电脑开热点 / 只要连接在同一个wifi下, 手机浏览器访问webpack开发服务器局域网ip地址即可

李老师经验分享

// 写任何需求:
// 章法
// 1. html+css(标签和样式搞定)
// 2. 铺设数据(调整内容, 可能调用接口拿到数据)
// 3. JS(交互/校验….效果), 前端拿到要传递给后台的值
// 4. 与后台交互(调用后台接口, 回显返回数据提示等)

// 技巧1:
// 看到变量, 能马上反应过来这个变量里装的什么
// 每个方法含义, 要什么参数, 返回值有无, 返回值什么意思, 都要马上反应过来
// 每行代码的意思, 为何这么写, 先模仿老师的思路, 锻炼, 多了经验以后就能自己写了
// 以上就多读代码多写代码多讨论积累经验

// 技巧2:
// 前端变量名可以直接跟后端 要求的参数名一致, 这样调用接口就不用再分开写了
// 前端变量名, 如果装对象, 用obj结尾
// 前端变量名, 如果装数组, 用arr或者list结尾
// 前端变量名, 如果装字符串, 用str结尾
// 这样看到变量能马上反应过来里面装的什么

// 技巧3:
// 统一判断http状态码, axios的”响应”拦截器
// axios的”请求”拦截器, 统一给请求配置对象中加入统一的东西
// 例如: 所有的请求都带上请求头字段Authorization和token值

// 技巧4:
// 所有状态一起变的, 一个变量控制所有人
// 每行状态”独立”改变的, 每行对应”对象”里的属性(obj.visible/其他属性), 显示隐藏的状态(2种值切换)

// 技巧5:
// 路由到底是几级的
// 不要光看路径的个数
// 实际:
// 在路由规则数组里的层级

// 技巧6: 什么时候需要提升功能封装
// (1): 多个页面使用的相同功能
// (2): 以后可能要扩展和修改的

// 跨域问题:
// 开发过程:
// 1. 直接让后台开启cors/jsonp, 直接调用(如果用jsonp你要注意你传参的格式)
// 2. 后台不开cors/jsonp, webpack开发服务器, vue.config.js - 代理转发
// 3. 后台不开cors/jsonp, 自己本地node+express搭建服务器(开cors) - 前端请求本地localhost:4005, 本地的请求转发代码(nodejs代码)

// 打包上线:
// 1. 后台开启cors, 直接用
// 2. 后台不开cors/jsonp, 把前端项目和后台项目放在一个服务器上(同源)
// 3. 后台代码和前端代码不在一起, 本地自己写一个node+express服务器部署(请求自己的)

------ 本文结束,感谢您的阅读 ------
本文作者: 程序员青阳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。