前端页面----面试经验页面

1.项目搭建

1.1 项目创建

      使用vue.clb脚手架搭建vue2框架

 vue create interviewexp -m npm  

       项目的配置为:      

1.2 导入模板

        安装vant

npm i vant@latest-v2 --legacy-peer-deps

        使用全局方式导入

import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)

        进行适配

npm i postcss-px-to-viewport@1.1.1 -D --legacy-peer-deps

        在根目录下,新建postcss的配置文件:postcss.config.js,里面的配置代码为

// postcss.config.js
module.exports = {
    plugins: {
        'postcss-px-to-viewport': {
            // 设计稿如果是2倍图,宽是750,则 750/2 = 375,下面就写375
            // 设计稿如果是3倍图,宽是 1080,则 1080/3 = 360,下面就写360
            viewportWidth: 375
        }
    }
}

    

2.项目设计

2.1 配置路由

        在router文件中的index.js配置文件中进行路由配置

const routes = [
	{ path: '/register', component: Register },
	{ path: '/login', component: Login },
	{ path: '/detail', component: Detail },
	{
		path: '/', component: Home,
		redirect: '/article',
		children: [
			{ path: '/article', component: Article },
			{ path: '/collect', component: Collect },
			{ path: '/like', component: Like },
			{ path: '/user', component: User }
		]
	},
	{ path: '*', component: NotFound }
]

            同时在App.vue和Home.vue中设置路由锚点

2.2 页面设计

        将页面分为两类,面试经验、收藏、点赞、我的这四个页面有下侧框,属于一类;

        登录、注册、详情和main为一类。

2.2.1 axios抽取

        使用axios实现前后端的数据的传输。为了解决冗余问题以及携带数据问题,使用axios提供的create方法实现对axios的改造。

//1.引入axios
import axios from "axios";
//2.复制并进行配置
let request = axios.create({
    baseURL: 'http://interview-api-t.itheima.net',//基础路径
    timeout: 5000,//超时计数器
});
//3.输出
export default request;
2.2.2 用户注册

        页面设计 

<template>
	<div class="register-page">

		<!-- 标题 -->
		<van-nav-bar title="面经注册" />

		<!-- 注册表单 -->
		<van-form @submit="onSubmit">
			<!-- 
				用户名
				v-model 双向绑定
				name
				label  框前面文字
				placeholder  框中提示文字
				rules  校验规则
			-->
			<van-field v-model="username" name="username" label="用户名" placeholder="用户名"
				:rules="[{ required: true, message: '请填写用户名' }]" />

			<!-- 密码 -->
			<van-field v-model="password" type="password" name="password" label="密码" placeholder="密码"
				:rules="[{ required: true, message: '请填写密码' }]" />

			<!-- 按钮 -->
			<div style="margin: 16px;">
				<van-button square block type="info" native-type="submit">提交</van-button>
			</div>
		</van-form>

		<!-- 超链接 -->
		<router-link class="link" to="/login">有账号,去登录</router-link>
	</div>
</template>
<style lang="less" scoped>
.link {
	font-size: 13px;
	float: right;
	margin-right: 20px;
	color: dodgerblue;
}
</style>

        功能设计:

         

<script>
import request from "@/utils/request"
export default {
	name: 'register-page',
	data() {
		return {
			username: '',
			password: ''
		}
	},
	methods: {
		async onSubmit(values) {
			try {
				let { data: res } = await request({
					method: 'post',
					url: '/h5/user/register',
					data: values,
				});
				console.log(res.data);
				this.$toast.success('注册成功');
				this.username = this.password = '';
			} catch (error) {
				if (error.response) {
					this.$toast.fail(error.response.data.message);
				} else {
					this.$toast.fail('注册失败,请联系管理员')
				}
			}
		}
	}
}
</script>
2.2.3 用户登录

        页面设计:

<template>
	<div class="login-page">
		<!-- 标题文字 -->
		<van-nav-bar title="面经登录" />

		<!-- 表单 -->
		<van-form @submit="onSubmit">
			<van-field v-model="username" name="username" label="用户名" placeholder="用户名"
				:rules="[{ required: true, message: '请填写用户名' }]" />
			<van-field v-model="password" type="password" name="password" label="密码" placeholder="密码"
				:rules="[{ required: true, message: '请填写密码' }]" />
			<div style="margin: 16px;">
				<van-button square block type="info" native-type="submit">登录</van-button>
			</div>
		</van-form>

		<!-- 超链接 -->
		<router-link class="link" to="/register">没有账号, 去注册</router-link>
	</div>
