点击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>
相关推荐
weixin_382395239 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__9 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.9 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~10 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华11 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子13 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅13 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni14 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学15 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端