下面开始构建后端数据,并且能点击进行切换
将数据渲染到页面上,这次我们要自己去写接口
server 是作为后端,后面我们的连接数,包括接口全部到server里面去做。
进入server,启动后端服务 npm run start
然后访问 http://localhost:3000 ,看到如下图,表示后端服务启动成功
前端访问的是http://192.168.31.97:8080/,而后端http://192.168.31.97:3000/,我们要去获取后端的数据,这明显跨域了,我们需要去设置代理 vite.config.js
let path = require("path");
module.exports = {
// 代理
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 目标url
changeOrigin: true, // 支持跨域
rewrite: (path) => path.replace(/^\/api/, ""),
// 重写路径,替换/api
}
}
},
configureWebpack: (config) => {
config.resolve = {
extensions: ['.js', '.json', '.vue'],
alias: {
'@': path.resolve(__dirname, './src'),
}
}
}
}
前端要请求后端数据,需要安装 axios ,后面 axios需要进行二次封装
npm install axios -S // 表示安装生产环境
npm install axios -D // 表示安装在开发环境
tea>npm install axios -S
tea>npm list axios
vue-tea@0.1.0 D:\workspace_uniapp\vue-tea
`-- axios@1.12.2
接口请求要放在created() {} 这生命周期函数里面
发送请求,只要后端能接受到数据并返回数据就OK了
created() {
axios({
url: '/api/home'
}).then(res => {
console.log(res);
})
},
接下来去后端server下的routes/index.js文件去配置'/api/home'这样一个接口
// get 请求接口
router.get('/api/home', function(req, res, next) {
res.send({
code: 0,
a: 1
});
});
只要前端能拿到后端的数据就OK了,牢记,后端只要有所改动,哪怕是一个数字,也要重新启动一下。 npm run start 然后刷新一下前端,可以看到我们已经拿到后端数据a:1的数据了。
res.data.a ==> 1
res.data.code ==> 0
通过以上拿到数据表示我们的接口是通的
接下来我们要去构建首页推荐的数据
接下来我们要去后端构建首页推荐的数据
/api/index_list/0/data/1
0 ==> 推荐
1 ==> 第一屏数据
/api/index_list/1/data/1
1 ==> 大红袍
1 ==> 第一屏数据
/api/index_list/2/data/1
2 ==> 铁观音
1 ==> 第一屏数据
前面的0,1,2,3...就是我们点击推荐,大红袍,铁观音,绿茶...时的index数据
接下来我们来构建推荐的第屏数据
前端构建数据 views/Home.vue
created() {
axios({
url: '/api/index_list/0/data/1'
}).then(res => {
console.log(res);
后端构建数据 server/routes/index.js
// get 请求
router.get('/api/index_list/0/data/1', function(req, res, next) {
res.send({ // 构建后端数据
code: 0, // 代表请求成功
data: [
topBar: [{
id: 0,
label: '推荐'
}, {
id: 1,
label: '大红袍'
}, {
id: 2,
label: '铁观音'
}, {
id: 3,
label: '绿茶'
}, {
id: 4,
label: '普洱'
}, {
id: 5,
label: '茶具'
}, {
id: 6,
label: '花茶'
}, {
id: 7,
label: '红茶'
}, {
id: 8,
label: '设计'
}, ]
]
});
});
完整数据 server/routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', {
title: 'Express'
});
});
// 首页推荐的数据 0==> 推荐 1==> 第一塀数据
router.get('/api/index_list/0/data/1', function(req, res, next) {
res.send({
code: 0,
data: {
topBar: [{
id: 0,
label: '推荐'
}, {
id: 1,
label: '大红袍'
}, {
id: 2,
label: '铁观音'
}, {
id: 3,
label: '绿茶'
}, {
id: 4,
label: '普洱'
}, {
id: 5,
label: '茶具'
}, {
id: 6,
label: '花茶'
}, {
id: 7,
label: '红茶'
}, {
id: 8,
label: '设计'
}, ],
// 这是我们的swiper
data: [{ // 这是swiper数据
id: 0,
type: 'swiperList',
data: [{
id: 1,
imgUrl: './images/swiper4.png'
}, {
id: 2,
imgUrl: './images/swiper5.png'
}, {
id: 3,
imgUrl: './images/swiper6.png'
}],
}, { // 这是Icons数据
id: 1,
type: 'iconsList',
data: [{
id: 1,
title: '自饮茶',
imgUrl: './images/icons1.png'
}, {
id: 2,
title: '茶具',
imgUrl: './images/icons2.png'
}, {
id: 3,
title: '茶礼盒',
imgUrl: './images/icons3.png'
}, {
id: 4,
title: '领取福利',
imgUrl: './images/icons4.png'
}, {
id: 5,
title: '官方验证',
imgUrl: './images/icons5.png'
}],
}, { // 爆款推荐
id: 2,
type: 'recommendList',
data: [{
id: 1,
name: '龙井1号铁观音250g',
content: '鲜爽甘醇 口粮首先',
price: '68',
imgUrl: './images/recommend2.png'
}, {
id: 2,
name: '龙井2号铁观音250g',
content: '鲜爽甘醇 口粮首先',
price: '58',
imgUrl: './images/recommend2.png'
}]
}, {
// 猜你喜欢
id: 3,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}]
}
})
});
module.exports = router;
请求到后端数据了
我们请求可能要很多次,比如你第一次进入首页,点击不同版块(比如推荐,大红袍,铁观音,绿茶...),是不是还要发生请求,所以首页请求这一块,一定不能直接写在created(){}里面,他一定要我们封装的,然后根据点击不同版块,进行数据切换,所以我们要把
created() {
this.getData();
}
封装成 getData() 这样一个方法
具体代码如下:刷新页面,依然可以拿到数据,如下图所示
created() {
this.getData();
},
methods: {
getData() {
axios({
url: '/api/index_list/0/data/1'
}).then(res => {
console.log(res);
})
},
changeTab(item, index) {
console.log(index);
}
}
我们请求的数据经过测试是没有问题,如上图所示,可我们最终的目的是要在视图层展示这些数据,我们该如何处理?
created() {
this.getData();
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
console.log(res);
}
}
此时我们仍然可以拿到后端返回的数据,
我们需要res.data.data.topBar才能拿到topBar具体数据, 如下图所示
created() {
this.getData();
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
console.log(res.data.data.topBar);
}
}
我们通过 res.data.data.data 可以拿到具体数据,如下图所示
created() {
this.getData();
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
console.log(res.data.data.data);
}
}
渲染数据 topBar
data() {
return {
selectedId: 0,
// topBar: [],
items: [],
options: {
activeColor: '#b0352f'
}
}
},
created() {
this.getData();
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
// this.items = res.data.data.topBar;
this.items = Object.freeze(res.data.data.topBar); // 优化性能
}
}
渲染所有数据
data() {
return {
selectedId: 0,
// topBar: [],
items: [],
options: {
activeColor: '#b0352f'
}
}
},
created() {
this.getData();
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
this.items = Object.freeze(res.data.data.topBar); // 优化性能
this.newData = Object.freeze(res.data.data.data);
console.log(Object.freeze(res.data.data.data)); // 先打印一下数组
}
}
我们取item的值,他是一个对象
<section ref="wrapper">
<div>
<div v-for="(item,index) in newData" :key="index">
{{item}}
<Swiper v-if="true"></Swiper>
<Icons v-if="true"></Icons>
<Recommend v-if="true"></Recommend>
<Ad v-if="false"></Ad>
<Like v-if="true"></Like>
<Footer v-if="false"></Footer>
</div>
</div>
</section>
如果我们取item.type
<section ref="wrapper">
<div>
<div v-for="(item,index) in newData" :key="index">
{{item.type}}
<Swiper v-if="true"></Swiper>
<Icons v-if="true"></Icons>
<Recommend v-if="true"></Recommend>
<Ad v-if="false"></Ad>
<Like v-if="true"></Like>
<Footer v-if="false"></Footer>
</div>
</div>
</section>
点击'推荐'返回如下数据 console.log(res.data.data);
点击大红袍res.data.data 就可以拿到数据 这是二级
点击推荐 res.data.data.data 才可以拿到数据 这是三级
点击'大红袍' 返回如下数据 console.log(res.data);
构建接口数据
router.get('/api/index_list/1/data/1',function(req, res, next) {
res.send({
data: [{
id: 1,
type: 'adList',
data: [
{
id: 1,
imgUrl: './images/dhp.png'
},
{
id: 2,
imgUrl: './images/dhp.png'
}
]
},{
// 猜你喜欢
id: 2,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}]
})
});
点击切换栏的不同按钮返回如下图示index的值
methods: {
changeTab(item, index) {
console.log(index);
}
}
前端要实现代理,需要安装 axios
npm install axios -S
Vue2项目代理设置方法
在Vue2项目中设置代理主要通过修改`vue.config.js`配置文件实现,下面是具体步骤:
1. **创建配置文件**
在项目根目录下创建`vue.config.js`文件(如果不存在)
2. **配置单个代理**(基本配置)
module.exports = {
devServer: {
port: 8080, // 可选:修改开发服务器端口
proxy: {
'/api': { // 代理标识前缀
target: 'http://your-api-server.com', // 目标服务器地址
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '' // 路径重写(去掉代理前缀)
}
}
}
}
}
**配置多个代理**
proxy: {
'/api1': {
target: 'http://server1.com',
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {
target: 'http://server2.com',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
**关键参数说明**:
- `target`:后端API服务器地址
- `changeOrigin`:修改请求头中的`Host`值(必须设为`true`避免404错误)
- `pathRewrite`:路径重写规则
- `ws`:是否代理WebSocket(可选)
**使用示例**:
前端请求 `/api/user` 会被代理到 `http://your-api-server.com/user\`
1. **注意事项**:
- 配置修改后需重启开发服务 (`npm run serve`)
- 仅适用于开发环境,生产环境需通过Nginx配置代理*1**2*
- 确保后端服务器地址和端口正确,否则会出现404错误*3*
- Vue CLI 3+ 项目必须使用`vue.config.js`文件配置
老师配置的代理
configureWebpack: (config) => {
config.resolve = {
extensions: ['.js', '.json', '.vue'],
alias: {
'@': path.resolve(_dirname, './src'),
}
}
}
点击Top切换数据 实现代码如下:
1, server/routes/index.js (server开头是后端代码)
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', {
title: 'Express'
});
});
// 首页推荐的数据 0==> 推荐 1==> 第一塀数据
router.get('/api/index_list/0/data/1', function(req, res, next) {
res.send({
code: 0,
data: {
topBar: [{
id: 0,
label: '推荐'
}, {
id: 1,
label: '大红袍'
}, {
id: 2,
label: '绿茶'
}, {
id: 3,
label: '铁观音'
}, {
id: 4,
label: '普洱'
}, {
id: 5,
label: '茶具'
}, {
id: 6,
label: '花茶'
}, {
id: 7,
label: '设计'
}, ],
// 这是我们的swiper
data: [{ // 这是swiper数据
id: 0,
type: 'swiperList',
data: [{
id: 0,
imgUrl: './images/swiper4.png'
}, {
id: 1,
imgUrl: './images/swiper5.png'
}, {
id: 2,
imgUrl: './images/swiper6.png'
}],
}, { // 这是Icons数据
id: 1,
type: 'iconsList',
data: [{
id: 1,
title: '自饮茶',
imgUrl: './images/icons1.png'
}, {
id: 2,
title: '茶具',
imgUrl: './images/icons2.png'
}, {
id: 3,
title: '茶礼盒',
imgUrl: './images/icons3.png'
}, {
id: 4,
title: '领取福利',
imgUrl: './images/icons4.png'
}, {
id: 5,
title: '官方验证',
imgUrl: './images/icons5.png'
}],
}, { // 爆款推荐
id: 2,
type: 'recommendList',
data: [{
id: 1,
name: '龙井1号铁观音250g',
content: '鲜爽甘醇 口粮首先',
price: '68',
imgUrl: './images/recommend2.png'
}, {
id: 2,
name: '龙井2号铁观音250g',
content: '鲜爽甘醇 口粮首先',
price: '58',
imgUrl: './images/recommend2.png'
}]
}, {
// 猜你喜欢
id: 3,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}]
}
})
});
// 首页大红袍的数据 1==> 大红袍 1==> 第一塀数据
router.get('/api/index_list/1/data/1', function(req, res, next) {
res.send({
code: 0,
data: [{
id;1,
type: 'adList',
data: [{
id: 1,
imgUrl: './images/dhp.png'
}, {
id: 2,
imgUrl: './images/dhp.png'
}]
}, {
// 猜你喜欢
id: 2,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}]
})
})
// 首页绿茶的数据 2==> 绿茶 1==> 第一塀数据
router.get('/api/index_list/2/data/1', function(req, res, next) {
res.send({
code: 0,
data: [{
id;1,
type: 'adList',
data: [{
id: 1,
imgUrl: './images/dhp.png'
}, {
id: 2,
imgUrl: './images/dhp.png'
}]
}, {
// 猜你喜欢
id: 2,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}]
})
})
// 首页铁观音的数据 3==> 铁观音 1==> 第一塀数据
router.get('/api/index_list/3/data/1', function(req, res, next) {
res.send({
code: 0,
data: [{
id;1,
type: 'adList',
data: [{
id: 1,
imgUrl: './images/dhp.png'
}, {
id: 2,
imgUrl: './images/dhp.png'
}]
}, {
// 猜你喜欢
id: 2,
type: 'likeList',
data: [{
id: 1,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 2,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}, {
id: 3,
imgUrl: './images/like8.png',
name: '建盏茶具套装 红色芝麻毫 12件套',
price: 299,
}]
}, { // 这是Icons数据
id: 3,
type: 'iconsList',
data: [{
id: 1,
title: '自饮茶',
imgUrl: './images/icons1.png'
}, {
id: 2,
title: '茶具',
imgUrl: './images/icons2.png'
}, {
id: 3,
title: '茶礼盒',
imgUrl: './images/icons3.png'
}, {
id: 4,
title: '领取福利',
imgUrl: './images/icons4.png'
}, {
id: 5,
title: '官方验证',
imgUrl: './images/icons5.png'
}]
})
})
// 首页普洱的数据 4==> 普洱 1==> 第一塀数据
router.get('/api/index_list/4/data/1', function(req, res, next) {})
// 首页茶具的数据 5==> 茶具 1==> 第一塀数据
router.get('/api/index_list/5/data/1', function(req, res, next) {})
// 首页花茶的数据 6==> 话茶 1==> 第一塀数据
router.get('/api/index_list/6/data/1', function(req, res, next) {})
// 首页设计的数据 7==> 设计 1==> 第一塀数据
router.get('/api/index_list/7/data/1', function(req, res, next) {})
// 测试
// router.get('/api/home', function(req, res, next) {
// res.send({
// code: 0,
// a: 1
// })
// });
module.exports = router;
2, src/assets/css/common.css
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
3, src/assets/css/iconfont.css
@font-face {
font-family: "iconfont";
/* Project id 5021940 */
src: url('../fonts/iconfont.woff2?t=1757920470232') format('woff2'),
url('../fonts/iconfont.woff?t=1757920470232') format('woff'),
url('../fonts/iconfont.ttf?t=1757920470232') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-kefu:before {
content: "\ec2e";
}
.icon-fangdajing:before {
content: "\e848";
}
4, src/assets/js/flexible.js
(function flexible(window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize() {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
} else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit() {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function(e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
// module.exports = {
// plugins: {
// 'postcss-px-to-viewport': {
// viewportWidth: 750, // 设计稿宽度
// unitPrecision: 5,
// viewportUnit: 'vw',
// selectorBlackList: [],
// minPixelValue: 1
// }
// }
// }
5, src/components/common/Tabbar.vue
<template>
<!-- 获取数据并将数据渲染到视图层 -->
<div class="tabbar">
<ul>
<li v-for="(item,index) in routerList" :key="index" @click="switchTab(item.path)">
<img :src="$route.path.includes(item.path) ? item.selected : item.active" alt="" />
<span :class="$route.path.includes(item.path) ? 'active' : ''">{{item.title}}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
// 构建数据渲染到视图层
data() {
return {
routerList: [{
title: "首页",
path: "/home",
active: "./images/home.png",
selected: "./images/home-select.png",
}, {
title: "分类",
path: "/list",
active: "./images/list.png",
selected: "./images/list-select.png",
}, {
title: "购物车",
path: "/cart",
active: "./images/cart.png",
selected: "./images/cart-select.png",
}, {
title: "我的",
path: "/my",
active: "./images/my.png",
selected: "./images/my-select.png",
}]
}
},
methods: {
switchTab(path) {
// 判断是否点击的是同一个路由 判断点击时路径等于当前路径,直接返回就OK
if (this.$route.path == path) return;
// this.$router.push(path);
// 对应跳转页面
this.$router.replace(path);
}
}
}
</script>
<style scoped>
.tabbar {
/* 定位在页面最下方 */
/* position: fixed;
left: 0;
bottom: 0;
z-index: 999; */
width: 100%;
height: 50px;
background-color: #fff;
}
.tabbar ul {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100%;
}
.tabbar ul li {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.tabbar ul li img {
width: 31px;
height: 31px;
}
.tabbar ul li span {
text-align: center;
font-size: 16px;
}
.active {
color: red;
}
</style>
6, src/components/home/Ad.vue
<template>
<div class="ad">
<ul>
<li v-for="(item,index) in adList" :key="index">
<img :src="intem.imgUrl" alt="" />
</li>
</ul>
</div>
</template>
<script>
export default { // 子组件(父组件传值给子组件)
// 子组件接收父组件传递过来的数据
props: {
adList: Array
},
}
</script>
<style scoped>
.ad {
width: 100%;
height: 112px;
}
.ad {
width: 100%;
height: 100%;
}
</style>
7, src/components/home/Card.vue
<template>
<div class="title">
<!-- 插槽 -->
<slot name="slotRecommend"></slot>
<slot name="slotLike"></slot>
</div>
</template>
<script>
</script>
<style scoped>
.title {
padding: 10px 0;
width: 100%;
text-align: center;
font-size: 16px;
}
.title span {
position: relative;
}
.title span::after {
content: "";
display: block;
position: absolute;
top: 50%;
right: -0.5rem;
margin-top: -4px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #d4c0a7;
}
.title span::before {
content: "";
display: block;
position: absolute;
top: 50%;
left: -0.5rem;
margin-top: -4px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #d4c0a7;
}
</style>
8, src/components/home/Footer.vue
<template>
<div id="m-footer" class="m-footer" style="">
<div class="m-footertxt">到底啦,你还可以看看这里</div>
<div class="m-footerbtn">
<span>
<a href="/categeryList?comefrom=index">更多茶叶</a>
</span>
<span>
<a href="/zhuanti/teaset?comefrom=index">更多茶具</a>
</span>
</div>
<div class="m-footertxt" style="margin-top: 20px;">
Copyright 2016 茶七网 TEA7.com
</div>
<div class="m-footertxt">
服务时间: 8:30-21:00 客服热线: 400-8993-513
</div>
<div class="m-footertxt">
备案号
<a href="http://beian.miit.gov.cn?comefrom=index">闽ICP备14011677号-2</a>
</div>
</div>
</template>
<script>
</script>
<style>
</style>
9, src/components/home/Header.vue
<template>
<Header>
<h1>
<img src="@/assets/images/logo.png" alt="" />
</h1>
<div class="search">
<i class="iconfont icon-fangdajing"></i>
<span>搜你喜欢的...</span>
</div>
<div class="kefu">
<i class="iconfont icon-kefu"></i>
</div>
</Header>
</template>
<script>
</script>
<style scoped>
header {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 60px;
background-color: #b0352f;
}
header h1 {
padding-right: 10px;
width: 120px;
height: 52px;
}
header h1 img {
padding-right: 10px;
width: 100%;
height: 100%;
}
.search {
display: flex;
align-items: center;
width: 246px;
height: 30px;
background-color: #FFFFFF;
border-radius: 12px;
}
.search i {
padding: 0 16px;
color: #ccc;
}
.search span {
font-size: 14px;
color: gray;
}
.kefu i {
font-size: 30px;
color: #fff;
}
</style>
10, src/components/home/Icons.vue
<template>
<ul class="icons">
<li v-for="(item,index) in iconsList" :key="index">
<img :src="item.imgUrl" alt="" />
<span>{{item.title}}</span>
</li>
</ul>
</template>
<script>
export default { // 子组件(父组件传值给子组件)
// 子组件接收父组件传递过来的数据
props: {
iconsList: Array
}
// data() { // 构建数据
// return {
// iconsList: [{
// id: 1,
// title: '自饮茶',
// imgUrl: './images/icons1.png'
// }, {
// id: 2,
// title: '茶具',
// imgUrl: './images/icons2.png'
// }, {
// id: 3,
// title: '茶礼盒',
// imgUrl: './images/icons3.png'
// }, {
// id: 4,
// title: '领取福利',
// imgUrl: './images/icons4.png'
// }, {
// id: 5,
// title: '官方验证',
// imgUrl: './images/icons5.png'
// }]
// }
// }
}
</script>
<style scoped>
.icons {
display: flex;
justify-content: space-around;
padding: 10px 0;
}
.icons li {
display: flex;
flex-direction: column;
align-items: center;
}
.icons img {
width: 38px;
height: 38px;
}
.icons span {
padding: 6px 0;
font-size: 16px;
}
</style>
11, src/components/home/Like.vue
<template>
<div class="like">
<Card v-slot:slotLike>
<span>猜你喜欢</span>
</Card>
<ul>
<li>
<h2 v-for="(item,index) in likeList" :key="index">
<img :src="item.imgUrl" alt="" />
</h2>
<h3>{{item.name}}</h3>
<div>
<span>¥</span>
<b>{{item.price}}</b>
</div>
</li>
</ul>
</div>
</template>
<script>
import Card from '@/components/home/Card.vue'
export default {
name: Like,
// 子组件接收父组件传递过来的数据
props: {
likeList: Array
},
// data() {
// return {
// likeList: [{
// id: 1,
// imgUrl: './images/like8.png',
// name: '建盏茶具套装 红色芝麻毫 12件套',
// price: 299,
// }, {
// id: 2,
// imgUrl: './images/like8.png',
// name: '建盏茶具套装 红色芝麻毫 12件套',
// price: 299,
// }, {
// id: 3,
// imgUrl: './images/like8.png',
// name: '建盏茶具套装 红色芝麻毫 12件套',
// price: 299,
// }]
// }
// },
components: {
Card
}
}
</script>
<style scoped>
.like ul {
display: flex;
flex-wrap: wrap;
}
.like ul li {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 50%;
text-align: center;
}
.like ul li>div {
padding: 6px;
width: 93%;
text-align: left;
color: #FF0000;
}
.like ul li>div span {
font-size: 12px;
}
.like ul li>div b {
font-size: 16px;
font-weight: 600;
}
.like h3 {
padding: 0 6px;
width: 93%;
font-size: 14px;
font-weight: 400;
color: #222;
/* 文字超出的部分隐藏 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.like img {
width: 176px;
height: 176px;
}
</style>
12, src/components/home/Recommend.vue
<template>
<div class="recommend">
<!-- 插槽 -->
<Card v-slot:slotRecommend>
<span>爆款推荐</span>
</Card>
<ul>
<li v-for="(item,index) in recommendList" :key="index">
<h2>
<img :src="item.imgUrl" alt="" />
<div>
<h3>{{item.name}}</h3>
<p>{{item.content}}</p>
<div class="price">
<span>¥</span>
<b>{{item.price}}</b>
</div>
</div>
</h2>
</li>
</ul>
</div>
</template>
<script>
import Card from '@/components/home/Card.vue'
export default {
// 子组件接收父组件(Home)传递过来的数据
props: {
recommendList: Array
},
// data() {
// return {
// recommendList: [{
// id: 1,
// name: '龙井1号铁观音250g',
// content: '鲜爽甘醇 口粮首先',
// price: '68',
// imgUrl: './images/recommend2.png'
// }, {
// id: 2,
// name: '龙井2号铁观音250g',
// content: '鲜爽甘醇 口粮首先',
// price: '58',
// imgUrl: './images/recommend2.png'
// }]
// }
// },
components: {
Card
}
}
</script>
<style scoped>
.recommend ul li {
position: relative;
}
.recommend ul li h2 {
display: flex;
flex-direction: row;
text-align: center;
}
.recommend ul li img {
width: 360px;
height: 144px;
border-radius: 12px;
}
.recommend ul li < div {
position: absolute;
right: 0;
top: 0;
}
.recommend ul li < div {
/* 将div里面的内容定位到最右边 */
position: absolute;
right: 0;
top: 0;
display: flex;
flex-direction: column;
padding: 20px;
}
.recommend ul li < div h3 {
font-size: 12px;
}
.recommend ul li < div p {
font-size: 16px;
}
.price {
margin-top: 25px;
text-align: right;
color: red;
}
.price span {
font-size: 14px;
}
</style>
13, src/components/home/Swiper.vue
<template>
<div class="swiper-main">
<swiper :options="swiperOption">
<swiper-slide v-for='(item,index) in swiperList' :key='index'>
<img :src="item.imgUrl" alt="" />
</swiper-slide>
<!-- 分页器 -->
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
<!--以下看需要添加-->
<!-- <div class="swiper-scrollbar"></div> -->
<!-- <div class="swiper-button-next"></div> -->
<!-- <div class="swiper-button-prev"></div> -->
</div>
</template>
<script>
import 'swiper/dist/css/swiper.css'
import {
swiper,
swiperSlide
} from 'vue-awesome-swiper'
export default {
name: 'Swiper',
// 子组件接收父组件传递过来的数据
props: {
swiperList: Array
},
data() {
return {
// swiperList: [{
// id: 1,
// imgUrl: './images/swiper4.png'
// },
// {
// id: 2,
// imgUrl: './images/swiper5.png'
// },
// {
// id: 3,
// imgUrl: './images/swiper6.png'
// }
// ],
swiperOption: { // 展示小圆点
autoplay: {
delay: 3000
},
loop: true,
speed: 1000,
pagination: {
el: '.swiper-pagination'
}
}
}
},
components: {
swiper,
swiperSlide
}
// async created() {
// // 动态获取数据(示例)
// this.swiperList = await fetch('/api/swiperList').then(res => res.json())
// }
}
</script>
<style scoped>
/* 必须引入的样式 */
@import 'swiper/dist/css/swiper.css';
.swiper-main {
position: relative;
width: 100%;
height: 165px;
/* margin-top: 120px; */
}
.swiper-main img {
width: 100%;
height: 165px;
}
.swiper-container {
width: 100%;
height: 165px;
}
.swiper-pagination {
width: 100%;
/* background-color: red; */
bottom: 0px;
}
::v-deep .swiper-pagination-bullet-active {
background-color: #b0352f;
}
::v-deep .swiper-pagination-bullet {
margin: 0 10px;
}
</style>
14, src/router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [{
path: "/home",
name: "Home",
component: Home
},
{
path: "/",
redirect: "/home" // 重定向
}, {
path: "/list",
name: "List",
component: () =>
import("../views/List.vue"),
}, {
path: "/cart",
name: "Cart",
component: () =>
import("../views/Cart.vue"),
}, {
path: "/my",
name: "My",
component: () =>
import("../views/My.vue"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
export default router;
15, src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {},
});
16, src/views/Cart.vue
<template>
<div class="cart">
这是购物车
<Tabbar></Tabbar>
</div>
</template>
<script>
import Tabbar from '@/components/common/Tabbar.vue'
export default {
name: "Cart",
components: { // 挂载
Tabbar
}
};
</script>
<style>
</style>
<template>
<div class="home">
<div class="headers">
<div class="headers-main">
<Header></Header>
<ly-tab v-model="selectId" :items="items" :options="options" @change="changeTab"></ly-tab>
</div>
</div>
<!-- <section>
<Swiper v-if="ture"></Swiper>
<Icons v-if="true"></Icons>
<Recommend v-if="ture"></Recommend>
<Ad v-if="false"></Ad>
<Like v-if="true"></Like>
</section> -->
<!-- 滚动区域 -->
<section ref="wrapper">
<div>
<div v-for="(item,index) in newData" :key="index">
<!-- {{item.type}} -->
<!-- {{item.data}} -->
<!-- 父组件传值给子组件 -->
<Swiper v-if="item.type == 'swiperList'" :swiperList="item.data"></Swiper>
<Icons v-if="item.type == 'iconsList'" :iconsList="item.data"></Icons>
<Recommend v-if="item.type == 'recommendList'" :recommendList="item.data"></Recommend>
<Ad v-if="item.type == 'adList'" :adList="item.data"></Ad>
<Like v-if="item.type == 'likeList'" :likeList="item.data"></Like>
</div>
</div>
</section>
<Footer></Footer>
<Tabbar></Tabbar>
</div>
</template>
<script>
// import 'ly-tab/lib/ly-tab.css'; // 引入默认样式
import Header from '@/components/home/Header.vue'
import Swiper from '@/components/home/Swiper.vue'
import Icons from '@/components/home/Icons.vue'
import Recommend from '@/components/home/Recommend.vue'
import Like from '@/components/home/Like.vue'
import Ad from '@/components/home/Ad.vue'
import Footer from '@/components/home/Footer.vue'
import Tabbar from '@/components/common/Tabbar.vue' // 引入
// 引入插件
import BetterScroll from 'better-scroll'
import axios from 'axios'
export default {
name: "Home",
data() {
return {
selectId: 0,
items: [],
newData: [],
// topBar: [],
// items: [{
// label: '推荐'
// },
// {
// label: '大红袍'
// },
// {
// label: '绿茶'
// },
// {
// label: '铁观音'
// },
// {
// label: '普洱'
// },
// {
// label: '茶具'
// },
// {
// label: '花茶'
// },
// {
// label: '设计'
// }
// ],
options: {
activeColor: "#b0352f", // 激活状态的颜色
// fixBottom: false, // 是否固定在底部
// reBoundExponent: 100, // 回弹指数
// reBoundingDuration: 360 // 回弹持续时间
}
}
},
components: { // 挂载
Header,
Swiper,
Icons,
Recommend,
Like,
Ad,
Footer,
Tabbar
},
// 接口请求放到create(){}里面
create() {
this.getData();
// axios({
// url: '/api/home'
// }).then(res => {
// console.log(res);
// })
},
mounted() {
// console.log(this.$refs.wrapper) // 获取DOM节点
let bs = new BetterScroll(this.$refs.wrapper, {
movable: true,
zoom: true
})
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
this.items = Object.freeze(res.data.data.topBar);
this.newData = Object.freeze(res.data.data.data);
// console.log(Object.freeze(res.data.data.data)); // []
// this.topBar = res.data.data.topBar;
// console.log(res.data.data.topBar);
},
// 负责点击请求
async addData(index) {
let res = await axios({
url: '/api/index_list/' + index + '/data/1'
});
console.log(res);
},
changeTab(item, index) {
this.addData(index)
// console.log(index);
}
}
};
</script>
<style scoped>
.home {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
}
section {
flex: 1;
margin-top: 120px;
overflow: hidden;
}
.headers {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 108px;
}
.headers-main {
/* position: fixed;
top: 0;
left: 0; */
}
::v-deep.ly-tabaar {
box-shadow: none;
border-bottom: none;
}
</style>17, src/views/Home.vue
18, src/views/List.vue
<template>
<div class="list">
这是分类
<Tabbar></Tabbar>
</div>
</template>
<script>
import Tabbar from '@/components/common/Tabbar.vue'
export default {
name: "List",
components: { // 挂载
Tabbar
}
};
</script>
<style>
</style>
19, src/views/My.vue
<template>
<div class="my">
这是我的
<Tabbar></Tabbar>
</div>
</template>
<script>
import Tabbar from '@/components/common/Tabbar.vue'
export default {
name: "My",
components: { // 挂载
Tabbar
}
};
</script>
<style>
</style>
20, App.vue
21, main.js
22, package.json
23, vue.config.js
```
#### views/Home.vue
```
<template>
<div class="home">
<div class="headers">
<div class="headers-main">
<Header></Header>
<ly-tab v-model="selectId" :items="items" :options="options" @change="changeTab"></ly-tab>
</div>
</div>
<!-- 滚动区域 -->
<section ref="wrapper">
<div>
<div v-for="(item,index) in newData" :key="index">
<!-- {{item.type}} -->
<!-- {{item.data}} -->
<!-- 父组件传值给子组件 -->
<Swiper v-if="item.type == 'swiperList'" :swiperList="item.data"></Swiper>
<Icons v-if="item.type == 'iconsList'" :iconsList="item.data"></Icons>
<Recommend v-if="item.type == 'recommendList'" :recommendList="item.data"></Recommend>
<Ad v-if="item.type == 'adList'" :adList="item.data"></Ad>
<Like v-if="item.type == 'likeList'" :likeList="item.data"></Like>
</div>
</div>
</section>
<Footer></Footer>
<Tabbar></Tabbar>
</div>
</template>
<script>
// import 'ly-tab/lib/ly-tab.css'; // 引入默认样式
import Header from '@/components/home/Header.vue'
import Swiper from '@/components/home/Swiper.vue'
import Icons from '@/components/home/Icons.vue'
import Recommend from '@/components/home/Recommend.vue'
import Like from '@/components/home/Like.vue'
import Ad from '@/components/home/Ad.vue'
import Footer from '@/components/home/Footer.vue'
import Tabbar from '@/components/common/Tabbar.vue' // 引入
// 引入插件
import BetterScroll from 'better-scroll'
import axios from 'axios'
export default {
name: "Home",
data() {
return {
selectId: 0,
// items: [],
// newData: [],
// topBar: [],
items: [{
label: '推荐'
},
{
label: '大红袍'
},
{
label: '绿茶'
},
{
label: '铁观音'
},
{
label: '普洱'
},
{
label: '茶具'
},
{
label: '花茶'
},
{
label: '设计'
}
],
options: {
activeColor: "#b0352f", // 激活状态的颜色
// fixBottom: false, // 是否固定在底部
// reBoundExponent: 100, // 回弹指数
// reBoundingDuration: 360 // 回弹持续时间
}
}
},
components: { // 挂载
Header,
Swiper,
Icons,
Recommend,
Like,
Ad,
Footer,
Tabbar
},
// 接口请求放到create(){}里面
create() {
this.getData();
// axios({
// url: '/api/home'
// }).then(res => {
// console.log(res);
// })
},
mounted() {
// console.log(this.$refs.wrapper) // 获取DOM节点
let bs = new BetterScroll(this.$refs.wrapper, {
movable: true,
zoom: true
})
},
methods: {
async getData() {
let res = await axios({
url: '/api/index_list/0/data/1'
});
this.items = Object.freeze(res.data.data.topBar);
this.newData = Object.freeze(res.data.data.data);
// console.log(Object.freeze(res.data.data.data)); // []
// this.topBar = res.data.data.topBar;
// console.log(res.data.data.topBar);
},
// 负责点击请求
async addData(index) {
let res = await axios({
url: '/api/index_list/' + index + '/data/1'
});
console.log(res);
},
changeTab(item, index) {
this.addData(index)
// console.log(index);
}
}
};
</script>
<style scoped>
.home {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
}
section {
flex: 1;
margin-top: 120px;
overflow: hidden;
}
.headers {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 108px;
}
.headers-main {
/* position: fixed;
top: 0;
left: 0; */
}
::v-deep.ly-tabaar {
box-shadow: none;
border-bottom: none;
}
</style>