19 changed files with 2178 additions and 94 deletions
@ -1,32 +1,99 @@ |
|||
<template> |
|||
<div id="app"> |
|||
<nav> |
|||
<router-link to="/">Home</router-link> | |
|||
<router-link to="/about">About</router-link> |
|||
</nav> |
|||
<router-view/> |
|||
<!-- 加载中状态 --> |
|||
<!-- <el-loading |
|||
v-if="loading" |
|||
fullscreen |
|||
text="加载中..." |
|||
background="rgba(255, 255, 255, 0.7)" |
|||
></el-loading> --> |
|||
|
|||
<!-- 顶部导航 --> |
|||
<HeaderNav v-if="$route.name !== 'Login' && $route.name !== 'Register'" /> |
|||
|
|||
<!-- 主内容区 --> |
|||
<main class="main-container"> |
|||
<router-view /> |
|||
</main> |
|||
|
|||
<!-- 页脚 --> |
|||
<Footer v-if="$route.name !== 'Login' && $route.name !== 'Register'" /> |
|||
|
|||
<!-- 回到顶部按钮 --> |
|||
<BackToTop /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HeaderNav from "./components/layout/HeaderNav"; |
|||
import Footer from './components/layout/Footer' |
|||
import BackToTop from "./components/common/BackToTop"; |
|||
import { mapGetters } from "vuex"; |
|||
|
|||
export default { |
|||
name: "App", |
|||
components: { |
|||
HeaderNav, |
|||
Footer, |
|||
BackToTop, |
|||
}, |
|||
computed: { |
|||
...mapGetters(["getLoadingStatus"]), |
|||
loading() { |
|||
return this.getLoadingStatus; |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
// 全局样式 |
|||
#app { |
|||
font-family: Avenir, Helvetica, Arial, sans-serif; |
|||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", |
|||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
text-align: center; |
|||
color: #2c3e50; |
|||
color: #333; |
|||
min-height: 100vh; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
nav { |
|||
padding: 30px; |
|||
.main-container { |
|||
flex: 1; |
|||
width: 100%; |
|||
max-width: 1200px; |
|||
margin: 0 auto; |
|||
padding: 20px; |
|||
box-sizing: border-box; |
|||
|
|||
a { |
|||
font-weight: bold; |
|||
color: #2c3e50; |
|||
// 响应式调整 |
|||
@media (max-width: 1200px) { |
|||
padding: 15px; |
|||
} |
|||
|
|||
&.router-link-exact-active { |
|||
color: #42b983; |
|||
} |
|||
@media (max-width: 768px) { |
|||
padding: 10px; |
|||
} |
|||
} |
|||
|
|||
// 全局样式重置 |
|||
* { |
|||
margin: 0; |
|||
padding: 0; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
a { |
|||
text-decoration: none; |
|||
color: inherit; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
} |
|||
|
|||
img { |
|||
vertical-align: middle; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,49 @@ |
|||
body, html { |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
|
|||
div { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/*单行隐藏*/ |
|||
.text-overflow { |
|||
overflow-x: hidden; |
|||
overflow-y: inherit; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
/*两行隐藏,其他行设置-webkit-line-clamp:n */ |
|||
.text-overflowRows { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
-webkit-line-clamp: 2; |
|||
word-break: break-all; |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
|
|||
.flex-between { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.flex-center { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.flex-around { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
align-items: center; |
|||
} |
|||
|
|||
.flex-column { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<div |
|||
class="back-to-top" |
|||
v-if="showBackToTop" |
|||
@click="scrollToTop" |
|||
> |
|||
<el-icon name="el-icon-arrow-up"></el-icon> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'BackToTop', |
|||
data() { |
|||
return { |
|||
showBackToTop: false |
|||
} |
|||
}, |
|||
mounted() { |
|||
window.addEventListener('scroll', this.handleScroll) |
|||
}, |
|||
beforeDestroy() { |
|||
window.removeEventListener('scroll', this.handleScroll) |
|||
}, |
|||
methods: { |
|||
handleScroll() { |
|||
// 当滚动超过500px时显示回到顶部按钮 |
|||
this.showBackToTop = window.pageYOffset > 500 |
|||
}, |
|||
scrollToTop() { |
|||
// 平滑滚动到顶部 |
|||
const scrollToTop = window.setInterval(() => { |
|||
const position = window.pageYOffset |
|||
if (position > 0) { |
|||
window.scrollTo(0, position - Math.max(20, position / 10)) |
|||
} else { |
|||
window.clearInterval(scrollToTop) |
|||
} |
|||
}, 16) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.back-to-top { |
|||
position: fixed; |
|||
bottom: 30px; |
|||
right: 30px; |
|||
width: 40px; |
|||
height: 40px; |
|||
background-color: #409eff; |
|||
color: white; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
|||
transition: all 0.3s ease; |
|||
z-index: 1000; |
|||
|
|||
&:hover { |
|||
background-color: #337ab7; |
|||
transform: translateY(-3px); |
|||
} |
|||
|
|||
.el-icon-arrow-up { |
|||
font-size: 20px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,149 @@ |
|||
<template> |
|||
<div class="footer-container"> |
|||
<div class="footer-top"> |
|||
<div class="footer-column"> |
|||
<h3>关于我们</h3> |
|||
<ul> |
|||
<li>平台简介</li> |
|||
<li>政策文件</li> |
|||
<li>平台标识指南</li> |
|||
<li>营业执照</li> |
|||
<li>食品经营许可证</li> |
|||
</ul> |
|||
</div> |
|||
<div class="footer-column"> |
|||
<h3>新手上路</h3> |
|||
<ul> |
|||
<li>供应商入驻流程</li> |
|||
<li>供应商操作手册</li> |
|||
<li>采购人入驻流程</li> |
|||
<li>采购人操作手册</li> |
|||
<li>供应商入驻联系方式</li> |
|||
</ul> |
|||
</div> |
|||
<div class="footer-column"> |
|||
<h3>交易流程</h3> |
|||
<ul> |
|||
<li>直购交易流程</li> |
|||
<li>竞购交易流程</li> |
|||
<li>货款结算流程</li> |
|||
</ul> |
|||
</div> |
|||
<div class="footer-column"> |
|||
<h3>常见问题</h3> |
|||
<ul> |
|||
<li>预留份额填报</li> |
|||
<li>支付结算问题</li> |
|||
<li>账号管理问题</li> |
|||
<li>在线客服问题</li> |
|||
</ul> |
|||
</div> |
|||
<div class="contact-info"> |
|||
<p>联系方式:</p> |
|||
<p>客服电话:222-222-222</p> |
|||
<p>工作时间:工作日 9:00-18:00</p> |
|||
<p>客服邮箱:xxxxxxxxx@stn.com</p> |
|||
<p>商务合作:18999999999</p> |
|||
</div> |
|||
<div class="qrcode-group"> |
|||
<div class="qrcode-item"> |
|||
<!-- 这里用 Element UI 布局占位,实际替换成真实二维码图片 --> |
|||
<el-empty |
|||
description="二维码" |
|||
style="width: 100px; height: 100px" |
|||
></el-empty> |
|||
<p>时味苏州小程序</p> |
|||
</div> |
|||
<div class="qrcode-item"> |
|||
<el-empty |
|||
description="二维码" |
|||
style="width: 100px; height: 100px" |
|||
></el-empty> |
|||
<p>时味苏州服务号</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="footer-bottom"> |
|||
<p>版权所有 苏州市特色农产品发展有限公司 | 苏ICP备2023023300号-1</p> |
|||
<p>本网站由 江苏大运远见文化科技发展有限公司 运营维护</p> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "Footer", |
|||
data() { |
|||
return { |
|||
// 可根据实际补充数据,比如动态加载链接、二维码地址等 |
|||
}; |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.footer-container { |
|||
background-color: #fff; |
|||
color: #666; |
|||
font-size: 12px; |
|||
border-top: 1px solid #eaeaea; |
|||
} |
|||
.footer-top { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
padding: 20px 0; |
|||
flex-wrap: wrap; |
|||
} |
|||
.footer-link { |
|||
display: flex; |
|||
gap: 20px; |
|||
align-items: center; |
|||
} |
|||
.footer-link span { |
|||
cursor: pointer; |
|||
transition: color 0.3s ease; |
|||
} |
|||
.footer-link span:hover { |
|||
color: #1890ff; |
|||
} |
|||
.contact-info p { |
|||
margin: 5px 0; |
|||
} |
|||
.footer-column { |
|||
margin-bottom: 20px; |
|||
} |
|||
.footer-column h3 { |
|||
font-size: 14px; |
|||
font-weight: bold; |
|||
margin-bottom: 10px; |
|||
color: #333; |
|||
} |
|||
.footer-column ul { |
|||
list-style: none; |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
.footer-column ul li { |
|||
margin: 5px 0; |
|||
cursor: pointer; |
|||
transition: color 0.3s ease; |
|||
} |
|||
.footer-column ul li:hover { |
|||
color: #1890ff; |
|||
} |
|||
.qrcode-group { |
|||
display: flex; |
|||
gap: 40px; |
|||
} |
|||
.qrcode-item { |
|||
text-align: center; |
|||
} |
|||
.footer-bottom { |
|||
text-align: center; |
|||
padding: 10px 0; |
|||
border-top: 1px solid #eaeaea; |
|||
} |
|||
.footer-bottom p { |
|||
margin: 5px 0; |
|||
} |
|||
</style> |
@ -0,0 +1,372 @@ |
|||
<template> |
|||
<header class="header-nav"> |
|||
<!-- 顶部通知栏 --> |
|||
<div class="top-notice"> |
|||
<div class="container"> |
|||
<p> |
|||
欢迎来到企业采购平台! |
|||
<a href="/register" class="highlight">立即注册</a> |
|||
</p> |
|||
<div class="top-links"> |
|||
<a href="/user" v-if="isLogin"> |
|||
<img v-lazy="userInfo.avatar" alt="用户头像" class="avatar" /> |
|||
{{ userInfo.username }} |
|||
</a> |
|||
<a href="/login" v-else>登录</a> |
|||
<span class="separator" v-if="isLogin">|</span> |
|||
<a href="/register" v-if="isLogin">注册</a> |
|||
<a href="/user">采购人中心</a> |
|||
<a href="/userCenter" v-if="isLogin">我的订单</a> |
|||
<a href="">商户后台</a> |
|||
<a href="javascript:;" @click="handleLogout" v-if="isLogin">退出</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 主导航栏 --> |
|||
<div class="main-nav"> |
|||
<div class="container"> |
|||
<div class="logo"> |
|||
<a href="/"> |
|||
<h1>精品商城</h1> |
|||
</a> |
|||
</div> |
|||
|
|||
<!-- 搜索框 --> |
|||
<div class="search-box"> |
|||
<el-input |
|||
placeholder="请输入搜索内容" |
|||
v-model="searchText" |
|||
class="search-input" |
|||
@keyup.enter.native="handleSearch" |
|||
> |
|||
<el-button |
|||
slot="append" |
|||
icon="el-icon-search" |
|||
@click="handleSearch" |
|||
></el-button> |
|||
</el-input> |
|||
<div class="hot-tags"> |
|||
<span>热门搜索:</span> |
|||
<a |
|||
href="javascript:;" |
|||
@click=" |
|||
searchText = '手机'; |
|||
handleSearch(); |
|||
" |
|||
>手机</a |
|||
> |
|||
<a |
|||
href="javascript:;" |
|||
@click=" |
|||
searchText = '电脑'; |
|||
handleSearch(); |
|||
" |
|||
>电脑</a |
|||
> |
|||
<a |
|||
href="javascript:;" |
|||
@click=" |
|||
searchText = '服装'; |
|||
handleSearch(); |
|||
" |
|||
>服装</a |
|||
> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 购物车入口 --> |
|||
<div class="cart-entry"> |
|||
<a href="/cart" class="cart-link"> |
|||
<i class="el-icon-shopping-cart-full cart-icon"></i> |
|||
<span>购物车</span> |
|||
<span class="cart-count" v-if="cartTotalCount > 0">{{ |
|||
cartTotalCount |
|||
}}</span> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 分类导航 --> |
|||
<div class="category-nav" v-if="false"> |
|||
<div class="container"> |
|||
<ul class="nav-list"> |
|||
<li class="nav-item"> |
|||
<a |
|||
href="/" |
|||
class="nav-link" |
|||
:class="{ active: $route.path === '/' }" |
|||
>首页</a |
|||
> |
|||
</li> |
|||
<li |
|||
v-for="category in categories" |
|||
:key="category.id" |
|||
class="nav-item" |
|||
> |
|||
<a |
|||
href="/category/:id" |
|||
:to="`/category/${category.id}`" |
|||
class="nav-link" |
|||
:class="{ active: $route.params.id == category.id }" |
|||
> |
|||
{{ category.name }} |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</header> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapGetters, mapActions } from "vuex"; |
|||
|
|||
export default { |
|||
name: "HeaderNav", |
|||
data() { |
|||
return { |
|||
searchText: "", |
|||
}; |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
"getCategories", |
|||
"getCartTotalCount", |
|||
"isUserLogin", |
|||
"getUserInfo", |
|||
]), |
|||
categories() { |
|||
return this.getCategories; |
|||
}, |
|||
cartTotalCount() { |
|||
return this.getCartTotalCount; |
|||
}, |
|||
isLogin() { |
|||
// return this.isUserLogin; |
|||
return true; |
|||
}, |
|||
userInfo() { |
|||
return this.getUserInfo || {}; |
|||
}, |
|||
}, |
|||
created() { |
|||
this.fetchCategories(); |
|||
}, |
|||
methods: { |
|||
...mapActions(["fetchCategories", "logout"]), |
|||
handleSearch() { |
|||
if (this.searchText.trim()) { |
|||
// 这里实际项目中应该跳转到搜索结果页 |
|||
this.$message.success(`搜索: ${this.searchText}`); |
|||
} else { |
|||
this.$message.warning("请输入搜索内容"); |
|||
} |
|||
}, |
|||
handleLogout() { |
|||
this.$confirm("确定要退出登录吗?", "提示", { |
|||
confirmButtonText: "确定", |
|||
cancelButtonText: "取消", |
|||
type: "warning", |
|||
}) |
|||
.then(() => { |
|||
this.logout(); |
|||
this.$message.success("退出登录成功"); |
|||
this.$router.push("/"); |
|||
}) |
|||
.catch(() => { |
|||
// 取消退出 |
|||
}); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.header-nav { |
|||
width: 100%; |
|||
|
|||
.top-notice { |
|||
background-color: #f5f5f5; |
|||
padding: 8px 0; |
|||
|
|||
.container { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
max-width: 1200px; |
|||
margin: 0 auto; |
|||
padding: 0 20px; |
|||
|
|||
p { |
|||
font-size: 14px; |
|||
color: #666; |
|||
|
|||
.highlight { |
|||
color: #ff4400; |
|||
margin: 0 5px; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
|
|||
.top-links { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 15px; |
|||
font-size: 14px; |
|||
|
|||
.avatar { |
|||
width: 24px; |
|||
height: 24px; |
|||
border-radius: 50%; |
|||
margin-right: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.separator { |
|||
color: #ccc; |
|||
} |
|||
|
|||
a { |
|||
color: #666; |
|||
transition: color 0.2s; |
|||
|
|||
&:hover { |
|||
color: #409eff; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.main-nav { |
|||
background-color: #fff; |
|||
padding: 15px 0; |
|||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); |
|||
|
|||
.container { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
max-width: 1200px; |
|||
margin: 0 auto; |
|||
padding: 0 20px; |
|||
} |
|||
|
|||
.logo { |
|||
a { |
|||
display: block; |
|||
|
|||
h1 { |
|||
font-size: 28px; |
|||
color: #409eff; |
|||
margin: 0; |
|||
font-weight: 700; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.search-box { |
|||
flex: 0 0 500px; |
|||
|
|||
@media (max-width: 992px) { |
|||
flex: 0 0 350px; |
|||
} |
|||
|
|||
@media (max-width: 768px) { |
|||
display: none; |
|||
} |
|||
|
|||
.search-input { |
|||
width: 100%; |
|||
} |
|||
|
|||
.hot-tags { |
|||
margin-top: 8px; |
|||
font-size: 12px; |
|||
color: #999; |
|||
|
|||
span { |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
a { |
|||
margin: 0 5px; |
|||
color: #666; |
|||
|
|||
&:hover { |
|||
color: #409eff; |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.cart-entry { |
|||
.cart-link { |
|||
display: flex; |
|||
align-items: center; |
|||
color: #333; |
|||
font-size: 16px; |
|||
|
|||
.cart-icon { |
|||
font-size: 20px; |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.cart-count { |
|||
display: inline-block; |
|||
width: 18px; |
|||
height: 18px; |
|||
background-color: #ff4400; |
|||
color: white; |
|||
border-radius: 50%; |
|||
font-size: 12px; |
|||
text-align: center; |
|||
line-height: 18px; |
|||
margin-left: 5px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.category-nav { |
|||
background-color: #409eff; |
|||
|
|||
.container { |
|||
max-width: 1200px; |
|||
margin: 0 auto; |
|||
padding: 0 20px; |
|||
} |
|||
|
|||
.nav-list { |
|||
display: flex; |
|||
margin: 0; |
|||
padding: 0; |
|||
|
|||
@media (max-width: 992px) { |
|||
overflow-x: auto; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.nav-item { |
|||
list-style: none; |
|||
|
|||
.nav-link { |
|||
display: inline-block; |
|||
padding: 12px 20px; |
|||
color: #fff; |
|||
font-size: 16px; |
|||
transition: background-color 0.2s; |
|||
|
|||
&:hover, |
|||
&.active { |
|||
background-color: #337ab7; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,227 @@ |
|||
<template> |
|||
<div class="home-layout-container"> |
|||
<!-- 左侧导航栏 --> |
|||
<div class="left-nav"> |
|||
<ul class="nav-list"> |
|||
<li class="nav-item"><i class="el-icon-sell"></i>全部商品</li> |
|||
<li class="nav-item"><i class="el-icon-burger"></i>米面油</li> |
|||
<li class="nav-item"><i class="el-icon-fork-spoon"></i>调味干货</li> |
|||
<li class="nav-item"><i class="el-icon-potato-strips"></i>休闲食品</li> |
|||
<li class="nav-item"><i class="el-icon-dish"></i>禽畜肉蛋</li> |
|||
<li class="nav-item"><i class="el-icon-grape"></i>鲜果蔬菜</li> |
|||
<li class="nav-item"><i class="el-icon-present"></i>组合集市</li> |
|||
</ul> |
|||
</div> |
|||
|
|||
<!-- 轮播图 --> |
|||
<div class="main-content"> |
|||
<el-carousel style="height: 100%"> |
|||
<el-carousel-item v-for="item in 4" :key="item.id"> |
|||
<img |
|||
src=" |
|||
https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png |
|||
" |
|||
class="carousel-img" |
|||
/> |
|||
</el-carousel-item> |
|||
</el-carousel> |
|||
</div> |
|||
|
|||
<!-- 右侧用户信息栏 --> |
|||
<div class="right-info"> |
|||
<div class="avatar-container"> |
|||
<img |
|||
src="https://picsum.photos/id/64/120/120" |
|||
alt="用户头像" |
|||
class="user-avatar" |
|||
/> |
|||
<div class="welcome-text">Hi-欢迎您</div> |
|||
</div> |
|||
|
|||
<div class="btn-group"> |
|||
<el-button type="danger" size="mini">登录</el-button> |
|||
<el-button type="warning" size="mini">注册</el-button> |
|||
<el-button type="primary" size="mini">客服</el-button> |
|||
</div> |
|||
|
|||
<div class="func-icons"> |
|||
<div class="icon-item"> |
|||
<i class="icon el-icon-user"></i> |
|||
<span>个人中心</span> |
|||
</div> |
|||
<div class="icon-item"> |
|||
<i class="icon el-icon-goods"></i> |
|||
<span>我的订单</span> |
|||
</div> |
|||
<div class="icon-item"> |
|||
<i class="icon el-icon-star-off"></i> |
|||
<span>我的收藏</span> |
|||
</div> |
|||
<div class="icon-item"> |
|||
<i class="icon el-icon-pie-chart"></i> |
|||
<span>议价单</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="announcement"> |
|||
<el-tag type="danger" size="mini">公告</el-tag> |
|||
<span>2099年12月平台重要新规速递</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "HomeLayout", |
|||
data() { |
|||
return { |
|||
// 轮播图数据 |
|||
carouselItems: [ |
|||
{ |
|||
id: 1, |
|||
imageUrl: "https://picsum.photos/id/26/1200/500", |
|||
altText: "新鲜水果促销", |
|||
}, |
|||
{ |
|||
id: 2, |
|||
imageUrl: "https://picsum.photos/id/292/1200/500", |
|||
altText: "有机蔬菜专场", |
|||
}, |
|||
{ |
|||
id: 3, |
|||
imageUrl: "https://picsum.photos/id/431/1200/500", |
|||
altText: "粮油特惠活动", |
|||
}, |
|||
], |
|||
}; |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.home-layout-container { |
|||
display: flex; |
|||
width: 100%; |
|||
height: auto; |
|||
} |
|||
|
|||
/* 左侧导航栏样式 */ |
|||
.left-nav { |
|||
width: 200px; |
|||
background-color: #f8f9fa; |
|||
padding: 20px 0; |
|||
box-sizing: border-box; |
|||
} |
|||
.nav-list { |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
.nav-item { |
|||
padding: 12px 20px; |
|||
cursor: pointer; |
|||
color: #333; |
|||
transition: all 0.3s ease; |
|||
|
|||
i { |
|||
color: #f63131; |
|||
margin-right: 5px; |
|||
} |
|||
} |
|||
.nav-item:hover { |
|||
background-color: #e9ecef; |
|||
padding-left: 25px; |
|||
} |
|||
|
|||
/* 中间主内容样式 */ |
|||
.main-content { |
|||
flex: 1; |
|||
height: auto; |
|||
overflow: hidden; |
|||
} |
|||
.carousel-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: cover; |
|||
} |
|||
.main-content ::v-deep .el-carousel__container { |
|||
height: 100% !important; |
|||
} |
|||
|
|||
/* 右侧用户信息栏样式 */ |
|||
.right-info { |
|||
width: 280px; |
|||
background-color: #fff; |
|||
padding: 20px; |
|||
box-sizing: border-box; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
border-left: 1px solid #eee; |
|||
} |
|||
.avatar-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
margin-bottom: 20px; |
|||
} |
|||
.user-avatar { |
|||
width: 60px; |
|||
height: 60px; |
|||
border-radius: 50%; |
|||
margin-bottom: 10px; |
|||
border: 2px solid #f0f0f0; |
|||
} |
|||
.welcome-text { |
|||
font-size: 14px; |
|||
color: #333; |
|||
} |
|||
.btn-group { |
|||
display: flex; |
|||
gap: 10px; |
|||
margin-bottom: 30px; |
|||
width: 100%; |
|||
} |
|||
.func-icons { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
width: 100%; |
|||
margin-bottom: 30px; |
|||
padding: 10px 0; |
|||
border-top: 1px dashed #eee; |
|||
border-bottom: 1px dashed #eee; |
|||
} |
|||
.icon-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
color: #666; |
|||
transition: color 0.3s ease; |
|||
width: 50px; |
|||
} |
|||
.icon-item:hover { |
|||
color: #1890ff; |
|||
} |
|||
.icon { |
|||
font-size: 24px; |
|||
margin-bottom: 5px; |
|||
} |
|||
.icon-item span { |
|||
font-size: 12px; |
|||
} |
|||
.announcement { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 12px; |
|||
color: #666; |
|||
width: 100%; |
|||
padding-top: 10px; |
|||
|
|||
span { |
|||
margin-left: 10px; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,191 @@ |
|||
<template> |
|||
<div class="product-list"> |
|||
<div class="product-grid"> |
|||
<div v-for="product in products" :key="product.id" class="product-card"> |
|||
<div class="product-img"> |
|||
<a :href="`/product/${product.id}`" :to="`/product/${product.id}`"> |
|||
<img |
|||
v-lazy="product.image" |
|||
:alt="product.name" |
|||
class="product-pic" |
|||
/> |
|||
</a> |
|||
</div> |
|||
<div class="product-info"> |
|||
<div class="flex-between"> |
|||
<div class="product-price"> |
|||
<span class="current-price">¥{{ product.price.toFixed(2) }}</span> |
|||
<span class="original-price" v-if="product.originalPrice" |
|||
>¥{{ product.originalPrice.toFixed(2) }}</span |
|||
> |
|||
</div> |
|||
<div class="product-sales" v-if="product.sales"> |
|||
<span>已售 {{ product.sales }} 件</span> |
|||
</div> |
|||
</div> |
|||
<h3 class="product-name"> |
|||
<a :href="`/product/${product.id}`" :to="`/product/${product.id}`"> |
|||
{{ product.name }} |
|||
</a> |
|||
</h3> |
|||
<div class="product-actions"> |
|||
<el-button |
|||
type="primary" |
|||
size="small" |
|||
style="background-color: #ff6e90; border: none" |
|||
@click="addToCart(product)" |
|||
> |
|||
<el-icon name="el-icon-shopping-cart"></el-icon> 加入购物车 |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 无商品时显示 --> |
|||
<div class="no-products" v-if="products.length === 0"> |
|||
<el-empty description="暂无相关商品"></el-empty> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapActions } from "vuex"; |
|||
|
|||
export default { |
|||
name: "ProductList", |
|||
props: { |
|||
products: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
}, |
|||
methods: { |
|||
...mapActions(["addToCart"]), |
|||
addToCart(product) { |
|||
// 检查用户是否登录 |
|||
if (!this.$store.getters.isUserLogin) { |
|||
this.$confirm("您尚未登录,是否前往登录?", "提示", { |
|||
confirmButtonText: "登录", |
|||
cancelButtonText: "取消", |
|||
type: "info", |
|||
}) |
|||
.then(() => { |
|||
this.$router.push({ |
|||
path: "/login", |
|||
query: { redirect: this.$route.fullPath }, |
|||
}); |
|||
}) |
|||
.catch(() => { |
|||
// 取消登录 |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
this.addToCart({ |
|||
id: product.id, |
|||
name: product.name, |
|||
price: product.price, |
|||
image: product.image, |
|||
quantity: 1, |
|||
}); |
|||
this.$message.success("已加入购物车"); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.product-list { |
|||
.product-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
|||
gap: 20px; |
|||
} |
|||
|
|||
.product-card { |
|||
border: 1px solid #eaeaea; |
|||
border-radius: 8px; |
|||
overflow: hidden; |
|||
transition: all 0.3s ease; |
|||
padding: 10px; |
|||
|
|||
&:hover { |
|||
transform: translateY(-5px); |
|||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.product-img { |
|||
height: 250px; |
|||
overflow: hidden; |
|||
|
|||
.product-pic { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: cover; |
|||
transition: transform 0.5s ease; |
|||
|
|||
&:hover { |
|||
transform: scale(1.05); |
|||
} |
|||
} |
|||
} |
|||
|
|||
.product-info { |
|||
padding: 10px; |
|||
|
|||
.product-name { |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
min-height: 40px; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
margin-bottom: 10px; |
|||
|
|||
a { |
|||
color: #333; |
|||
|
|||
&:hover { |
|||
color: #409eff; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.product-price { |
|||
margin-bottom: 15px; |
|||
|
|||
.current-price { |
|||
color: #ff4400; |
|||
font-size: 16px; |
|||
font-weight: 700; |
|||
} |
|||
|
|||
.original-price { |
|||
color: #999; |
|||
font-size: 12px; |
|||
text-decoration: line-through; |
|||
margin-left: 8px; |
|||
} |
|||
} |
|||
|
|||
.product-actions { |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
.product-sales { |
|||
font-size: 12px; |
|||
color: #999; |
|||
margin-bottom: 15px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.no-products { |
|||
padding: 50px 0; |
|||
text-align: center; |
|||
} |
|||
} |
|||
</style> |
@ -1,12 +1,64 @@ |
|||
import Vue from 'vue' |
|||
import App from './App.vue' |
|||
import App from './App' |
|||
import router from './router' |
|||
import store from './store' |
|||
import ElementUI from 'element-ui' |
|||
import 'element-ui/lib/theme-chalk/index.css' |
|||
import axios from 'axios' |
|||
import VueLazyload from 'vue-lazyload' |
|||
import '@/assets/css/common.css' |
|||
|
|||
// 全局配置
|
|||
Vue.config.productionTip = false |
|||
Vue.use(ElementUI) |
|||
Vue.prototype.$http = axios |
|||
|
|||
// 配置图片懒加载
|
|||
Vue.use(VueLazyload, { |
|||
preLoad: 1.3, // 预加载高度比例
|
|||
error: require('./assets/logo.png'), |
|||
loading: require('./assets/logo.png'), |
|||
attempt: 3, // 增加尝试次数
|
|||
listenEvents: ['scroll', 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'], // 确保监听事件完整
|
|||
adapter: { |
|||
// 增加加载状态日志
|
|||
loaded({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error, Init }) { |
|||
console.log('图片加载完成:', src) |
|||
}, |
|||
error({ bindType, el, error, $parent, src, loading }) { |
|||
console.log('图片加载失败:', src) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
// 请求拦截器设置
|
|||
axios.interceptors.request.use( |
|||
config => { |
|||
// 可以在这里添加token等信息
|
|||
return config |
|||
}, |
|||
error => { |
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
|
|||
// 响应拦截器设置
|
|||
axios.interceptors.response.use( |
|||
response => { |
|||
return response.data |
|||
}, |
|||
error => { |
|||
// 统一错误处理
|
|||
ElementUI.Message.error('请求失败,请稍后重试') |
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
|
|||
/* eslint-disable no-new */ |
|||
new Vue({ |
|||
el: '#app', |
|||
router, |
|||
store, |
|||
render: h => h(App) |
|||
}).$mount('#app') |
|||
components: { App }, |
|||
template: '<App/>' |
|||
}) |
|||
|
@ -1,29 +1,153 @@ |
|||
import Vue from 'vue' |
|||
import VueRouter from 'vue-router' |
|||
import HomeView from '../views/HomeView.vue' |
|||
import Router from 'vue-router' |
|||
|
|||
Vue.use(VueRouter) |
|||
Vue.use(Router) |
|||
|
|||
const routes = [ |
|||
{ |
|||
path: '/', |
|||
name: 'home', |
|||
component: HomeView |
|||
}, |
|||
{ |
|||
path: '/about', |
|||
name: 'about', |
|||
// route level code-splitting
|
|||
// this generates a separate chunk (about.[hash].js) for this route
|
|||
// which is lazy-loaded when the route is visited.
|
|||
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue') |
|||
} |
|||
] |
|||
|
|||
const router = new VueRouter({ |
|||
const router = new Router({ |
|||
mode: 'history', |
|||
base: process.env.BASE_URL, |
|||
routes |
|||
routes: [ |
|||
{ |
|||
path: '/', |
|||
name: 'Index', |
|||
meta: { |
|||
title: '首页 - 精品商城', |
|||
keepAlive: false |
|||
}, |
|||
component: () => import('@/views/Index.vue') |
|||
}, |
|||
{ |
|||
path: '/Home', |
|||
name: 'Home', |
|||
meta: { |
|||
title: '首页 - 精品商城', |
|||
keepAlive: false |
|||
}, |
|||
component: () => import('@/views/Home.vue') |
|||
}, |
|||
// {
|
|||
// path: '/category/:id?',
|
|||
// name: 'Category',
|
|||
// meta: {
|
|||
// title: '商品分类 - 精品商城',
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/Category.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '/product/:id',
|
|||
// name: 'ProductDetail',
|
|||
// meta: {
|
|||
// title: '商品详情 - 精品商城',
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/ProductDetail.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '/cart',
|
|||
// name: 'Cart',
|
|||
// meta: {
|
|||
// title: '购物车 - 精品商城',
|
|||
// requireAuth: true,
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/Cart.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '/checkout',
|
|||
// name: 'Checkout',
|
|||
// meta: {
|
|||
// title: '结算 - 精品商城',
|
|||
// requireAuth: true,
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/Checkout.vue')
|
|||
// },
|
|||
{ |
|||
path: '/userCenter', |
|||
name: 'UserCenter', |
|||
meta: { |
|||
title: '个人中心 - 精品商城', |
|||
requireAuth: true, |
|||
keepAlive: false |
|||
}, |
|||
component: () => import('@/views/User/UserCenter.vue') |
|||
}, |
|||
{ |
|||
path: 'orderList', |
|||
name: 'OrderList', |
|||
meta: { |
|||
title: '我的订单 - 精品商城', |
|||
requireAuth: true, |
|||
keepAlive: false |
|||
}, |
|||
component: () => import('@/views/User/OrderList.vue') |
|||
}, |
|||
// {
|
|||
// path: '/user/orders/:id',
|
|||
// name: 'OrderDetail',
|
|||
// meta: {
|
|||
// title: '订单详情 - 精品商城',
|
|||
// requireAuth: true,
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/OrderDetail.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '/login',
|
|||
// name: 'Login',
|
|||
// meta: {
|
|||
// title: '登录 - 精品商城',
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/Login.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '/register',
|
|||
// name: 'Register',
|
|||
// meta: {
|
|||
// title: '注册 - 精品商城',
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/Register.vue')
|
|||
// },
|
|||
// {
|
|||
// path: '*',
|
|||
// name: 'NotFound',
|
|||
// meta: {
|
|||
// title: '页面不存在 - 精品商城',
|
|||
// keepAlive: false
|
|||
// },
|
|||
// component: () => import('@/views/NotFound.vue')
|
|||
// }
|
|||
], |
|||
scrollBehavior(to, from, savedPosition) { |
|||
// 页面滚动到顶部
|
|||
return { x: 0, y: 0 } |
|||
} |
|||
}) |
|||
|
|||
// 路由守卫
|
|||
router.beforeEach((to, from, next) => { |
|||
// 设置页面标题
|
|||
if (to.meta.title) { |
|||
document.title = to.meta.title |
|||
} |
|||
|
|||
// 验证登录状态
|
|||
if (to.meta.requireAuth) { |
|||
const token = localStorage.getItem('token') |
|||
if (token) { |
|||
next() |
|||
} else { |
|||
next({ |
|||
path: '/login', |
|||
query: { redirect: to.fullPath } |
|||
}) |
|||
} |
|||
} else { |
|||
next() |
|||
} |
|||
}) |
|||
|
|||
export default router |
|||
export default router |
@ -1,17 +1,202 @@ |
|||
import Vue from 'vue' |
|||
import Vuex from 'vuex' |
|||
import createPersistedState from 'vuex-persistedstate' |
|||
|
|||
Vue.use(Vuex) |
|||
|
|||
export default new Vuex.Store({ |
|||
plugins: [ |
|||
createPersistedState({ |
|||
storage: window.localStorage, |
|||
reducer(val) { |
|||
return { |
|||
// 只持久化需要的状态
|
|||
cart: val.cart, |
|||
user: val.user |
|||
} |
|||
} |
|||
}) |
|||
], |
|||
state: { |
|||
}, |
|||
getters: { |
|||
// 购物车状态
|
|||
cart: { |
|||
items: [], // 购物车商品列表
|
|||
totalCount: 0, // 商品总数
|
|||
totalPrice: 0 // 商品总价
|
|||
}, |
|||
// 用户状态
|
|||
user: { |
|||
isLogin: false, |
|||
info: null, |
|||
token: '' |
|||
}, |
|||
// 分类数据
|
|||
categories: [], |
|||
// 全局加载状态
|
|||
loading: false |
|||
}, |
|||
mutations: { |
|||
// 更新分类数据
|
|||
UPDATE_CATEGORIES(state, categories) { |
|||
state.categories = categories |
|||
}, |
|||
|
|||
// 更新加载状态
|
|||
UPDATE_LOADING(state, status) { |
|||
state.loading = status |
|||
}, |
|||
|
|||
// 用户登录
|
|||
USER_LOGIN(state, { userInfo, token }) { |
|||
state.user.isLogin = true |
|||
state.user.info = userInfo |
|||
state.user.token = token |
|||
localStorage.setItem('token', token) |
|||
}, |
|||
|
|||
// 用户登出
|
|||
USER_LOGOUT(state) { |
|||
state.user.isLogin = false |
|||
state.user.info = null |
|||
state.user.token = '' |
|||
localStorage.removeItem('token') |
|||
}, |
|||
|
|||
// 添加商品到购物车
|
|||
ADD_TO_CART(state, product) { |
|||
const existingItem = state.cart.items.find(item => item.id === product.id) |
|||
|
|||
if (existingItem) { |
|||
existingItem.quantity += product.quantity || 1 |
|||
} else { |
|||
state.cart.items.push({ |
|||
...product, |
|||
quantity: product.quantity || 1 |
|||
}) |
|||
} |
|||
|
|||
this.commit('UPDATE_CART_TOTAL') |
|||
}, |
|||
|
|||
// 从购物车移除商品
|
|||
REMOVE_FROM_CART(state, productId) { |
|||
state.cart.items = state.cart.items.filter(item => item.id !== productId) |
|||
this.commit('UPDATE_CART_TOTAL') |
|||
}, |
|||
|
|||
// 更新购物车商品数量
|
|||
UPDATE_CART_ITEM_QUANTITY(state, { productId, quantity }) { |
|||
const item = state.cart.items.find(item => item.id === productId) |
|||
if (item) { |
|||
item.quantity = quantity |
|||
this.commit('UPDATE_CART_TOTAL') |
|||
} |
|||
}, |
|||
|
|||
// 清空购物车
|
|||
CLEAR_CART(state) { |
|||
state.cart.items = [] |
|||
this.commit('UPDATE_CART_TOTAL') |
|||
}, |
|||
|
|||
// 更新购物车总计
|
|||
UPDATE_CART_TOTAL(state) { |
|||
state.cart.totalCount = state.cart.items.reduce((total, item) => { |
|||
return total + item.quantity |
|||
}, 0) |
|||
|
|||
state.cart.totalPrice = state.cart.items.reduce((total, item) => { |
|||
return total + (item.price * item.quantity) |
|||
}, 0) |
|||
} |
|||
}, |
|||
actions: { |
|||
// 获取分类数据
|
|||
fetchCategories({ commit }) { |
|||
commit('UPDATE_LOADING', true) |
|||
// 模拟API请求
|
|||
return new Promise(resolve => { |
|||
setTimeout(() => { |
|||
const categories = [ |
|||
{ id: 1, name: '电子产品', icon: 'el-icon-laptop' }, |
|||
{ id: 2, name: '服装鞋帽', icon: 'el-icon-shopping-bag-1' }, |
|||
{ id: 3, name: '家居用品', icon: 'el-icon-home' }, |
|||
{ id: 4, name: '美妆个护', icon: 'el-icon-present' }, |
|||
{ id: 5, name: '食品饮料', icon: 'el-icon-dish' }, |
|||
{ id: 6, name: '图书音像', icon: 'el-icon-document' } |
|||
] |
|||
commit('UPDATE_CATEGORIES', categories) |
|||
commit('UPDATE_LOADING', false) |
|||
resolve(categories) |
|||
}, 500) |
|||
}) |
|||
}, |
|||
|
|||
// 用户登录
|
|||
login({ commit }, { username, password }) { |
|||
commit('UPDATE_LOADING', true) |
|||
// 模拟登录API请求
|
|||
return new Promise((resolve, reject) => { |
|||
setTimeout(() => { |
|||
if (username === 'test' && password === '123456') { |
|||
const userInfo = { id: 1, username: 'test', avatar: 'https://picsum.photos/200' } |
|||
const token = 'fake-token-123456' |
|||
commit('USER_LOGIN', { userInfo, token }) |
|||
commit('UPDATE_LOADING', false) |
|||
resolve(userInfo) |
|||
} else { |
|||
commit('UPDATE_LOADING', false) |
|||
reject(new Error('用户名或密码错误')) |
|||
} |
|||
}, 1000) |
|||
}) |
|||
}, |
|||
|
|||
// 用户登出
|
|||
logout({ commit }) { |
|||
commit('USER_LOGOUT') |
|||
}, |
|||
|
|||
// 添加商品到购物车
|
|||
addToCart({ commit }, product) { |
|||
commit('ADD_TO_CART', product) |
|||
}, |
|||
|
|||
// 从购物车移除商品
|
|||
removeFromCart({ commit }, productId) { |
|||
commit('REMOVE_FROM_CART', productId) |
|||
}, |
|||
|
|||
// 更新购物车商品数量
|
|||
updateCartItemQuantity({ commit }, payload) { |
|||
commit('UPDATE_CART_ITEM_QUANTITY', payload) |
|||
}, |
|||
|
|||
// 清空购物车
|
|||
clearCart({ commit }) { |
|||
commit('CLEAR_CART') |
|||
} |
|||
}, |
|||
modules: { |
|||
getters: { |
|||
// 获取分类列表
|
|||
getCategories: state => state.categories, |
|||
|
|||
// 获取购物车信息
|
|||
getCart: state => state.cart, |
|||
|
|||
// 获取购物车商品总数
|
|||
getCartTotalCount: state => state.cart.totalCount, |
|||
|
|||
// 获取购物车商品总价
|
|||
getCartTotalPrice: state => state.cart.totalPrice, |
|||
|
|||
// 获取用户登录状态
|
|||
isUserLogin: state => state.user.isLogin, |
|||
|
|||
// 获取用户信息
|
|||
getUserInfo: state => state.user.info, |
|||
|
|||
// 获取加载状态
|
|||
getLoadingStatus: state => state.loading |
|||
} |
|||
}) |
|||
|
@ -1,5 +0,0 @@ |
|||
<template> |
|||
<div class="about"> |
|||
<h1>This is an about page</h1> |
|||
</div> |
|||
</template> |
@ -0,0 +1,384 @@ |
|||
<template> |
|||
<div class="home-page"> |
|||
<!-- 轮播图 --> |
|||
<el-carousel |
|||
height="500px" |
|||
indicator-position="outside" |
|||
class="home-carousel" |
|||
> |
|||
<el-carousel-item v-for="item in 4" :key="item"> |
|||
<img |
|||
v-lazy="`https://picsum.photos/1200/500?random=${item}`" |
|||
alt="轮播图片" |
|||
class="carousel-img" |
|||
> |
|||
</el-carousel-item> |
|||
</el-carousel> |
|||
|
|||
<!-- 分类导航 --> |
|||
<div class="category-nav"> |
|||
<h2 class="section-title">商品分类</h2> |
|||
<div class="category-list"> |
|||
<div |
|||
v-for="category in categories" |
|||
:key="category.id" |
|||
class="category-item" |
|||
@click="$router.push(`/category/${category.id}`)" |
|||
> |
|||
<el-icon :name="category.icon" class="category-icon"></el-icon> |
|||
<span class="category-name">{{ category.name }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 热门商品 --> |
|||
<div class="hot-products"> |
|||
<div class="section-header"> |
|||
<h2 class="section-title">热门商品</h2> |
|||
<a href="#" class="more-link">查看更多 <i class="el-icon-arrow-right"></i></a> |
|||
</div> |
|||
|
|||
<ProductList :products="hotProducts" /> |
|||
</div> |
|||
|
|||
<!-- 新品上市 --> |
|||
<div class="new-products"> |
|||
<div class="section-header"> |
|||
<h2 class="section-title">新品上市</h2> |
|||
<a href="#" class="more-link">查看更多 <i class="el-icon-arrow-right"></i></a> |
|||
</div> |
|||
|
|||
<ProductList :products="newProducts" /> |
|||
</div> |
|||
|
|||
<!-- 促销活动 --> |
|||
<div class="promotion-section"> |
|||
<h2 class="section-title">限时促销</h2> |
|||
<div class="promotion-container"> |
|||
<div class="promotion-item"> |
|||
<img |
|||
v-lazy="`https://picsum.photos/600/300?random=10`" |
|||
alt="促销活动图片" |
|||
class="promotion-img" |
|||
> |
|||
<div class="promotion-info"> |
|||
<h3>夏季大促</h3> |
|||
<p>全场商品低至5折</p> |
|||
<el-button type="primary" size="medium">立即抢购</el-button> |
|||
</div> |
|||
</div> |
|||
<div class="promotion-item"> |
|||
<img |
|||
v-lazy="`https://picsum.photos/600/300?random=11`" |
|||
alt="促销活动图片" |
|||
class="promotion-img" |
|||
> |
|||
<div class="promotion-info"> |
|||
<h3>新品首发</h3> |
|||
<p>限量发售,先到先得</p> |
|||
<el-button type="primary" size="medium">立即抢购</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapGetters, mapActions } from 'vuex' |
|||
import ProductList from '../components/product/ProductList' |
|||
|
|||
export default { |
|||
name: 'Home', |
|||
components: { |
|||
ProductList |
|||
}, |
|||
data() { |
|||
return { |
|||
// 模拟热门商品数据 |
|||
hotProducts: [ |
|||
{ |
|||
id: 1, |
|||
name: '超薄笔记本电脑', |
|||
price: 5999, |
|||
originalPrice: 6999, |
|||
image: 'https://picsum.photos/300/300?random=1', |
|||
sales: 1254, |
|||
categoryId: 1 |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: '智能手表', |
|||
price: 1599, |
|||
originalPrice: 1799, |
|||
image: 'https://picsum.photos/300/300?random=2', |
|||
sales: 856, |
|||
categoryId: 1 |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: '纯棉T恤', |
|||
price: 99, |
|||
originalPrice: 199, |
|||
image: 'https://picsum.photos/300/300?random=3', |
|||
sales: 2356, |
|||
categoryId: 2 |
|||
}, |
|||
{ |
|||
id: 4, |
|||
name: '休闲牛仔裤', |
|||
price: 199, |
|||
originalPrice: 399, |
|||
image: 'https://picsum.photos/300/300?random=4', |
|||
sales: 1890, |
|||
categoryId: 2 |
|||
}, |
|||
{ |
|||
id: 5, |
|||
name: '舒适沙发', |
|||
price: 2999, |
|||
originalPrice: 3999, |
|||
image: 'https://picsum.photos/300/300?random=5', |
|||
sales: 324, |
|||
categoryId: 3 |
|||
}, |
|||
{ |
|||
id: 6, |
|||
name: '智能扫地机器人', |
|||
price: 1899, |
|||
originalPrice: 2299, |
|||
image: 'https://picsum.photos/300/300?random=6', |
|||
sales: 754, |
|||
categoryId: 3 |
|||
} |
|||
], |
|||
// 模拟新品数据 |
|||
newProducts: [ |
|||
{ |
|||
id: 7, |
|||
name: '高清投影仪', |
|||
price: 3299, |
|||
originalPrice: 3699, |
|||
image: 'https://picsum.photos/300/300?random=7', |
|||
sales: 156, |
|||
categoryId: 1 |
|||
}, |
|||
{ |
|||
id: 8, |
|||
name: '保湿面霜', |
|||
price: 299, |
|||
originalPrice: 359, |
|||
image: 'https://picsum.photos/300/300?random=8', |
|||
sales: 423, |
|||
categoryId: 4 |
|||
}, |
|||
{ |
|||
id: 9, |
|||
name: '有机水果礼盒', |
|||
price: 159, |
|||
originalPrice: 199, |
|||
image: 'https://picsum.photos/300/300?random=9', |
|||
sales: 287, |
|||
categoryId: 5 |
|||
}, |
|||
{ |
|||
id: 10, |
|||
name: '经典文学名著', |
|||
price: 129, |
|||
originalPrice: 199, |
|||
image: 'https://picsum.photos/300/300?random=10', |
|||
sales: 342, |
|||
categoryId: 6 |
|||
}, |
|||
{ |
|||
id: 11, |
|||
name: '无线蓝牙耳机', |
|||
price: 799, |
|||
originalPrice: 999, |
|||
image: 'https://picsum.photos/300/300?random=11', |
|||
sales: 567, |
|||
categoryId: 1 |
|||
}, |
|||
{ |
|||
id: 12, |
|||
name: '运动鞋', |
|||
price: 499, |
|||
originalPrice: 699, |
|||
image: 'https://picsum.photos/300/300?random=12', |
|||
sales: 432, |
|||
categoryId: 2 |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'getCategories' |
|||
]), |
|||
categories() { |
|||
return this.getCategories |
|||
} |
|||
}, |
|||
created() { |
|||
this.fetchCategories() |
|||
}, |
|||
methods: { |
|||
...mapActions([ |
|||
'fetchCategories' |
|||
]) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.home-page { |
|||
.home-carousel { |
|||
margin-bottom: 30px; |
|||
|
|||
.carousel-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: cover; |
|||
} |
|||
} |
|||
|
|||
.category-nav { |
|||
margin-bottom: 40px; |
|||
|
|||
.category-list { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
flex-wrap: wrap; |
|||
gap: 20px; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.category-item { |
|||
flex: 1; |
|||
min-width: 120px; |
|||
height: 150px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border: 1px solid #eaeaea; |
|||
border-radius: 8px; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
|
|||
&:hover { |
|||
transform: translateY(-5px); |
|||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|||
border-color: #409eff; |
|||
} |
|||
|
|||
.category-icon { |
|||
font-size: 36px; |
|||
color: #409eff; |
|||
margin-bottom: 15px; |
|||
} |
|||
|
|||
.category-name { |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 24px; |
|||
color: #333; |
|||
padding-bottom: 10px; |
|||
border-bottom: 2px solid #409eff; |
|||
display: inline-block; |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.section-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 20px; |
|||
|
|||
.more-link { |
|||
color: #409eff; |
|||
font-size: 14px; |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
&:hover { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
i { |
|||
margin-left: 5px; |
|||
font-size: 16px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.hot-products, .new-products { |
|||
margin-bottom: 40px; |
|||
} |
|||
|
|||
.promotion-section { |
|||
margin: 40px 0; |
|||
|
|||
.promotion-container { |
|||
display: flex; |
|||
gap: 20px; |
|||
margin-top: 20px; |
|||
|
|||
@media (max-width: 768px) { |
|||
flex-direction: column; |
|||
} |
|||
} |
|||
|
|||
.promotion-item { |
|||
flex: 1; |
|||
position: relative; |
|||
height: 300px; |
|||
border-radius: 8px; |
|||
overflow: hidden; |
|||
|
|||
.promotion-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: cover; |
|||
transition: transform 0.5s ease; |
|||
|
|||
&:hover { |
|||
transform: scale(1.05); |
|||
} |
|||
} |
|||
|
|||
.promotion-info { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
color: #fff; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 20px; |
|||
text-align: center; |
|||
|
|||
h3 { |
|||
font-size: 24px; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
p { |
|||
font-size: 16px; |
|||
margin-bottom: 20px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
</script> |
@ -1,18 +0,0 @@ |
|||
<template> |
|||
<div class="home"> |
|||
<img alt="Vue logo" src="../assets/logo.png"> |
|||
<HelloWorld msg="Welcome to Your Vue.js App"/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
// @ is an alias to /src |
|||
import HelloWorld from '@/components/HelloWorld.vue' |
|||
|
|||
export default { |
|||
name: 'HomeView', |
|||
components: { |
|||
HelloWorld |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,88 @@ |
|||
<template> |
|||
<div class="bg"> |
|||
<HomeLayout /> |
|||
|
|||
<div class="product-box"> |
|||
<h2>今日推荐</h2> |
|||
<ProductList :products="newProducts" /> |
|||
</div> |
|||
|
|||
<div class="product-box"> |
|||
<h2>热销排行</h2> |
|||
<ProductList :products="newProducts" /> |
|||
</div> |
|||
|
|||
<div class="product-box"> |
|||
<h2>新品上市</h2> |
|||
<ProductList :products="newProducts" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HomeLayout from "@/components/layout/HomeLayout.vue"; |
|||
import ProductList from "@/components/product/ProductList.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HomeLayout, |
|||
ProductList, |
|||
}, |
|||
data() { |
|||
return { |
|||
newProducts: [ |
|||
{ |
|||
id: 7, |
|||
name: "高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪高清投影仪", |
|||
price: 3299, |
|||
originalPrice: 3699, |
|||
image: |
|||
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png", |
|||
sales: 156, |
|||
categoryId: 1, |
|||
}, |
|||
{ |
|||
id: 8, |
|||
name: "保湿面霜", |
|||
price: 299, |
|||
originalPrice: 359, |
|||
image: |
|||
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png", |
|||
sales: 423, |
|||
categoryId: 4, |
|||
}, |
|||
{ |
|||
id: 9, |
|||
name: "有机水果礼盒", |
|||
price: 159, |
|||
originalPrice: 199, |
|||
image: |
|||
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png", |
|||
sales: 287, |
|||
categoryId: 5, |
|||
}, |
|||
{ |
|||
id: 10, |
|||
name: "经典文学名著", |
|||
price: 129, |
|||
originalPrice: 199, |
|||
image: |
|||
"https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png", |
|||
sales: 342, |
|||
categoryId: 6, |
|||
}, |
|||
], |
|||
}; |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.product-box { |
|||
margin: 30px 0; |
|||
|
|||
h2 { |
|||
margin-bottom: 20px; |
|||
} |
|||
} |
|||
</style> |
@ -1,4 +1,5 @@ |
|||
const { defineConfig } = require('@vue/cli-service') |
|||
module.exports = defineConfig({ |
|||
transpileDependencies: true |
|||
transpileDependencies: true, |
|||
runtimeCompiler: true |
|||
}) |
|||
|
Loading…
Reference in new issue