本次项目基于vue.js,react.js可以看这链接。
前端框架:vue.js,element-ui/iview,脚手架工具vue-cli3.0+
1.官网下载安装node.js,一路回车默认安装就好。附链接:node.js官网
安装完成后打开cmd/powershell查看
2.安装脚手架工具vue-cli,现在的话使用3.0以上版本的偏多。vue3.0以上有vite支持,有兴趣的可以查看我以前探坑写过的一篇文章vite构建vue3.0项目踩坑
本文采用vue2.6.x版本加最新的vue-cli。
3.全局安装vue-cli(npm与cnpm网上很多,不多讲了)
cnpm install vue-cli -g
安装后输入vue -V 查看是否安装成功 (注意这里是大写的V)
若在此之前已经安装过vue-cli3.0以下版本的,需要先卸载低版本(uninstall),再安装。
cnpm install vue-cli -g
4.我们下一个网易云音乐 NodeJS 版 API
下载到本地后npm i,node app.js运行方便用于我们后面的api封装和调试。
至此,就像是打王者,配置好了铭文,装备方案,快捷消息,约上了野王,法王组队,可以开始进入游戏了。
在我们的学习盘新建一个文件夹study,打开powershell,输入vue create my-vue
其中 my-vue 是我们创建的项目的名字。
创建好之后如下图:
按照提示,输入cd my-vue进入项目,再输入npm run serve就可以启动项目了。成功启动之后会看到如下页面:一般默认为 http://localhost:8080/
至此,项目构建完成了,接着开始我们的开发。
安装element-ui,axios,vue-router,less/sass/scss,vuex(根据实际情况看是否需要用到)。
element-ui: element官方
axios: axios使用说明
vue-router: vue-vouter官方
less: npm install less less-loader --save
less文档
vuex: vuex官方文档
对public下的index.html文件简单修改一下默认样式,根据自己实际需要修改或引入reset.css
cli3.0以后构建的项目需要自己配置vue.config.js文件
内容大致如下
var path = require('path')
const ENV = process.env.NODE_ENV
// console.log(ENV)
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
//关闭eslint规范
lintOnSave: false,
devServer: {
// port: 8080,
// host: 'localhost',
// 代理
proxy: {
'/api': {
target: 'http://localhost:8090',
pathRewrite: {
'^/api': '/hock'
}
}
}
},
chainWebpack: config => {
// 参考 https://cli.vuejs.org/zh/guide/webpack.html#%E9%93%BE%E5%BC%8F%E6%93%8D%E4%BD%9C-%E9%AB%98%E7%BA%A7
// git: https://github.com/neutrinojs/webpack-chain
// 通用配置区域
// 配置别名
config.resolve.alias
.set('@src', resolve('src')) // 代码根目录
.set('@components', resolve('src/components')) // 代码组件目录
.set('@api', resolve('src/api')) // 代码接口层根目录
.set('@style', resolve('src/style')) // 代码通用样式目录
.set('@page', resolve('src/page')) // 业务代码目录
.set('@assets', resolve('src/assets')) // 资源目录
.set('@public', resolve('public'))
// 环境配置区域
if (ENV == 'development') {
// 开发环境配置
} else if (ENV == 'production') {
// 部署环境配置
}
}
}
在src目录下新建router文件夹和views文件夹,分别创建index.js和login.vue文件。
路由分hash模式和history模式,我喜欢干净点,不带#号的,采用history模式。
在index文件中写入:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const routes = [{
path: '',
name: 'login',
component: (resolve) => require(['../views/login.vue'], resolve)
}]
const router = new Router({
mode: 'history',
routes,
})
export default router
在main.js文件引入router(顺便把element也引了吧,反正后面要用)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
再对我们的App.vue文件修改一下,删除#app下的所有,引入router-view
<template>
<div id="app">
<router-view />
div>
template>
登录应该都会写,先来个简单的看效果:
login.vue
<style lang="less">
.login {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
style>
<template>
<div class="login">
这是登录
div>
template>
<script>
export default {
data() {
return {
}
},
created() {},
mounted() {}
}
script>
点击登录跳转到我们的主界面,需要在views下新增home.vue文件,并添加对应路由(router/index.js)
this.$router.replace({ name: "home" });
使用element的Container 布局容器结合菜单组件一起使用,完成之后如下:
这里的el-aside和el-menu需要自己绑定一下样式,menu组件都是element的被我删减了,代码如下
<style lang="less" scoped>
.home-container {
width: 100%;
height: 100%;
.header {
display: flex;
align-items: center;
justify-content: space-between;
.header-right {
display: flex;
align-items: center;
.icon-box {
width: 46px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.avatar {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 50px;
height: 50px;
display: block;
border-radius: 25px;
cursor: pointer;
}
}
.nickname {
height: 60px;
line-height: 60px;
padding: 0 10px;
box-sizing: border-box;
}
}
}
.main {
background: #f6f8f9;
}
}
style>
<style lang="less">
.home-container .el-container {
height: 100vh;
}
.el-aside {
transition: all 0.5s ease;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
style>
<template>
<section class="home-container">
<el-container>
<el-aside :width="`${isCollapse ? '66px' : '202px'}`">
<el-menu
default-active="1-4-1"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
:collapse="isCollapse"
>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-location">i>
<span slot="title">导航一span>
template>
el-submenu>
el-menu>el-aside
>
<el-container>
<el-header class="header">
<div class="header-left">
<i
v-show="!isCollapse"
@click="isCollapse = !isCollapse"
class="el-icon-s-fold"
style="cursor: pointer"
/>
<i
v-show="isCollapse"
@click="isCollapse = !isCollapse"
class="el-icon-s-unfold"
style="cursor: pointer"
/>
div>
<div class="header-right">
<div class="icon-box">
<el-badge :value="6" class="item">
<i class="el-icon-message" />
el-badge>
div>
<div class="avatar">
<img class="avatar" src="../assets/avatar_01.png" />
div>
<div class="nickname">五更月div>
<div class="icon-box">
<i class="el-icon-arrow-down" />
div>
div>
el-header>
<el-main class="main">
<router-view>router-view>
el-main>
el-container>
el-container>
section>
template>
<script>
export default {
data() {
return {
isCollapse: false,
};
},
created() {},
mounted() {},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
},
};
script>
可以把aside,header单独拎出来做组件,后面组件化再说吧。
接下来就是我们的菜单跳转了,点击menu跳转对应的界面。
这里涉及到子路由children了。这里我以网易云音乐api为调用举例,页面命名采用music。
在views文件夹下新建music文件夹,新建index.vue,search.vue,playMusic.vue三个文件。
playmusic和search先不写内容,写上两个文案即可,用于验证路由跳转是否成功。
<style lang="less">
.music-container {
position: relative;
}
style>
<template>
<div class="music-container">
<router-view>router-view>
div>
template>
<script>
export default {
name: 'music',
}
script>
然后配置路由文件index.js,基于home往下配置children
const routes = [{
path: '',
name: 'login',
component: (resolve) => require(['../views/login.vue'], resolve)
},{
path: '/home',
name: 'home',
component: (resolve) => require(['../views/home.vue'], resolve),
children: [
{
path: '/home/music',
name: 'music',
// redirect: '/home/music/findMusic',
component: (resolve) => require(['../views/music/index.vue'], resolve),
children: [
{
path: '/home/music/findMusic',
name: 'searchMusic',
component: (resolve) => require(['../views/music/search.vue'], resolve),
},
{
path: '/home/music/playMusic',
name: 'playMusic',
component: (resolve) => require(['../views/music/playMusic.vue'], resolve),
}
]
}
]
}]
OK,回到我们的home.vue文件,配置菜单开始验证跳转
给el-menu添加router属性,暂时先不考虑三级菜单的情况对menu稍微改造一下:
<el-menu
:default-active="activePath"
class="el-menu-vertical-demo"
router
@open="handleOpen"
@close="handleClose"
:collapse="isCollapse"
>
<el-submenu index="1" v-for="(item, index) in menuList" :key="index">
<template slot="title">
<i :class="`${item.icon}`">i>
<span>{{ item.title }}span>
template>
<el-menu-item-group>
<el-menu-item
v-for="items in item.submenu"
:key="items.id"
:index="`${items.path}`"
>{{ items.title }}el-menu-item
>
el-menu-item-group>
el-submenu>
el-menu>
默认展开在mounted添加一段代码
this.activePath = this.$route.path
menuList配置:
menuList: [
{
name: "findMusic",
title: "网易云",
icon: "el-icon-service",
id: "0",
submenu: [
{
name: "findMusic",
title: "发现音乐",
icon: "",
id: "0-1",
path: "/home/music/findMusic",
},
{
name: "playMusic",
title: "播放音乐",
icon: "",
id: "0-2",
path: "/home/music/playMusic",
},
],
},
],
好了,完成之后打开可以正常跳转了,效果如下:
到此,就可以按照这个顺序编写页面样式开发了,接下来说一下axios的封装和使用。
在src下新建api文件夹,创建request.js文件
import axios from 'axios'
// import store from '@src/store' 暂时不用
// import { HTTP_CODE } from '@lib/const.js' 暂时不用
import Vue from 'vue'
import Router from '@src/router'
// import { LOGIN_OUT } from '@src/store/mutation-types' 暂时不用
// import auth from '@lib/utils/auth.js'
const env = process.env.NODE_ENV
// 根据域名判断是否测试环境;替换vue.manageplatform.com为你的线上域名
function IsTest() {
let hostUrl = window.location.href
return hostUrl.indexOf('vue.manageplatform.com') == -1
}
//请求的接口地址--自定义
const HOST =
env === 'development'
? 'http://localhost:3000'
: IsTest() == true
? 'https://api.mtnhao.com/'
: 'https://api.mtnhao.com/'
const CancelToken = axios.CancelToken
const source = CancelToken.source()
const request = axios.create({
baseURL: HOST,
timeout: 30000
})
request.defaults.headers.post['Content-Type'] = 'application/json'
request.interceptors.request.use(config => {
if (config.cancel) {
config.cancelToken = source.token
source.cancel()
}
config.url = subSplash(config.url)
// token验证 -- 根据自己的实际情况写入,我这里后端需要每次请求默认带上token
// if (store.getters.isLogin == true) {
// const token = store.state.user.user.token
// if (token && config.noToken !== true) {
// if (config.method === 'get') {
// config.params = { ...config.params, token }
// } else if (config.method === 'post') {
// if (config.headers['Content-Type'] == 'multipart/form-data') {
// config.data.append('token', token)
// } else {
// config.data = { ...config.data, token }
// }
// }
// }
// }
// if (config.needSign) {
// if (!config.data) {
// config.params = auth.Auth(config.params)
// } else {
// config.data = auth.Auth(config.data)
// }
// }
return config
})
request.interceptors.response.use(
response => {
// 错误统一拦截--根据自己的实际情况来修改
if (parseInt(response.data.status) === 403) {
// store.commit(LOGIN_OUT) // vuex登出处理
new Vue().$message.error({
message: '登录已过期,请重新登录',
onClose: () => {
Router.push({
name: 'login'
})
}
})
return Promise.reject(response.data.msg)
}
return response
},
error => {
return Promise.reject(error)
}
)
// 删除restapi末尾的反斜杠
function subSplash(url) {
return url.endsWith('/') ? url.slice(0, -1) : url
}
export default request
首先启动我们下好的网易云api,成功后运行在 http://localhost:3000 端口
对应我们views文件夹下的music,在api文件夹下也创建一个music的文件夹,创建search.js文件,写入代码
import request from '../request'
// 搜索歌曲
const SearchSongs = ({ pageSize, start, keywords }) => {
return request
.get('/search', {
params: {
limit: pageSize,
offset: start,
keywords,
},
})
.then((result) => {
return result.data
})
}
export default {
SearchSongs
}
对search.vue简单做个搜索功能
<div class="search-bar">
<div class="search-title">{{ keywords }}的搜索结果:div>
<el-input
class="search-input"
v-model="keywords"
placeholder="请输入"
@change="SearchInfo"
>el-input>
div>
引入api
import searchApi from "../../api/music/search";
methods: {
SearchInfo() {
searchApi
.SearchSongs({
pageSize: 10,
start: 0,
keywords: this.keywords,
})
.then((res) => {
console.log("res", res);
});
},
},
完成之后结果如下:
好啦,基本流程大致如下,后面再更新vuex,组件封装。
这里有一篇很详细的使用介绍可以参考;可以尝试把搜索出来的结果存到store试手。
vue中使用vuex(超详细)
拿menu举例,
把el-menu剪切出来,到component文件夹下新建home文件夹,再新建menu.vue,
创建好vue模板后往template粘贴。
<style lang="less" scoped>
style>
<template>
<el-menu
:default-active="activePath"
class="el-menu-vertical-demo"
router
@open="handleOpen"
@close="handleClose"
:collapse="isCollapse"
>
<el-submenu
:index="`${index + 1}`"
v-for="(item, index) in menuList"
:key="index"
>
<template slot="title">
<i :class="`${item.icon}`">i>
<span>{{ item.title }}span>
template>
<el-menu-item-group>
<el-menu-item
v-for="items in item.submenu"
:key="items.id"
:index="`${items.path}`"
>{{ items.title }}el-menu-item
>
el-menu-item-group>
el-submenu>
el-menu>
template>
<script>
export default {
props: {
menuList: {
type: Array,
default: [],
},
isCollapse: {
type: Boolean,
default: false,
},
activePath: {
type: String,
default: "/home/music/findMusic",
},
},
data() {
return {};
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
},
};
script>
在home.vue文件使用组件
<el-aside :width="`${isCollapse ? '66px' : '202px'}`">
<home-menu :isCollapse="isCollapse" :menuList="menuList" :activePath="activePath">home-menu>
el-aside>
import HomeMenu from "../components/home/menu.vue"
components: {
HomeHeader,
HomeMenu
},