点击Top切换数据

下面开始构建后端数据,并且能点击进行切换
将数据渲染到页面上,这次我们要自己去写接口
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>
相关推荐
程序猿追5 小时前
Vue组件化开发
前端·html
艾德金的溪5 小时前
redis-7.4.6部署安装
前端·数据库·redis·缓存
小光学长6 小时前
基于Vue的2025年哈尔滨亚冬会志愿者管理系统5zqg6m36(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
@PHARAOH6 小时前
WHAT - 受控组件和非受控组件
前端·javascript·react.js
生莫甲鲁浪戴6 小时前
Android Studio新手开发第二十六天
android·前端·android studio
JH30736 小时前
B/S架构、HTTP协议与Web服务器详解
前端·http·架构
yi碗汤园6 小时前
【超详细】C#自定义工具类-StringHelper
开发语言·前端·unity·c#·游戏引擎
麦麦大数据7 小时前
D027 v2 vue+django+neo4j 基于知识图谱红楼梦问答系统 (新增问关系功能;新增知识节点和关系管理功能,neo4j增删改查功能)
vue.js·django·问答系统·知识图谱·neo4j·图谱管理·neo4j增删改查
Kevin Wang7277 小时前
解除chrome中http无法录音问题,权限
前端·chrome