</template>
<style lang="less" scoped>
.link {
	float: right;
	margin-right: 20px;
	color: #1989fa;
}
</style>

        页面操作:

<script>
import request from '@/utils/request'
export default {
	name: 'login-page',
	data() {
		return {
			username: '',
			password: '',
			data: values,
		}
	},
	methods: {
		async onSubmit(values) {
			try {
				let { data: res } = await request({
					method: 'post',
					url: '/h5/user/login',
					data:values
				});
				this.$toast.success('登录成功');
				console.log(res.data);
				localStorage.setItem('mj-token', res.data.token)//存储token
				this.username = this.password = '';//将页面转换为原来情况
				this.$router.push('/article');//进行跳转
			} catch (error) {
				if (error.response) {
					this.$toast.fail(error.response.$router.data.message);
				} else {
					this.$toast.fail('登录失败');
				}
			}

		}
	}
}
</script>

2.2.4 面经列表

        在request.js中添加拦截器请求头,因为只有在登录成功后获取了服务器给的token才能访问个人页面。

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 添加请求头
    config.headers.Authorization = "Bearer "+localStorage.getItem('mj-token');
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

        一般执行的操作是查,就先实行js;执行的是增删改,就先设计页面。

<script>
import request from '@/utils/request';

export default {
	name: 'article-page',
	data() {
		return {
			current: 1,
			pageSize: 5,
			sorter: 'weight_desc',
			articleList:[]//准备集合接收返回数据
		}
	},
	methods: {
		//查询面经列表
		async findList() {
			try {
				let { data: res } = await request({
					method: 'get',
					url: '/h5/interview/query',
					params: {
						'current': this.current,
						'pageSize': this.pageSize,
						'sorter': this.sorter
					}
				})

				this.articleList = res.data.rows;//获取数据列表
				console.log(this.articleList);
			} catch (err) {
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('获取列表失败');
				}
			}
		}
	},
	created() {
		//进入页面执行查询列表的方法
		this.findList()
	}
}
</script>

        提取组件,展示列表可以做成一个组件,在components中创建ArticleItem.vue组件

<template>
    <van-cell class="article-item">
        <template #title>
            <div class="head">
                <img :src="article.avatar" alt="" />
                <div class="con">
                    <p class="title van-ellipsis">{{ article.stem }}</p>
                    <p class="other">{{ article.creator }} | {{ article.createdAt }}</p>
                </div>
            </div>
        </template>
        <template #label>
            <div class="body van-multi-ellipsis--l2">
                {{ removeHTMLTags(article.content) }}
            </div>
            <div class="foot">点赞 {{ article.likeCount }} | 浏览 {{ article.views }}</div>
        </template>
    </van-cell>
</template>
<script>
export default {
    //接收父组件传入的变量
    props: ['article'],
    methods: {
        //去除字符串中的html标签
        removeHTMLTags(htmlString) {
            return htmlString.replace(/<[^>]+>/g, '');
        }
    }
}
</script>
<style lang="less" scoped>
.article-item {
    .head {
        display: flex;

        img {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            overflow: hidden;
        }

        .con {
            flex: 1;
            overflow: hidden;
            padding-left: 10px;

            p {
                margin: 0;
                line-height: 1.5;

                &.title {
                    width: 280px;
                }

                &.other {
                    font-size: 10px;
                    color: #999;
                }
            }
        }
    }

    .body {
        font-size: 14px;
        color: #666;
        line-height: 1.6;
        margin-top: 10px;
    }

    .foot {
        font-size: 12px;
        color: #999;
        margin-top: 10px;
    }
}
</style>

        将组件声明为全局组件

import ArticleItem from '@/components/ArticleItem.vue'
Vue.component('ArticleItem', ArticleItem)

        最终的article页面代码:

