Browse Source

提交

master
chenkainan 2 months ago
parent
commit
bec427f266
  1. 24
      src/App.vue
  2. 0
      src/assets/css/common.scss
  3. 51
      src/components/layout/HeaderNav.vue
  4. 55
      src/components/layout/HomeLayout.vue
  5. 14
      src/components/product/ProductList.vue
  6. 14
      src/libs/utils.js
  7. 2
      src/main.js
  8. 13
      src/store/index.js
  9. 61
      src/views/Detail/Index.vue
  10. 56
      src/views/Index.vue
  11. 24
      src/views/Login.vue
  12. 514
      src/views/ProductPage/Index.vue

24
src/App.vue

@ -9,18 +9,23 @@
></el-loading> --> ></el-loading> -->
<!-- 顶部导航 --> <!-- 顶部导航 -->
<HeaderNav v-if="$route.name !== 'Login' && $route.name !== 'Register'" /> <HeaderNav v-if="$route.name !== 'Register'" />
<!-- 主内容区 --> <!-- 主内容区 -->
<main class="main-container"> <main class="main-container" v-if="$route.name !== 'Login'">
<router-view />
</main>
<!-- 登录页面 -->
<main class="main-containers" v-if="$route.name == 'Login'">
<router-view /> <router-view />
</main> </main>
<!-- 侧边栏 --> <!-- 侧边栏 -->
<Sidebar v-if="$route.name !== 'Login' && $route.name !== 'Register'" /> <Sidebar v-if="$route.name !== 'Register'" />
<!-- 页脚 --> <!-- 页脚 -->
<Footer v-if="$route.name !== 'Login' && $route.name !== 'Register'" /> <Footer v-if="$route.name !== 'Register'" />
<!-- 回到顶部按钮 --> <!-- 回到顶部按钮 -->
<BackToTop /> <BackToTop />
@ -29,8 +34,8 @@
<script> <script>
import HeaderNav from "./components/layout/HeaderNav"; import HeaderNav from "./components/layout/HeaderNav";
import Footer from './components/layout/Footer'; import Footer from "./components/layout/Footer";
import Sidebar from './components/layout/Sidebar'; import Sidebar from "./components/layout/Sidebar";
import BackToTop from "./components/common/BackToTop"; import BackToTop from "./components/common/BackToTop";
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
@ -40,7 +45,7 @@ export default {
HeaderNav, HeaderNav,
Footer, Footer,
BackToTop, BackToTop,
Sidebar Sidebar,
}, },
computed: { computed: {
...mapGetters(["getLoadingStatus"]), ...mapGetters(["getLoadingStatus"]),
@ -82,6 +87,11 @@ export default {
} }
} }
.main-containers {
flex: 1;
width: 100%;
}
// //
* { * {
margin: 0; margin: 0;

0
src/assets/css/common.css → src/assets/css/common.scss

51
src/components/layout/HeaderNav.vue

@ -4,7 +4,7 @@
<div class="top-notice"> <div class="top-notice">
<div class="container"> <div class="container">
<p> <p>
欢迎来到企业采购平台 欢迎来到时味苏州平台
<a href="/register" class="highlight">立即注册</a> <a href="/register" class="highlight">立即注册</a>
</p> </p>
<div class="top-links"> <div class="top-links">
@ -16,6 +16,7 @@
<span class="separator" v-if="isLogin">|</span> <span class="separator" v-if="isLogin">|</span>
<a href="/register" v-if="isLogin">注册</a> <a href="/register" v-if="isLogin">注册</a>
<a href="/user">采购人中心</a> <a href="/user">采购人中心</a>
<a href="/user">购物车<span v-if="cartTotalCount > 0">({{ cartTotalCount }})</span></a>
<a href="/userCenter" v-if="isLogin">我的订单</a> <a href="/userCenter" v-if="isLogin">我的订单</a>
<a href="">商户后台</a> <a href="">商户后台</a>
<a href="javascript:;" @click="handleLogout" v-if="isLogin">退出</a> <a href="javascript:;" @click="handleLogout" v-if="isLogin">退出</a>
@ -28,7 +29,11 @@
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<a href="/"> <a href="/">
<h1>精品商城</h1> <img
src="https://static.ticket.sz-trip.com/shiweisuzhou/pc/logo.png"
alt="logo"
class="logo-img"
/>
</a> </a>
</div> </div>
@ -75,12 +80,15 @@
</div> </div>
</div> </div>
<!-- 购物车入口 --> <!-- 电话 -->
<div class="cart-entry"> <div class="cart-entry">
<a href="/cart" class="cart-link"> <a class="cart-link">
<i class="el-icon-shopping-cart-full cart-icon"></i> <img
<span>购物车</span> src="https://static.ticket.sz-trip.com/shiweisuzhou/pc/login/phone.png"
<span class="cart-count" v-if="cartTotalCount > 0">{{ alt=""
/>
<span>0512-680541</span>
<span class="cart-count" v-if="cartTotalCount > 0 && false">{{
cartTotalCount cartTotalCount
}}</span> }}</span>
</a> </a>
@ -157,11 +165,22 @@ export default {
methods: { methods: {
...mapActions(["fetchCategories", "logout"]), ...mapActions(["fetchCategories", "logout"]),
handleSearch() { handleSearch() {
if (this.searchText.trim()) { this.$store.commit("setSearchText", this.searchText); // Vuex
//
this.$message.success(`搜索: ${this.searchText}`); //
const isSearchPage =
this.$route.path === "/productList" &&
this.$route.query.type === "search";
if (isSearchPage) {
//
this.$emit("search", this.searchText); //
} else { } else {
this.$message.warning("请输入搜索内容"); //
this.$router.push({
path: "/productList",
query: { keyword: this.searchText, type: "search" },
});
} }
}, },
handleLogout() { handleLogout() {
@ -231,9 +250,10 @@ export default {
a { a {
color: #666; color: #666;
transition: color 0.2s; transition: color 0.2s;
margin-left: 10px;
&:hover { &:hover {
color: #409eff; color: #6a8a27;
} }
} }
} }
@ -271,7 +291,7 @@ export default {
flex: 0 0 500px; flex: 0 0 500px;
@media (max-width: 992px) { @media (max-width: 992px) {
flex: 0 0 350px; flex: 0 0 500px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -280,6 +300,7 @@ export default {
.search-input { .search-input {
width: 100%; width: 100%;
border: 1px solid #6a8a27;
} }
.hot-tags { .hot-tags {
@ -310,6 +331,10 @@ export default {
color: #333; color: #333;
font-size: 16px; font-size: 16px;
span {
margin-left: 10px;
}
.cart-icon { .cart-icon {
font-size: 20px; font-size: 20px;
margin-right: 5px; margin-right: 5px;

55
src/components/layout/HomeLayout.vue

@ -3,26 +3,17 @@
<!-- 左侧导航栏 --> <!-- 左侧导航栏 -->
<router-link to="/productList" class="left-nav"> <router-link to="/productList" class="left-nav">
<ul class="nav-list"> <ul class="nav-list">
<li class="nav-item"><i class="el-icon-sell"></i>全部商品</li> <li v-for="item in tagList" :key="item.id" class="nav-item">
<li class="nav-item"><i class="el-icon-burger"></i>米面油</li> <img :src="util.showImg(item.image)" alt="" />{{ item.name }}
<li class="nav-item"><i class="el-icon-fork-spoon"></i>调味干货</li> </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> </ul>
</router-link> </router-link>
<!-- 轮播图 --> <!-- 轮播图 -->
<div class="main-content"> <div class="main-content">
<el-carousel style="height: 100%"> <el-carousel style="height: 100%">
<el-carousel-item v-for="item in 4" :key="item.id"> <el-carousel-item v-for="item in topBanner" :key="item.id">
<img <img :src="item.head_img" class="carousel-img" />
src="
https://static.ticket.sz-trip.com/jundaosuzhou/images/scenicType/topImg.png
"
class="carousel-img"
/>
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
@ -74,27 +65,18 @@
<script> <script>
export default { export default {
name: "HomeLayout", name: "HomeLayout",
props: {
topBanner: {
type: Array,
default: () => [],
},
tagList: {
type: Array,
default: () => [],
},
},
data() { data() {
return { 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> </script>
@ -123,9 +105,10 @@ export default {
cursor: pointer; cursor: pointer;
color: #333; color: #333;
transition: all 0.3s ease; transition: all 0.3s ease;
display: flex;
i { img {
color: #f63131; width: 20px;
margin-right: 5px; margin-right: 5px;
} }
} }

14
src/components/product/ProductList.vue

@ -5,8 +5,8 @@
<div class="product-img"> <div class="product-img">
<a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`"> <a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`">
<img <img
v-lazy="product.image" v-lazy="product.headimg"
:alt="product.name" :alt="product.title"
class="product-pic" class="product-pic"
/> />
</a> </a>
@ -14,18 +14,18 @@
<div class="product-info"> <div class="product-info">
<div class="flex-between"> <div class="flex-between">
<div class="product-price"> <div class="product-price">
<span class="current-price">¥{{ product.price.toFixed(2) }}</span> <span class="current-price">¥{{ product.price / 100 }}</span>
<span class="original-price" v-if="product.originalPrice" <span class="original-price" v-if="product.market_price"
>¥{{ product.originalPrice.toFixed(2) }}</span >¥{{ product.market_price / 100 }}</span
> >
</div> </div>
<div class="product-sales" v-if="product.sales"> <div class="product-sales" v-if="product.sales">
<span>已售 {{ product.sales }} </span> <span>已售 {{ product.sales_number }} </span>
</div> </div>
</div> </div>
<h3 class="product-name"> <h3 class="product-name">
<a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`"> <a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`">
{{ product.name }} {{ product.title }}
</a> </a>
</h3> </h3>
<div class="product-actions"> <div class="product-actions">

14
src/libs/utils.js

@ -55,24 +55,12 @@ export default {
let para = arrUrl[1]; let para = arrUrl[1];
return para ? para.split('&') : false; return para ? para.split('&') : false;
}, },
openMap(item) {
let data = {
type: 'map',
lon: item.scene_lon,
lat: item.scene_lat,
name: item.title,
address: item.address
}
uni.navigateTo({
url: '/subPackages/h5Web/h5Web?data=' + JSON.stringify(data)
})
},
showImg(img) { showImg(img) {
if(!img) return; if(!img) return;
if (img.indexOf('https://') != -1 || img.indexOf('http://') != -1) { if (img.indexOf('https://') != -1 || img.indexOf('http://') != -1) {
return img; return img;
} else { } else {
return 'https://changshu.js-dyyj.com' + img; return 'https://static.ticket.sz-trip.com' + img;
} }
}, },
gotoPath(path) { gotoPath(path) {

2
src/main.js

@ -5,7 +5,7 @@ import store from './store'
import ElementUI from 'element-ui' import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css' import 'element-ui/lib/theme-chalk/index.css'
import VueLazyload from 'vue-lazyload' import VueLazyload from 'vue-lazyload'
import '@/assets/css/common.css' import '@/assets/css/common.scss'
import '../src/libs/axios.js' // axios处理 import '../src/libs/axios.js' // axios处理
import utils from './libs/utils.js' import utils from './libs/utils.js'

13
src/store/index.js

@ -33,7 +33,8 @@ export default new Vuex.Store({
// 分类数据 // 分类数据
categories: [], categories: [],
// 全局加载状态 // 全局加载状态
loading: false loading: false,
searchText: '' // 存储搜索词
}, },
mutations: { mutations: {
// 更新分类数据 // 更新分类数据
@ -108,6 +109,10 @@ export default new Vuex.Store({
state.cart.totalPrice = state.cart.items.reduce((total, item) => { state.cart.totalPrice = state.cart.items.reduce((total, item) => {
return total + (item.price * item.quantity) return total + (item.price * item.quantity)
}, 0) }, 0)
},
setSearchText(state, text) {
state.searchText = text; // 修改搜索词
} }
}, },
actions: { actions: {
@ -197,6 +202,10 @@ export default new Vuex.Store({
getUserInfo: state => state.user.info, getUserInfo: state => state.user.info,
// 获取加载状态 // 获取加载状态
getLoadingStatus: state => state.loading getLoadingStatus: state => state.loading,
// 获取搜索词
getSearchText: state => state.searchText
} }
}) })

61
src/views/Detail/Index.vue

@ -10,7 +10,7 @@
@change="handleCarouselChange" @change="handleCarouselChange"
indicator-position="none" indicator-position="none"
> >
<el-carousel-item v-for="(img, index) in productImages" :key="index"> <el-carousel-item v-for="(img, index) in info.listimg" :key="index">
<img <img
:src="img" :src="img"
:alt="`商品图片${index + 1}`" :alt="`商品图片${index + 1}`"
@ -24,7 +24,7 @@
<!-- 自定义图片指示器 --> <!-- 自定义图片指示器 -->
<div class="image-indicators"> <div class="image-indicators">
<div <div
v-for="(img, index) in productImages" v-for="(img, index) in info.listimg"
:key="index" :key="index"
class="indicator-item" class="indicator-item"
:class="{ active: activeIndex === index }" :class="{ active: activeIndex === index }"
@ -38,19 +38,26 @@
<!-- 右侧商品信息区域保持不变 --> <!-- 右侧商品信息区域保持不变 -->
<div class="right-section"> <div class="right-section">
<h2 class="product-title"> <h2 class="product-title">
{{ productTitle }} {{ info.title }}
<span class="product-count">[{{ productCount }}]</span> <!-- <span class="product-count">[{{ productCount }}]</span> -->
</h2> </h2>
<div class="product-tags"> <div class="product-tags">
<el-tag type="info" size="mini">[产品标签]</el-tag> <el-tag
<span class="subtitle">{{ productSubtitle }}</span> effect="dark"
size="mini"
v-for="(item, index) in info.display_tags.split(',')"
:key="index"
style="margin-right: 5px"
>{{ item }}</el-tag
>
<span class="subtitle">{{ info.subtitle }}</span>
</div> </div>
<div class="price-info"> <div class="price-info">
<span class="price-label">售价</span> <span class="price-label">售价</span>
<span class="price-amount">¥{{ productPrice }}</span> <span class="price-amount">¥{{ info.price / 100 }}</span>
<span class="sales-volume">已售 {{ salesVolume }}</span> <span class="sales-volume">已售 {{ info.sales_number }}</span>
</div> </div>
<div class="product-attr"> <div class="product-attr">
@ -68,20 +75,20 @@
<span class="attr-label">商品规格</span> <span class="attr-label">商品规格</span>
<div class="custom-radio-group"> <div class="custom-radio-group">
<label <label
v-for="(spec, idx) in productSpecs" v-for="(spec, idx) in info.sku"
:key="idx" :key="idx"
class="custom-radio" class="custom-radio"
:class="{ 'is-checked': selectedSpec === spec }" :class="{ 'is-checked': selectedSpec === idx }"
@click="selectedSpec = spec" @click="selectedSpec = idx"
> >
<span class="radio-text">{{ spec }}</span> <span class="radio-text">{{ spec.sku_name }}</span>
</label> </label>
</div> </div>
</div> </div>
<div class="attr-item"> <div class="attr-item">
<span class="attr-label">发货地</span> <span class="attr-label">发货地</span>
<span class="attr-value">{{ origin }}</span> <span class="attr-value">{{ info.supplier_address }}</span>
</div> </div>
<div class="attr-item"> <div class="attr-item">
@ -151,7 +158,11 @@
</div> </div>
<!-- 商品详情 --> <!-- 商品详情 -->
<div class="product-detail-main" v-show="!tabIndex"></div> <div
class="product-detail-main"
v-show="!tabIndex"
v-html="util.formateRichText(info.content)"
></div>
<!-- 商品评价 --> <!-- 商品评价 -->
<Evaluate v-show="tabIndex" /> <Evaluate v-show="tabIndex" />
@ -169,23 +180,13 @@ export default {
}, },
data() { data() {
return { return {
productImages: [ info: {},
"https://picsum.photos/id/102/500/500",
"https://picsum.photos/id/103/500/500",
"https://picsum.photos/id/104/500/500",
"https://picsum.photos/id/105/500/500",
],
activeIndex: 0, // activeIndex: 0, //
productTitle: "面包",
productCount: "52个", productCount: "52个",
productSubtitle: "副标题", productSubtitle: "副标题",
productPrice: 509,
salesVolume: 1.22,
moq: 1, moq: 1,
deliveryMethod: "邮寄", deliveryMethod: "邮寄",
productSpecs: ["规格一", "规格二", "规格三", "规格四", "规格五"], selectedSpec: 0,
selectedSpec: "规格一",
origin: "江苏省苏州市吴中区",
otherInfo: "下单填写留言,即免费赠送精美贺卡!", otherInfo: "下单填写留言,即免费赠送精美贺卡!",
deliveryRange: "全国(可配送至全国1000多个城市,苏州市区内免配送费)", deliveryRange: "全国(可配送至全国1000多个城市,苏州市区内免配送费)",
quantity: 1, quantity: 1,
@ -257,6 +258,13 @@ export default {
if (!this.$refs.carousel) { if (!this.$refs.carousel) {
console.warn("轮播组件未正确加载,请检查ref属性是否设置"); console.warn("轮播组件未正确加载,请检查ref属性是否设置");
} }
this.get(
{ id: this.$route.params.id },
"/api/product/get_product_detail"
).then((res) => {
this.info = res.data;
});
}, },
}; };
</script> </script>
@ -341,7 +349,6 @@ export default {
margin-bottom: 15px; margin-bottom: 15px;
.subtitle { .subtitle {
margin-left: 5px;
color: #666; color: #666;
} }
} }

56
src/views/Index.vue

@ -1,20 +1,20 @@
<template> <template>
<div class="bg"> <div class="bg">
<HomeLayout /> <HomeLayout :topBanner="topBanner" :tagList="tagList" />
<div class="product-box"> <div class="product-box">
<h2>今日推荐</h2> <h2>今日推荐</h2>
<ProductList :products="newProducts" /> <ProductList :products="prouctList[0].list" />
</div> </div>
<div class="product-box"> <div class="product-box">
<h2>热销排行</h2> <h2>热销排行</h2>
<ProductList :products="newProducts" /> <ProductList :products="prouctList[1].list" />
</div> </div>
<div class="product-box"> <div class="product-box">
<h2>新品上市</h2> <h2>新品上市</h2>
<ProductList :products="newProducts" /> <ProductList :products="prouctList[2].list" />
</div> </div>
</div> </div>
</template> </template>
@ -31,6 +31,24 @@ export default {
data() { data() {
return { return {
topBanner: [], topBanner: [],
tagList: [],
prouctList: [
{
id: 670,
title: "今日推荐",
list: [],
},
{
id: 671,
title: "热销排行",
list: [],
},
{
id: 672,
title: "新品上市",
list: [],
},
],
newProducts: [ newProducts: [
{ {
id: 7, id: 7,
@ -77,10 +95,12 @@ export default {
}, },
created() { created() {
this.getTopBanner(); this.getTopBanner();
this.getTags();
this.getProductList();
}, },
methods: { methods: {
// banner
getTopBanner() { getTopBanner() {
// banner
this.post( this.post(
{ {
type_id: 6, type_id: 6,
@ -93,6 +113,32 @@ export default {
} }
}); });
}, },
//
getTags() {
this.post(
{
pid: 662,
},
"/api/product/tag_list"
).then((res) => {
this.tagList = res.data;
});
},
//
getProductList() {
this.prouctList.map((i) => {
this.post(
{
tag_id: i.id,
offset: 0,
limit: 4,
},
"/api/product/get_product_by_tag"
).then((res) => {
i.list = res.data.list;
});
});
},
}, },
}; };
</script> </script>

24
src/views/Login.vue

@ -2,7 +2,11 @@
<div class="login-page"> <div class="login-page">
<!-- 背景图容器 --> <!-- 背景图容器 -->
<div class="bg-container"> <div class="bg-container">
<img src="@/assets/logo.png" alt="login background" class="bg-img" /> <img
src="https://static.ticket.sz-trip.com/shiweisuzhou/pc/login/loginBg.png"
alt="login background"
class="bg-img"
/>
</div> </div>
<!-- 登录表单卡片 --> <!-- 登录表单卡片 -->
@ -230,15 +234,16 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.login-page { .login-page {
position: relative; position: relative;
left: 0;
width: 100%; width: 100%;
height: 100vh; height: 100%;
overflow: hidden; overflow: hidden;
// //
.bg-container { .bg-container {
width: 100%; width: 100vw;
height: 100%; height: 100%;
position: absolute; // position: absolute;
left: 0; left: 0;
top: 0; top: 0;
z-index: 1; z-index: 1;
@ -278,8 +283,8 @@ export default {
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
&.active { &.active {
color: #1890ff; color: #6a8a27;
border-bottom-color: #1890ff; border-bottom-color: #6a8a27;
} }
} }
} }
@ -299,6 +304,9 @@ export default {
.code-btn { .code-btn {
padding: 0 15px; padding: 0 15px;
height: 40px; height: 40px;
background-color: #6a8a27;
border: none;
margin-left: 8px;
} }
// //
@ -310,7 +318,7 @@ export default {
// //
.login-btn { .login-btn {
width: 100%; width: 100%;
background: #ff4d4f; background: #6a8a27;
border: none; border: none;
&:hover { &:hover {
background: #e03e40; background: #e03e40;
@ -324,7 +332,7 @@ export default {
color: #999; color: #999;
.register-link { .register-link {
color: #ff4d4f; color: #6a8a27;
text-decoration: underline; text-decoration: underline;
margin-left: 5px; margin-left: 5px;
} }

514
src/views/ProductPage/Index.vue

@ -1,11 +1,13 @@
<template> <template>
<div class="product-grid-page"> <div class="product-grid-page">
<!-- 筛选排序区可根据需求扩展设计稿未体现时可隐藏 --> <!-- 筛选排序区 -->
<div class="filter-bar" v-if="showFilter"> <div class="filter-bar" v-if="showFilter">
<!-- 分类弹框 -->
<el-select <el-select
v-model="selectedCategory" v-model="selectedCategory"
placeholder="选择分类" placeholder="全部分类"
class="filter-select" class="filter-select"
@change="handleCategoryChange"
> >
<el-option <el-option
v-for="cat in categories" v-for="cat in categories"
@ -15,28 +17,35 @@
></el-option> ></el-option>
</el-select> </el-select>
<!-- 排序选项 -->
<el-select <el-select
v-model="selectedSort" v-model="selectedSort"
placeholder="排序方式" placeholder="综合排序"
class="filter-select" class="filter-select"
@change="handleSortChange"
> >
<el-option <el-option label="综合排序" value="default"></el-option>
v-for="sort in sortOptions" <el-option label="销量↑" value="sales_asc"></el-option>
:key="sort.value" <el-option label="销量↓" value="sales_desc"></el-option>
:label="sort.label" <el-option label="价格↑" value="price_asc"></el-option>
:value="sort.value" <el-option label="价格↓" value="price_desc"></el-option>
></el-option>
</el-select> </el-select>
</div>
<el-input <!-- 搜索结果统计 -->
v-model="searchKeyword" <div class="result-stats" v-if="type == 'search'">
placeholder="搜索商品" <span>全部结果 ></span>
class="search-input" <span class="keyword" v-if="searchKeyword">"{{ searchKeyword }}"</span>
clearable <span
@keyup.enter.native="handleSearch" v-if="
selectedCategory &&
categories.find((cat) => cat.value === selectedCategory)
"
class="category"
> >
<i slot="suffix" class="el-icon-search" @click="handleSearch" style="line-height: 3;"></i> "{{ categories.find((cat) => cat.value === selectedCategory).label }}"
</el-input> </span>
<span>{{ totalProducts }}个结果</span>
</div> </div>
<!-- 商品网格 --> <!-- 商品网格 -->
@ -44,10 +53,9 @@
<div <div
class="product-card" class="product-card"
v-for="(product, index) in visibleProducts" v-for="(product, index) in visibleProducts"
:key="index" :key="product.id"
@click="goToDetail(product.id)" @click="goToDetail(product.id)"
> >
<!-- 商品图片懒加载 + 占位 -->
<el-image <el-image
v-lazy="product.image" v-lazy="product.image"
:alt="product.name" :alt="product.name"
@ -59,8 +67,6 @@
<i class="el-icon-loading"></i> <i class="el-icon-loading"></i>
</div> </div>
</el-image> </el-image>
<!-- 标签如新品折扣等设计稿未体现时可注释 -->
<div <div
class="tag" class="tag"
v-if="product.tag" v-if="product.tag"
@ -68,16 +74,17 @@
> >
{{ product.tag }} {{ product.tag }}
</div> </div>
<!-- 商品信息区 -->
<div class="product-info"> <div class="product-info">
<div class="product-name">{{ product.name }}</div> <div class="product-name">{{ product.name }}</div>
<div class="price-row"> <div class="price-row">
<span class="current-price">¥{{ product.price }}</span> <span class="current-price">¥{{ product.price.toFixed(2) }}</span>
<span class="original-price" v-if="product.originalPrice" <span class="original-price" v-if="product.originalPrice"
>¥{{ product.originalPrice }}</span >¥{{ product.originalPrice.toFixed(2) }}</span
> >
</div> </div>
<div class="sales-volume" v-if="product.sales > 0">
<i class="el-icon-shopping-cart"></i> 已售 {{ product.sales }}
</div>
<el-button <el-button
type="primary" type="primary"
size="mini" size="mini"
@ -88,15 +95,13 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<!-- 空状态 -->
<div class="empty-state" v-if="visibleProducts.length === 0"> <div class="empty-state" v-if="visibleProducts.length === 0">
<el-empty description="暂无商品"></el-empty> <el-empty description="暂无符合条件的商品"></el-empty>
</div> </div>
</div> </div>
<!-- 分页组件 --> <!-- 分页组件 -->
<div class="pagination-container"> <div class="pagination-container" v-if="totalProducts > 0">
<el-pagination <el-pagination
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
@ -111,262 +116,229 @@
</template> </template>
<script> <script>
import { mapGetters } from "vuex";
export default { export default {
name: "ProductGridPage", name: "ProductGridPage",
data() { data() {
return { return {
// / type: "",
selectedCategory: "", //
selectedSort: "",
searchKeyword: "", searchKeyword: "",
showFilter: true, // 稿 false // /
selectedCategory: "",
selectedSort: "default", //
showFilter: true,
// //
currentPage: 1, currentPage: 1,
pageSize: 12, // 稿 4 4 12 / pageSize: 12,
//
//
products: [ products: [
{ {
id: 1, id: 1,
name: "产品名称产品名称1", name: "无线蓝牙耳机 主动降噪长续航",
image: "https://picsum.photos/id/101/200/200", image: "https://picsum.photos/id/101/300/300",
price: 359, price: 359.0,
originalPrice: 499, originalPrice: 499.0,
category: "digital",
tag: "限时折扣", tag: "限时折扣",
tagColor: "#FF6B6B", tagColor: "#FF6B6B",
sales: 120,
}, },
{ {
id: 2, id: 2,
name: "产品名称产品名称", name: "智能手表 心率监测运动计步",
image: "https://picsum.photos/id/102/200/200", image: "https://picsum.photos/id/102/300/300",
price: 259, price: 259.0,
originalPrice: 329, originalPrice: 329.0,
category: "digital",
tag: "新品", tag: "新品",
tagColor: "#4ECDC4", tagColor: "#4ECDC4",
sales: 86,
}, },
{ {
id: 3, id: 3,
name: "产品名称产品名称", name: "纯棉短袖T恤 宽松休闲",
image: "https://picsum.photos/id/103/200/200", image: "https://picsum.photos/id/103/300/300",
price: 159, price: 89.0,
originalPrice: 229, originalPrice: 129.0,
tag: "爆款", category: "life",
tagColor: "#FFD166", sales: 320,
}, },
{ {
id: 4, id: 4,
name: "产品名称产品名称", name: "新鲜水果礼盒 当季混合装",
image: "https://picsum.photos/id/104/200/200", image: "https://picsum.photos/id/104/300/300",
price: 215, price: 159.0,
originalPrice: 299, category: "food",
tag: "推荐", tag: "热销",
tagColor: "#06D6A0", tagColor: "#FF9F1C",
sales: 215,
}, },
{ {
id: 5, id: 5,
name: "产品名称产品名称", name: "全自动咖啡机 家用小型",
image: "https://picsum.photos/id/105/200/200", image: "https://picsum.photos/id/105/300/300",
price: 299, price: 1299.0,
originalPrice: 399, originalPrice: 1599.0,
tag: "限时折扣", category: "life",
tagColor: "#FF6B6B", sales: 45,
}, },
{ {
id: 6, id: 6,
name: "产品名称产品名称", name: "高清投影仪 家用办公两用",
image: "https://picsum.photos/id/106/200/200", image: "https://picsum.photos/id/106/300/300",
price: 159, price: 2499.0,
originalPrice: 229, category: "digital",
tag: "新品", tag: "推荐",
tagColor: "#4ECDC4", tagColor: "#2EC4B6",
sales: 78,
}, },
{ {
id: 7, id: 7,
name: "产品名称产品名称", name: "进口红酒 赤霞珠干红",
image: "https://picsum.photos/id/107/200/200", image: "https://picsum.photos/id/107/300/300",
price: 229, price: 199.0,
originalPrice: 329, originalPrice: 258.0,
tag: "爆款", category: "food",
tagColor: "#FFD166", sales: 63,
}, },
{ {
id: 8, id: 8,
name: "产品名称产品名称", name: "瑜伽垫 防滑专业健身垫",
image: "https://picsum.photos/id/108/200/200", image: "https://picsum.photos/id/108/300/300",
price: 289, price: 129.0,
originalPrice: 399, category: "life",
tag: "推荐", sales: 156,
tagColor: "#06D6A0",
}, },
{ {
id: 9, id: 9,
name: "产品名称产品名称", name: "机械键盘 青轴游戏专用",
image: "https://picsum.photos/id/109/200/200", image: "https://picsum.photos/id/109/300/300",
price: 359, price: 299.0,
originalPrice: 499, originalPrice: 399.0,
category: "digital",
tag: "限时折扣", tag: "限时折扣",
tagColor: "#FF6B6B", tagColor: "#FF6B6B",
sales: 92,
}, },
{ {
id: 10, id: 10,
name: "产品名称产品名称", name: "有机蔬菜礼盒 新鲜配送",
image: "https://picsum.photos/id/110/200/200", image: "https://picsum.photos/id/110/300/300",
price: 159, price: 89.0,
originalPrice: 229, category: "food",
tag: "新品", sales: 205,
tagColor: "#4ECDC4",
}, },
{ {
id: 11, id: 11,
name: "产品名称产品名称", name: "北欧风落地灯 客厅卧室",
image: "https://picsum.photos/id/111/200/200", image: "https://picsum.photos/id/111/300/300",
price: 259, price: 199.0,
originalPrice: 329, category: "life",
tag: "爆款", sales: 57,
tagColor: "#FFD166",
}, },
{ {
id: 12, id: 12,
name: "产品名称产品名称", name: "便携式充电宝 20000mAh",
image: "https://picsum.photos/id/112/200/200", image: "https://picsum.photos/id/112/300/300",
price: 299, price: 129.0,
originalPrice: 399, originalPrice: 169.0,
tag: "推荐", category: "digital",
tagColor: "#06D6A0", sales: 310,
},
{
id: 13,
name: "产品名称产品名称",
image: "https://picsum.photos/id/113/200/200",
price: 359,
originalPrice: 499,
tag: "限时折扣",
tagColor: "#FF6B6B",
},
{
id: 14,
name: "产品名称产品名称",
image: "https://picsum.photos/id/114/200/200",
price: 159,
originalPrice: 229,
tag: "新品",
tagColor: "#4ECDC4",
},
{
id: 15,
name: "产品名称产品名称",
image: "https://picsum.photos/id/115/200/200",
price: 259,
originalPrice: 329,
tag: "爆款",
tagColor: "#FFD166",
},
{
id: 16,
name: "产品名称产品名称",
image: "https://picsum.photos/id/116/200/200",
price: 299,
originalPrice: 399,
tag: "推荐",
tagColor: "#06D6A0",
},
{
id: 17,
name: "产品名称产品名称",
image: "https://picsum.photos/id/117/200/200",
price: 359,
originalPrice: 499,
tag: "限时折扣",
tagColor: "#FF6B6B",
},
{
id: 18,
name: "产品名称产品名称",
image: "https://picsum.photos/id/118/200/200",
price: 159,
originalPrice: 229,
tag: "新品",
tagColor: "#4ECDC4",
},
{
id: 19,
name: "产品名称产品名称",
image: "https://picsum.photos/id/119/200/200",
price: 259,
originalPrice: 329,
tag: "爆款",
tagColor: "#FFD166",
},
{
id: 20,
name: "产品名称产品名称",
image: "https://picsum.photos/id/120/200/200",
price: 299,
originalPrice: 399,
tag: "推荐",
tagColor: "#06D6A0",
}, },
], ],
//
//
currentPage: 1,
pageSize: 12,
// 稿
categories: [ categories: [
{ label: "全部", value: "" }, { label: "全部", value: "" },
{ label: "美食", value: "food" }, { label: "美食", value: "food" },
{ label: "生活", value: "life" }, { label: "生活", value: "life" },
{ label: "数码", value: "digital" }, { label: "数码", value: "digital" },
], ],
sortOptions: [
{ label: "默认排序", value: "" },
{ label: "价格 ascending", value: "price_asc" },
{ label: "价格 descending", value: "price_desc" },
],
}; };
}, },
computed: { computed: {
...mapGetters(["getSearchText"]),
// //
filteredProducts() { filteredProducts() {
return this.products.filter((product) => { return this.products.filter((product) => {
//
const categoryMatch = this.selectedCategory const categoryMatch = this.selectedCategory
? product.category === this.selectedCategory ? product.category === this.selectedCategory
: true; : true;
//
const keywordMatch = this.searchKeyword const keywordMatch = this.searchKeyword
? product.name.includes(this.searchKeyword) ? product.name
.toLowerCase()
.includes(this.searchKeyword.toLowerCase())
: true; : true;
return categoryMatch && keywordMatch; return categoryMatch && keywordMatch;
}); });
}, },
// //
sortedProducts() { sortedProducts() {
if (this.selectedSort === "price_asc") { const sorted = [...this.filteredProducts];
return this.filteredProducts.sort((a, b) => a.price - b.price); console.log(...this.filteredProducts, sorted);
} else if (this.selectedSort === "price_desc") {
return this.filteredProducts.sort((a, b) => b.price - a.price); switch (this.selectedSort) {
case "price_asc":
return sorted.sort((a, b) => a.price - b.price);
case "price_desc":
return sorted.sort((a, b) => b.price - a.price);
case "sales_asc":
return sorted.sort((a, b) => a.sales - b.sales);
case "sales_desc":
return sorted.sort((a, b) => b.sales - a.sales);
default: // ID
return sorted.sort((a, b) => a.id - b.id);
} }
return this.filteredProducts;
}, },
// //
visibleProducts() { visibleProducts() {
const start = (this.currentPage - 1) * this.pageSize; const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize; const end = start + this.pageSize;
return this.sortedProducts.slice(start, end); return this.sortedProducts.slice(start, end);
}, },
// //
totalProducts() { totalProducts() {
return this.sortedProducts.length; return this.sortedProducts.length;
}, },
}, },
watch: {
//
getSearchText(newVal, oldVal) {
if (newVal !== oldVal) {
this.searchKeyword = newVal;
this.fetchProducts(); //
}
},
},
created() {
this.type = this.$route.query?.type;
this.searchKeyword = this.getSearchText;
this.fetchProducts();
},
methods: { methods: {
// //
async fetchProducts() {
console.log(this.getSearchText);
// const response = await this.$axios.get('/api/products', { params });
},
//
handleSearch() { handleSearch() {
this.currentPage = 1; this.currentPage = 1; //
},
//
handleCategoryChange() {
this.currentPage = 1; //
},
//
handleSortChange() {
this.currentPage = 1; //
}, },
// //
@ -378,14 +350,17 @@ export default {
// //
handleCurrentChange(val) { handleCurrentChange(val) {
this.currentPage = val; this.currentPage = val;
//
window.scrollTo(0, 0);
}, },
// //
addToCart(product) { addToCart(product) {
this.$message.success(`${product.name} 已加入购物车`); this.$message.success(`${product.name} 已加入购物车`);
// API
}, },
// //
goToDetail(id) { goToDetail(id) {
this.$router.push(`/product/${id}`); this.$router.push(`/product/${id}`);
}, },
@ -396,30 +371,53 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.product-grid-page { .product-grid-page {
padding: 20px; padding: 20px;
background-color: #fff; background-color: #f5f7fa;
max-width: 1400px;
margin: 0 auto;
// 稿 //
.filter-bar { .filter-bar {
display: flex; display: flex;
gap: 15px; gap: 15px;
margin-bottom: 20px; margin-bottom: 20px;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center;
padding: 15px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
.search-input {
flex: 1;
min-width: 200px;
}
.filter-select { .filter-select {
min-width: 150px; min-width: 140px;
} }
}
.search-input { //
flex: 1; .result-stats {
max-width: 300px; margin: 0 0 15px 5px;
color: #666;
font-size: 14px;
padding: 5px 0;
.keyword,
.category {
color: #ff4d4f;
font-weight: 500;
margin: 0 5px;
} }
} }
// 4 //
.product-grid { .product-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px; gap: 20px;
margin-bottom: 30px;
} }
// //
@ -434,89 +432,92 @@ export default {
&:hover { &:hover {
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
} }
//
.product-img { .product-img {
width: 100%; width: 100%;
height: 200px; height: 200px;
border-radius: 8px 8px 0 0; background-color: #f5f5f5;
}
.image-placeholder {
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
} }
// 稿
.tag { .tag {
position: absolute; position: absolute;
top: 10px; top: 10px;
left: 10px; left: 10px;
padding: 4px 8px; padding: 3px 8px;
border-radius: 4px;
color: #fff;
font-size: 12px; font-size: 12px;
color: #fff;
border-radius: 4px;
z-index: 1; z-index: 1;
} }
//
.product-info { .product-info {
padding: 15px; padding: 15px;
position: relative;
.product-name { .product-name {
font-size: 14px; font-size: 14px;
font-weight: 500; color: #333;
margin-bottom: 8px; margin-bottom: 10px;
line-height: 1.4; height: 40px;
height: 36px;
overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden;
} }
.price-row { .price-row {
display: flex; display: flex;
align-items: baseline; align-items: center;
gap: 8px; margin-bottom: 8px;
margin-bottom: 10px;
.current-price { .current-price {
font-size: 16px;
color: #ff4d4f; color: #ff4d4f;
font-weight: bold; font-weight: bold;
font-size: 16px;
} }
.original-price { .original-price {
font-size: 12px;
color: #999; color: #999;
font-size: 12px;
text-decoration: line-through; text-decoration: line-through;
margin-left: 8px;
} }
} }
.cart-btn { .sales-volume {
position: absolute;
bottom: 15px;
right: 15px;
padding: 4px 12px;
font-size: 12px; font-size: 12px;
color: #666;
margin-bottom: 10px;
display: flex;
align-items: center;
i {
font-size: 12px;
margin-right: 4px;
}
} }
}
}
// .cart-btn {
.image-placeholder { width: 100%;
width: 100%; }
height: 200px; }
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
color: #ccc;
} }
// //
.empty-state { .empty-state {
grid-column: 1 / -1;
padding: 60px 0;
text-align: center; text-align: center;
padding: 50px 0;
} }
// //
@ -524,17 +525,34 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 20px; margin-top: 20px;
padding: 10px;
} }
} }
// / //
@media (max-width: 768px) { @media (max-width: 768px) {
.product-grid { .product-grid-page {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); padding: 10px;
}
.product-card .product-img { .filter-bar {
height: 160px; padding: 10px;
gap: 10px;
}
.product-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.product-card {
.product-img {
height: 140px;
}
.image-placeholder {
height: 140px;
}
}
} }
} }
</style> </style>
Loading…
Cancel
Save