# maizuo **Repository Path**: chaihongjun/maizuo ## Basic Information - **Project Name**: maizuo - **Description**: 仿卖座网 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-11-04 - **Last Updated**: 2024-12-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Vue 阶段综合演练 [TOC] # 一、项目概要 ## 1、效果前瞻 仿照网站:卖座网 http://m.maizuo.com ![效果预览](https://storage.lynnn.cn/assets/markdown/91147/pictures/2020/10/cbf14ac0b60d10ba7cac70cd93f87af85d26a06b.png?sign=3d94c060b86cc17a523f2f153bfc4485&t=5f7c13a9) 说明: - 可能会有一些偏差 - 课上由于时间关系,并不会花太多时间写样式 ## 2、开发流程(重点) 熟悉一个项目从 0-1 流程? - 产品**立项** (需求分析、技术选型、项目人员确定),产出立项报告 - 项目立项报告(百度文库搜)【产品经理,PM】 - 当前背景 - 项目需求 - 人员安排 - 功能介绍 - 市场需求 - 项目风险 - 产品原型 (设计产品**原型图**)【产品经理】 - 产品原型图(通过简单的黑白线条勾勒出项目的初始界面效果) - 进行 ui 设计(效果图) - 依据用户的视觉体验给界面加上了颜色 - 项目开发 (前端 与 后端)【周期最长的一步】 周期最长的 - 设计(UI):设计图和切图【UI 人员】 - 前端:出一版静态页(模板) - 以前:html+css+js+其他库文件 - 现在: - v - r - a - 后端:服务器端 - 写接口 - 搭服务器 - 写业务逻辑 - 前后端整合 - 耦合式开发(也还算行,模版引擎) - **前后端分离式(幸福,只需要看前端代码即可)** - 产出 v1.0 的代码 - 项目测试 - 测试部门:QA 人员(质量保障) - 测试 - 内测 - 公测(大公司的产品) - 项目上线 - 运维&后端&前端 面试会问的问题:之前的团队的人员配置。 答:这个回答需要取决公司的规模,比方说,以美团为例,研发岗 170 个人。前端:40 个,后端:50 个,运维:30 个,设计:30 个,产品经理:10 个,测试:10。 如果是小规模,则例如 20 个人总共,前端可以为 5 个,后端 5 个人,运维 2,测试 2 个,产品 2-3 个人,UI:3-4 人。 ## 3、开发环境 - 开发工具:vs code 并安装 vue 开发插件:Vetur - 开发环境:windows / mac (mac 重度患者优先考虑) - 项目运行环境:node v10+ - Vue 脚手架: vue-cli 4+ - 代码版本工具:**Git**/Svn(乌龟 SVN),**Gitee**/GitLab/Github,乌龟 Git(基于可视化界面的 git 工具) # 二、初始化及必要知识点 ## 1、初始化远程仓库 > 以 Github 为例:https://github.com/ - 创建一个新项目 ![创建项目](https://storage.lynnn.cn/assets/markdown/91147/pictures/2020/10/306929e959182a95bde37358ccab47ce0842b100.png?sign=1a495fc0bde2637359bee1e4d0c92f50&t=5f7d46b9) - 仓库配置 仓库名称自行决定。 ![仓库配置](https://storage.lynnn.cn/assets/markdown/91147/pictures/2020/10/f4d0bdf4868f9e4391a08e500c3b41fa265bde96.png?sign=d4e36722cdba0f62c2ec2c0c352b57a5&t=5f7d4765) > 以 gitee 为例:https://gitee.com - 在主页的右上角选择“+”号,展开后,选择“新建仓库” ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/05/05d25b3019fb3294f5889687359454d287d69f9d.png?sign=d180ff0a2c0196203f44048710f04823&t=6093a0b4) - 填写仓库基本的信息,只需要填写仓库名称即可 ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/05/b2ad1a75584e3c31f16ef9db0506f770f4291660.png?sign=22a688e30d88217b0e0802289a3296e3&t=6093a1a4) - 创建好的仓库界面 ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/05/50886fd8d6286dd7efa67ac935fb3df2068be8e3.png?sign=aea47bf3d46520461308dfdc1bcf2f10&t=6093a202) ## 2、创建项目 - 使用`vue-cli`脚手架创建项目 ```shell vue create i-moive ``` > 项目名称`i-moive`可以根据自己的情况进行替换 - 脚手架创建项目询问式选择回答如下 ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/ec36518639ae7694c5ec5b60756a8360aace19bc.png?sign=66a52052e9a6e73635ae1833c113b1fa&t=6065456c) ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/0b23bb6c4321a823c2e101db83838b0753c19f56.png?sign=79ae440abeb051fc90f5025dd77a34db&t=606545b8) ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/696828a88d1d25c2e25b33931f8ba09b0723308b.png?sign=d00a3e63f95783b0777cbb7320918ba6&t=606545e8) ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/b66f81459654972a8a1c85e49e64532b5119d358.png?sign=1af8db20cd700f2311537fb6ccff9bab&t=6065460a) > 使用历史路由模式,在上线部署的时候需要做服务端的重写配置,否则项目不支持刷新,一刷新就 404。 ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/997c871df05b0cf46a7d654c82475b3ee153ee15.png?sign=12c41f8be26ab41a498941f8ac0ea8aa&t=6065463c) ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/e39f4222e65e502329a9e151b51d3315eefcd00d.png?sign=ec0c061f8dd169d350c77423a7745663&t=60654654) 其实 vue 也支持界面化的方式管理项目(与`vue create`命令二选一): ```shell vue ui ``` ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/05/52cbe29c964ae11c1acec5123c34e70b19cfce8d.png?sign=33c9b2bd039c51936f9558b22b483711&t=6093a720) - 项目创建完毕 ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/04/d68f92025a93ea9f43eac9cbdca2f0572ad4903d.png?sign=1136006de9c9f74a4e33167e3ffdbb3b&t=6065473b) - 同步初始化项目到远程仓库 ```shell # i-moive,根据自己的项目名称进行替换 cd i-moive git remote add origin 远程仓库地址 # 将本地当前的分支代码上传到远程的master分支中 git push -u origin master ``` - 创建开发分支`dev`(用于做测试的分支,等测试代码没问题之后再往主分支上合并) > 以后实际工作是 master 分支为最终稳定运行的版本的代码,而在开发期间提交的代码一般会提交给开发分支,待后期测试没问题,再与 master 分支进行合并(pull request)。 ```shell git branch dev git checkout dev git push -u origin dev ``` > 后续操作开发就在 dev 分支上开发,等全部代码编写完毕,再与 master 分支合并。 - (==可选操作==)为当前项目设置记住密码/**SSH 免密登录** > 如果每次提交都提示输入帐号密码,则可以做此步骤。 > > 修改当前项目中的`.git/config`文件 将配置: ```config [remote "origin"] url = https://github.com/...... ``` 修改为: ```config [remote "origin"] url = https://用户名:密码@github.com/...... ``` **后续每天工作使用 Git 指令是什么??** ```shell # 将远程仓库的代码拉取到本地 git pull # 写代码的环节 # 写好代码 git add . git commit -m "注释" # 下班 git push # 打卡下班 ``` ## 3、路由规划 在后续开始之前先对项目进行一个清理,删除不需要的东西: - 删除`src/assets/logo.png`文件 - 删除`src/components/HelloWorld.vue`文件 - 删除`src/views/Home.vue`和`src/views/About.vue`文件 - 修改`src/router/index.js`,删除对`Home.vue`和`About.vue`的引用 - 删除`Home.vue`de 的引入 - 删除根路由规则 - 删除`/about`路由规则 - 修改`src/App.vue`文件 - 删除`id="nav"`的 div 元素 - div 的`id="app"`这个属性也可以删除 - 清除`style`标签之间的所有的样式 最终清理完毕的标志:页面是白的,控制台没有任何报错和警告。 ———————————————————————————————————————————— 如果项目中所有的路由都写在入口文件中,那么将不便于编写项目和后期维护。因此路由需要进行模块化处理。 可以**先行**添加以下几个空的路由模块: - 电影模块 - 正在热映 - 即将上映 - 影院模块 - 个人中心模块 > 如果后续还有其他模块,届时再进行增加即可。 **创建各个模块对应的视图组件文件** > - 在`src/views`目录下创建对应的文件夹与文件,同时,可以删除自带的`Home.vue`与`About.vue`文件 > > - 创建每个视图组件后在其中书写好基本内容 > > - ```html > > ``` ```text src/views ├─Center (个人中心) │ └─Index.vue │ ├─Cinemas (电影院) │ └─Index.vue │ ├─News (资讯) │ └─Index.vue │ └─Films (电影) │ Index.vue │ NowPlaying.vue └─ComingSoon.vue ``` **创建模块化的目录及路由文件** > 在每个路由模块文件中注册好对应的路由及各自所使用的视图组件 ```text src/router ├─index.js │ └─modules │ center.js │ cinemas.js │ news.js └─films.js ``` **在剔除`router/index.js`中无用代码后,示例代码如下** ```javascript import Vue from 'vue'; import VueRouter from 'vue-router'; Vue.use(VueRouter); // 导入模块化后的路由模块文件 import filmRoutes from './modules/films'; import cinemasRoutes from './modules/cinemas'; import newsRoutes from './modules/news'; import centerRoutes from './modules/center'; const routes = [ // 路由重定向 { path: '/', redirect: '/films/nowPlaying', }, // 电影模块的路由 ...filmRoutes, // 电影院模块路由 ...cinemasRoutes, // 资讯模块路由 ...newsRoutes, // 我的模块路由 ...centerRoutes, ]; const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes, }); export default router; ``` ## 4、反向代理配置 为什么需要配置:接口有很多都是存在跨域问题的,但是 cors、jsonp 的方式都是依赖后端去解决支持问题,所以只能考虑代理操作。 配置 vue 的代理操作需要注意以下几点: - 后续使用的配置是基于 node 的,不是 vue 的 - 代理只在本地的开发环境下生效,打包上线后就没了 - 上线时可能需要更改实际请求的地址(cors) - 针对 vue 项目的代理配置需要在项目的根目录下创建文件“vue.config.js”,**切勿写错名字** - 以下配置不要去背(webpack 的配置),认识即可 - 这个文件修改之后需要重启项目才能生效(对于 vue 根目录下的其他配置文件也适用) ```json module.exports = { // 开发服务器设置 devServer: { open: true, // 设置 npm run serve 启动后的端口号 port: 3000, // 如果你开始了eslint,不要让eslint在页面中遮罩,它错误会在console.log控制台打印 overlay: false, // vue项目代理请求 proxy: { // 规则 // axios中相对地址开头的字符串 匹配请求uri中的前几位 "/api": { // 把相对地址中的域名 映射到 目标地址中 // localhost:3000 => https://api.iynn.cn/film/api/v1/ target: "https://api.iynn.cn/film/api/v1", // 修改host请求的域名为目标域名 // changeOrigin: false, changeOrigin: true, // 请求uri和目标uri有一个对应关系 // 请求/api/login ==> 目标 /v1/api/login pathRewrite: { "^/api": "", }, }, }, }, }; ``` ## 5、网络请求封装 **在封装前请先安装 axios** ```shell npm i -S axios ``` **步骤:** - 请求地址文件配置 - 路径:config/uri.js - 好处:后期接口地址如果发生了变化,我们可以统一去管理和修改 ```javascript // 作用:对请求地址的配置,简化原本很长的地址写法 let prefix = '/api/'; export default { // 声明各个请求地址 // 城市信息 getCities: prefix + 'getCitiesInfo', // 正在热映 getNowList: prefix + 'getNowPlayingFilmList', // 即将上映 getSoonList: prefix + 'getComingSoonFilmList', // 。。。 }; ``` - 封装请求文件 - 路径:api/http.js ```javascript import axios from 'axios'; // 可以对axios进行封装 // 以往在学习使用axios的时候每次取获取数据的结果都是从ret.data中获取 // 这种写法很是不方便,我们可以在此处对axios进行改造,让返回的ret就等同于以前的ret.data // 拦截器:此处是对返回结果其实就是响应进行处理,所以得使用响应拦截器 // var a = 'index.php?' // a + 'username=zhangsan' axios.interceptors.response.use((ret) => { // 将ret.data换成ret return ret.data || ret; // if (ret.data) { // return ret.data; // } else { // return ret; // } }); // 请求拦截器 // axios.interceptors.request.use(); export default axios; ``` - 注册 axios 到 vue 实例上 - 好处:后续操作简单,因为每个组件中都有 vue 实例`this`,注册到实例上后续可以直接通过 this 调用,而不再需要每次都 import`http.js` - 修改文件:main.js ```javascript import Vue from 'vue'; import App from './App.vue'; import router from './router'; import store from './store'; // 导入axios import axios from './api/http'; // 将axios注册到vue实例上(原型上) Vue.prototype.$http = axios; Vue.config.productionTip = false; new Vue({ router, store, render: (h) => h(App), }).$mount('#app'); ``` - 测试可用性 - 随便找一个组件测试,测试代码测试完毕之后需**要删除** ```vue ``` ## 6、vant 组件配置 官网:https://vant-contrib.gitee.io/vant/#/zh-CN/ Vant 是有赞前端团队开源的**移动端**组件库(Vue 的 pc 端一般采用 element UI),于 2017 年开源,已持续维护 4 年时间。Vant 对内承载了有赞所有核心业务,对外服务十多万开发者,是业界主流的移动端组件库之一。 **配置使用步骤** - 安装 ```shell npm i -S vant ``` - 引入组件 ```shell npm i -D babel-plugin-import ``` ```javascript // 对于使用 babel7 的用户,可以在 /babel.config.js 中配置 module.exports = { plugins: [ [ 'import', { libraryName: 'vant', libraryDirectory: 'es', style: true, }, 'vant', ], ], }; ``` - 导入&使用 UI 组件 - import ... from ... - Vue.use( ... ) - # 三、功能开发(一) ## 1、导航实现 ### 1.1、底部导航 vant 组件:https://vant-contrib.gitee.io/vant/#/zh-CN/tabbar 组件文件名:src/components/Nav/GlobalTabBar.vue a. 需要创建上述组件,将对应的组件在合适的位置做引入并使用 > 底部菜单在大部分时候都是需要展示的,因此建议将底部导航组件 GlobalTabBar 放到全局组件 App.vue 中。 ```vue ``` b. 完善底部菜单的实现 ```vue ``` c. 解决问题: - 图标问题 - 思路:使用阿里矢量图 - 点击之后页面显示没变化的问题 - 解决地址不变的问题 - 解决地址变后刷新页面索引归零的问题 最终实现代码如下: ```vue ``` ### 1.2、电影模块顶部导航 vant 组件是:https://vant-contrib.gitee.io/vant/#/zh-CN/tab 项目组件文件名:src/components/Nav/FilmsHeader.vue a. 在上述的位置新建组件,然后在合适的位置导入该组件 > 合适的位置:src/views/Films/Index.vue ```vue ``` b. 设置 FilmsHeader.vue 组件的内容,实现基本的展示没问题 ```vue ``` c. 解决功能实现后的 bug - 点击 tab 菜单后,页面内容和地址没有发生变化 - 页面刷新后位置会归零 ```vue ``` ## 2、电影模块 ### 2.1、实现正在热映列表 思路: - 调用接口去获取后端提供的列表的数据 - 使用获取到的数据展示出来基本的效果 - 挑选合适的组件来展示 - 卡片组件:https://vant-contrib.gitee.io/vant/#/zh-CN/card - 循环遍历数据,将数据展示在组件上 - 样式问题 - 上滑加载更多的效果实现 注意点: 电影列表在展示的时候需要使用到 vant 的卡片组件,卡片组件需要使用第三个自定义组件展示,其中使用到了类似于如下的写法: ```vue ``` 其中,其`template #tags`写法实质上就是具名插槽的写法(slot="tags"),在 card 组件中,其所有的展示内容用的属性都支持改写成插槽形式。例如,价格属性`price`可以写成以下形式放到 card 组件标签之间: ```vue ``` 之所以会有这种写法是因为如果要展示的内容在属性里的话,则给其制定样式非常不灵活;而我们把属性的内容做成插槽形式,则可以写很多标签,进而可以灵活的写样式。 a. 电影模块顶部导航吸顶的效果 修改组件文件:src/components/Nav/FilmsHeader.vue 书写固定定位的样式: ```css .sticky { position: fixed; z-index: 999; width: 100%; } ``` 设置吸顶默认值为 false,即不吸顶,并在 div 上应用: ![](https://storage.lynnn.cn/assets/markdown/91147/pictures/2021/07/17fac0f6c7f446d2342302268a3273e94ea10c5b.png?sign=3c6f819aff7f247abcc5e8b1f4341a05&t=60deda82) 获取滚动条的高度来决定是否应用吸顶效果: ```js // 挂载完毕后获取滚动条的高度 mounted() { // 监听滚动事件以实时获取滚动条高度 addEventListener("scroll", () => { let scrollTop = document.documentElement.scrollTop; if (scrollTop >= 200) { // 固定顶部导航 this.isSticky = true; } else { // 不固定顶部导航 this.isSticky = false; } }); }, ``` b. 实现基本的电影列表数据展示(不带分页的实现) ```vue ``` c. 实现加载更多(带分页) 加载更多的实现也需要使用 vant 组件:https://vant-contrib.gitee.io/vant/#/zh-CN/list > 注意:使用 list 组件的时候只需要复制 list 组件的代码即可,其里面的内容即列表内容,我们已经有 card 组件了,所以不要多复制。换句话来讲,我们需要通过 van-list 包裹 van-card,而不是全盘复制文档的代码,需要选择性复制。 List 组件通过 `loading` 和 `finished` 两个变量控制加载状态,当组件滚动到底部时,会触发 `load` 事件并将 `loading` 设置成 `true`。此时可以发起异步操作并更新数据,数据更新完毕后,将 `loading` 设置成 `false` 即可。若数据已全部加载完毕,则直接将 `finished` 设置成 `true` 即可。 ```vue ``` ### 2.2、实现即将上映列表 - 作业 - 注意点: - 按钮上的文案变化为“预购” - 电影描述的地方有一个`上映时间`字段,该字段对应接口响应中的`premiereAt`字段,该字段格式是时间戳,单位秒,记得转化 ### 2.3、电影详情的实现 思路: - 产生电影详情的组件和路由规则 - 赋予每个电影内容一个点击事件,允许其被点击,并且在点击事件中要有一个编程式路由且携带电影的 id 号(动态路由参数的形式传递电影的 id 号) - 进入详情组件之后获取电影的 id 号,依据 id 号获取电影详情内容 - 再使用插值/循环等模板语法进行对内容的输出 a. 创建空白组件及路由规则 组件位置:src/views/Films/Detail.vue ```js // src/router/modules/films.js // 电影详情 { path: "/film/:filmID", component: Detail } ``` b. 给卡片添加点击事件,点击之后跳转到详情页面去 ```vue ``` 对应的事件处理程序: ```js // 去详情 show_detail(filmID) { this.$router.push("/film/" + filmID); }, ``` c. 在详情组件中获取 id 号,并依据 id 号获取电影详情 先在地址配置文件 src/config/uri.js 中配置详情的地址: ```js // 获取电影详情信息 getDetail: prefix + "getFilmInfo", ``` d. 使用模板语法输出基本的数据 在做基础数据展示的时候要考虑上映时间格式处理,先安装 moment 包(npm i -S moment),然后创建过滤器对时间进行处理: ```js // 导入moment包 import moment from "moment" // 过滤器 filters: { // 处理时间戳 parseTime(timestamps) { // 语法:moment(时间戳).format(格式化) return moment(timestamps * 1000).format("YYYY-MM-DD"); }, }, ``` 演职人员(包括后面的剧照)使用的时候一种可以滑动的效果,对于这种滑动/轮播的效果,一般前端采用 swiper 来实现(swiper 一般分为两类,主流前端组件库自带的和独立于组件库的 swiper)当前组件库 vant 中的 swipe 组件只能实现全屏的滚动效果,不能实现演职人员需求效果,因此本功能的实现是需要借助于独立于 vant 组件库的第三方组件:swiper,地址:https://www.swiper.com.cn/ 采用 ES6 模块化规范使用 swiper 1. 安装 swiper:npm i -S swiper 2. 注意点:在我们使用真实的数据去结合 swiper 使用的时候,会出现只看到第一张图片的情况,后续图片虽然有,但是无法滑动,即便滑动了释放鼠标又回去了。原因是之前实现 demo 的时候数据是死的,因此 swiper 知道有多少个元素,给多大的宽度;但是现在数据真实了,数据是通过异步获取再赋值、再循环产生的,这个过程的执行远远比 new swiper 来的慢一些,swiper 实例就在产生的时候是无法得知有多少个子元素需要展示,也就无法计算出应该给大的 div 多大宽度,因此显示异常。 3. 虽然使用定时器可以解决上述的问题,但是代码不够优雅,第二个原因是时间设置成多少我们无法准确获知,因此不建议使用。请使用 vue 里提供的异步渲染方法:this.$nextTick。语法如下: ```js this.$nextTick(() => { // 等待异步结束之后再去执行里面的代码 }); ``` 具体实现: ```js created() { // 获取电影id号 let filmID = this.$route.params.filmID; this.$http.get(uri.getDetail + "?filmId=" + filmID).then((res) => { this.filmInfo = res.data.film; // 演职人员信息只有在接口获取到数据之后才有 // 定时器可以解决异步渲染的问题(但是不推荐) // setTimeout(() => { // new Swiper(".swiper-container", { // // 每页显示4个 // slidesPerView: 4, // // 每个之间的间距 // spaceBetween: 30, // }); // }, 1000); // vue中nextTick this.$nextTick(() => { new Swiper(".swiper-container", { // 每页显示4个 slidesPerView: 4, // 每个之间的间距 spaceBetween: 30, }); }) }); }, ``` 4. 图片在加载的时候可能因为网络及图片的大小导致图片的加载时候过长,然后突然一下蹦跶出来了,对于用户体验不是很好,此处可以进行优化:使用图片懒加载(在网络图片加载时,先用一个体积小、样式统一、带有公司/项目标识的图片替代,等待网络图片请求完毕后,再做替换)。 1. 安装一个三方包:vue-lazyload(npm i -S vue-lazyload) 2. 需要找一张用于替代显示的图片(真实项目中一般设计会提供,带有企业 logo 或者项目 logo 的图片) 3. 在项目入口文件 main.js 中做出如下代码操作(指定图片) ```js // 导入懒加载 import VueLazyload from 'vue-lazyload'; // 使用懒加载指定图片 Vue.use(VueLazyload, { loading: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.lgpc.com.cn%2F1%2Fimg%2Ftime.gif&refer=http%3A%2F%2Fwww.lgpc.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1627874508&t=e726a0c4aeab3d62e454a2f98ce9fb33', }); ``` 4. 只需要在需要懒加载的图片位置,将原本的 src 属性名,改成`v-lazy`指令即可,例如电影详情中的海报的展示: ```html
``` ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/).