<template>
	<div class="article-page">
		<nav class="my-nav van-hairline--bottom">
			<a href="javascript:;">推荐</a>
			<a href="javascript:;">最新</a>
			<div class="logo"><img src="@/assets/logo.png" alt=""></div>
		</nav>

		<!-- 
			van-list  分页列表组件
			loading: false, 是否正在加载数据, false:没有在加载,此时就可以调用findList方法, 一旦开始调用了,组件会立即将loading设置为true, 表示正在加载, 此时无法进行二次加载
      		finished: false, 表示数据是否全部加载完, false: 没加载完, 还可以继续加载下一页; true, 表示没有可以加载的数据了, 就不能再加载下一页了
			@load="查询数据时候要调用的方法"
		-->
		<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="findList">
			<!-- 使用组件 -->
			<article-item v-for="article in articleList" :article="article" :key="article.id"></article-item>
		</van-list>
	</div>
</template>

<script>
import request from '@/utils/request';


export default {
	name: 'article-page',
	data() {
		return {
			loading: false, //是否正在加载数据, false:没有在加载,此时就可以调用findList方法, 一旦开始调用了,组件会立即将loading设置为true, 表示正在加载, 此时无法进行二次加载
      		finished: false,//表示数据是否全部加载完, false: 没加载完, 还可以继续加载下一页; true, 表示没有可以加载的数据了, 就不能再加载下一页了

			//推荐数据中的前5条
			current: 1,//默认第一页
			pageSize: 10, //每页5条
			sorter: 'weight_desc', // weight_desc: 推荐数据   null: 最新的数据
			articleList: [],//准备集合接收返回数据
		}
	},
	methods: {
		//查询面经列表
		async findList() {
			try {
				let { data: res } = await request({
					method: 'get',
					url: '/h5/interview/query',
					params: {
						'current': this.current,
						'pageSize': this.pageSize,
						'sorter': this.sorter
					}
				})

				//不能再使用=复制,而是每次查询回来的数据追加到集合中
				this.articleList.push(...res.data.rows);
				console.log(this.articleList);

				//本次加载已经完毕, 后续可以进行下一次加载了
				this.loading = false;

				//将页码+1
				this.current++;

				//当页码已经超过了最大页码, 不能继续加载了
				if(this.current > res.data.pageTotal){
					this.finished = true
				}

			} catch (err) {
				console.log(err);
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('面经列表查询失败,请联系管理员');
				}
			}
		}
	},
	// created() {
	// 	//页面加载完成之后,获取面经列表数据
	// 	this.findList();
	// }
}
</script>
2.2.4 页面详情

        页面设计

<template>
	<div class="detail-page">
		<van-nav-bar left-text="返回" @click-left="$router.back()" fixed title="面经详细" />
		<header class="header">
			<h1>{{ article.stem }}</h1>
			<p>
				创建时间:{{ article.createdAt }} | 浏览量:{{ article.views }} | 点赞数:{{ article.likeCount }}
			</p>
			<p>
				<img :src="article.avatar" alt />
				<span>{{ article.creator }}</span>
			</p>
		</header>
		<main class="body" v-html="article.content"></main>
		<div class="opt">
			<van-icon name="like-o" :class="{ active: article.likeFlag }" />
			<van-icon name="star-o" :class="{ active: article.collectFlag }" />
		</div>
	</div>
</template>


<style lang="less" scoped>
.detail-page {
	margin-top: 44px;
	overflow: hidden;
	padding: 0 15px;

	.header {
		h1 {
			font-size: 24px;
		}

		p {
			color: #999;
			font-size: 12px;
			display: flex;
			align-items: center;
		}

		img {
			width: 40px;
			height: 40px;
			border-radius: 50%;
			overflow: hidden;
		}
	}

	.opt {
		position: fixed;
		bottom: 100px;
		right: 0;

		>.van-icon {
			margin-right: 20px;
			background: #fff;
			width: 40px;
			height: 40px;
			line-height: 40px;
			text-align: center;
			border-radius: 50%;
			box-shadow: 2px 2px 10px #ccc;
			font-size: 18px;

			&.active {
				background: #fec635;
				color: #fff;
			}
		}
	}
}
</style>

        操作:

<script>
import request from '@/utils/request';
export default {
	name: 'detail-page',
	data() {
		return {
			article: {},//接受远程服务器传来的对象
		}
	},
	methods: {
		findById() {
			try {
				let { data: res } = request({
					method: 'get',
					url: '/h5/interview/show',
					params: {
						'id': this.id
					}
				});
				console.log(res);
				this.article = res.data;
			} catch (error) {
				console.log(err);
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('获取面经详情失败,请重试');
				}
			}
		}
	},
	created() {
		this.findById();
	}
}
</script>

        点赞和收藏:

       

<template>
    <div class="detail-page">
		.......省略
        <div class="opt">
            <van-icon name="like-o" :class="{ 'active': article.collectFlag }" @click="opt(2)" />
            <van-icon name="star-o" :class="{ 'active': article.likeFlag }" @click="opt(1)" />
        </div>
    </div>
</template>

//js中的代码
async opt(optType) {
			try { 
				let { data: res } = await request({
					method: 'post',
					url: '/h5/interview/opt',
					data: {
						'id': this.article.id ,
						'optType':optType
					}
				})
				console.log(res);
				//执行操作成功
				this.$toast.success('操作成功');
				//重新查询一次
				this.findById();
			} catch (err) {
				console.log(err);
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('点赞/收藏失败,请重试');
				}
			}
		},

2.2.5 个人中心

<template>
  <div class="user-page">
    <div class="user">
      <img :src="user.avatar" alt />
      <h3>{{ user.username }}</h3>
    </div>
    <van-grid clickable :column-num="3" :border="false">
      <van-grid-item icon="clock-o" text="历史记录" to="/" />
      <van-grid-item icon="bookmark-o" text="我的收藏" to="/collect" />
      <van-grid-item icon="thumb-circle-o" text="我的点赞" to="/like" />
    </van-grid>

    <van-cell-group class="mt20">
      <van-cell title="推荐分享" is-link />
      <van-cell title="意见反馈" is-link />
      <van-cell title="关于我们" is-link />
      <van-cell title="退出登录" is-link @click="logout()" />
    </van-cell-group>
  </div>
</template>

<script>
import request from '@/utils/request';
export default {
  name: 'user-page',
  data() {
    return {
      //个人信息
      user: {}
    }
  },
  methods: {
    //退出
    logout() {
      //清理token
      localStorage.removeItem('mj-token');

      //跳转到登录页面
      this.$router.push("/login")
    },

    //查询个人信息
    async findUserInfo() {
      try {
        let { data: res } = await request({
          method: 'get',
          url: '/h5/user/currentUser',
        })

        //获取返回结果中的个人信息赋值到user
        this.user = res.data;
      } catch (err) {
        console.log(err);
        if (err.response) {
          this.$toast.fail(err.response.data.message);
        } else {
          this.$toast.fail('个人信息查询失败,请重试');
        }
      }
    }
  },
  created() {
    //调用查询个人信息
    this.findUserInfo()
  }

}
</script>

<style lang="less" scoped>
.user-page {
  padding: 0 10px;
  background: #f5f5f5;
  height: 100vh;

  .mt20 {
    margin-top: 20px;
  }

  .user {
    display: flex;
    padding: 20px 0;
    align-items: center;

    img {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      overflow: hidden;
    }

    h3 {
      margin: 0;
      padding-left: 20px;
      font-size: 18px;
    }
  }
}
</style>
2.2.5 我的收藏
<template>
	<div class="collect-page">
		<!-- 标题文字 -->
		<van-nav-bar title="我的收藏" />

		<!-- 收藏列表 -->
		<!-- 分页组件 
			loading变量:控制是否显示加载中按钮 true:显示提示,不能再执行加载操作  false:不显示,可以再执行加载操作
			@load="获取数据的方法"
		-->
		<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="findList">
			<article-item v-for="article in articleList" :article="article" :key="article.id"></article-item>
		</van-list>
	</div>
</template>

<script>
import request from '@/utils/request'
export default {
	name: 'collect-page',
	data() {
		return {
			loading: false,
			finished: false,
			current: 1,
			pageSize: 5,
			articleList: []//准备集合接收返回数据
		}
	},
	methods: {
		//查询面经列表
		async findList() {
			try {
				let { data: res } = await request({
					method: 'get',
					url: '/h5/interview/opt/list',
					params: {
						'page': this.current,
						'pageSize': this.pageSize,
						'optType': 2
					}
				})

				this.articleList.push(...res.data.rows);//获取数据列表,追加到list

				//去除加载中字样
				this.loading = false;

				//页码+1
				this.current++;

				//如果加载完毕
				if (this.current > res.data.pageTotal) {
					this.finished = true;
				}
			} catch (err) {
				console.log(err);
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('获取列表失败');
				}
			}

		}
	}
}
</script>

<style lang="less" scoped>
.collect-page {
	margin-bottom: 50px;
}
</style>
2.2.6 我的点赞
<template>
	<div class="collect-page">
        <!-- 标题 -->
        <van-nav-bar title="我的点赞" />

		<!-- 
			van-list  分页列表组件
			loading: false, 是否正在加载数据, false:没有在加载,此时就可以调用findList方法, 一旦开始调用了,组件会立即将loading设置为true, 表示正在加载, 此时无法进行二次加载
      		finished: false, 表示数据是否全部加载完, false: 没加载完, 还可以继续加载下一页; true, 表示没有可以加载的数据了, 就不能再加载下一页了
			@load="查询数据时候要调用的方法"
		-->
		<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="findList">
			<!-- 使用组件 -->
			<article-item v-for="article in articleList" :article="article" :key="article.id"></article-item>
		</van-list>
	</div>
</template>

<script>
import request from '@/utils/request';
export default {
	name: 'like-page',
	data() {
		return {
			loading: false, //是否正在加载数据, false:没有在加载,此时就可以调用findList方法, 一旦开始调用了,组件会立即将loading设置为true, 表示正在加载, 此时无法进行二次加载
			finished: false,//表示数据是否全部加载完, false: 没加载完, 还可以继续加载下一页; true, 表示没有可以加载的数据了, 就不能再加载下一页了

			optType: 1,
			pageSize: 10,
			page: 1,
			articleList: []
		}
	},
	methods: {
		//查询面经列表
		async findList() {
			try {
				let { data: res } = await request({
					method: 'get',
					url: '/h5/interview/opt/list',
					params: {
						'page': this.page,
						'pageSize': this.pageSize,
						'optType': this.optType
					}
				})

				//不能再使用=复制,而是每次查询回来的数据追加到集合中
				this.articleList.push(...res.data.rows);
				console.log(this.articleList);

				//本次加载已经完毕, 后续可以进行下一次加载了
				this.loading = false;

				//将页码+1
				this.page++;

				//当页码已经超过了最大页码, 不能继续加载了
				if (this.page > res.data.pageTotal) {
					this.finished = true
				}
			} catch (err) {
				console.log(err);
				if (err.response) {
					this.$toast.fail(err.response.data.message);
				} else {
					this.$toast.fail('点赞列表查询失败,请联系管理员');
				}
			}
		}
	},
}
</script>

<style lang="less" scoped>
.collect-page {
	margin-bottom: 50px;
}
</style>
2.2.7 响应拦截器
import router from "@/router";
request.interceptors.response.use(function (response) {
    // 响应码2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    if (error.response) {
        if (error.response.status === 401) {
            //移除token
            localStorage.removeItem('mj-token')
            //跳转到登录页面
            router.push('/login')
        }
    }
    return Promise.reject(error)
})
2.2.8 主题定制

        引入less

import Vant from 'vant'

        在vue.config.js文件中进行配置

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
    transpileDependencies: true,
    css: {
        loaderOptions: {
            less: {
                // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
                lessOptions: {
                    modifyVars: {
                        // 直接覆盖变量
                        blue: '#FA6D1D',
                        // 'text-color': '#111',
                        // 'border-color': '#eee'
                        // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
                        // hack: 'true; @import "your-less-file-path.less";'
                    }
                }
            }
        }
    }
})

3.注:

个人中心中仅实现用户名和用户头像以及退出登录功能。

源代码链接:https://pan.baidu.com/s/1pWfAASzsf2pOu0d88vxY0Q?pwd=vje2 
提取码:vje2 
--来自百度网盘超级会员V5的分享

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值