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> -->
<!-- 顶部导航 -->
<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 />
</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 />
@ -29,8 +34,8 @@
<script>
import HeaderNav from "./components/layout/HeaderNav";
import Footer from './components/layout/Footer';
import Sidebar from './components/layout/Sidebar';
import Footer from "./components/layout/Footer";
import Sidebar from "./components/layout/Sidebar";
import BackToTop from "./components/common/BackToTop";
import { mapGetters } from "vuex";
@ -40,7 +45,7 @@ export default {
HeaderNav,
Footer,
BackToTop,
Sidebar
Sidebar,
},
computed: {
...mapGetters(["getLoadingStatus"]),
@ -82,6 +87,11 @@ export default {
}
}
.main-containers {
flex: 1;
width: 100%;
}
//
* {
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="container">
<p>
欢迎来到企业采购平台
欢迎来到时味苏州平台
<a href="/register" class="highlight">立即注册</a>
</p>
<div class="top-links">
@ -16,6 +16,7 @@
<span class="separator" v-if="isLogin">|</span>
<a href="/register" v-if="isLogin">注册</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="">商户后台</a>
<a href="javascript:;" @click="handleLogout" v-if="isLogin">退出</a>
@ -28,7 +29,11 @@
<div class="container">
<div class="logo">
<a href="/">
<h1>精品商城</h1>
<img
src="https://static.ticket.sz-trip.com/shiweisuzhou/pc/logo.png"
alt="logo"
class="logo-img"
/>
</a>
</div>
@ -75,12 +80,15 @@
</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">{{
<a class="cart-link">
<img
src="https://static.ticket.sz-trip.com/shiweisuzhou/pc/login/phone.png"
alt=""
/>
<span>0512-680541</span>
<span class="cart-count" v-if="cartTotalCount > 0 && false">{{
cartTotalCount
}}</span>
</a>
@ -157,11 +165,22 @@ export default {
methods: {
...mapActions(["fetchCategories", "logout"]),
handleSearch() {
if (this.searchText.trim()) {
//
this.$message.success(`搜索: ${this.searchText}`);
this.$store.commit("setSearchText", this.searchText); // Vuex
//
const isSearchPage =
this.$route.path === "/productList" &&
this.$route.query.type === "search";
if (isSearchPage) {
//
this.$emit("search", this.searchText); //
} else {
this.$message.warning("请输入搜索内容");
//
this.$router.push({
path: "/productList",
query: { keyword: this.searchText, type: "search" },
});
}
},
handleLogout() {
@ -231,9 +250,10 @@ export default {
a {
color: #666;
transition: color 0.2s;
margin-left: 10px;
&:hover {
color: #409eff;
color: #6a8a27;
}
}
}
@ -271,7 +291,7 @@ export default {
flex: 0 0 500px;
@media (max-width: 992px) {
flex: 0 0 350px;
flex: 0 0 500px;
}
@media (max-width: 768px) {
@ -280,6 +300,7 @@ export default {
.search-input {
width: 100%;
border: 1px solid #6a8a27;
}
.hot-tags {
@ -310,6 +331,10 @@ export default {
color: #333;
font-size: 16px;
span {
margin-left: 10px;
}
.cart-icon {
font-size: 20px;
margin-right: 5px;

55
src/components/layout/HomeLayout.vue

@ -3,26 +3,17 @@
<!-- 左侧导航栏 -->
<router-link to="/productList" 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>
<li v-for="item in tagList" :key="item.id" class="nav-item">
<img :src="util.showImg(item.image)" alt="" />{{ item.name }}
</li>
</ul>
</router-link>
<!-- 轮播图 -->
<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 v-for="item in topBanner" :key="item.id">
<img :src="item.head_img" class="carousel-img" />
</el-carousel-item>
</el-carousel>
</div>
@ -74,27 +65,18 @@
<script>
export default {
name: "HomeLayout",
props: {
topBanner: {
type: Array,
default: () => [],
},
tagList: {
type: Array,
default: () => [],
},
},
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: "粮油特惠活动",
},
],
};
return {};
},
};
</script>
@ -123,9 +105,10 @@ export default {
cursor: pointer;
color: #333;
transition: all 0.3s ease;
display: flex;
i {
color: #f63131;
img {
width: 20px;
margin-right: 5px;
}
}

14
src/components/product/ProductList.vue

@ -5,8 +5,8 @@
<div class="product-img">
<a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`">
<img
v-lazy="product.image"
:alt="product.name"
v-lazy="product.headimg"
:alt="product.title"
class="product-pic"
/>
</a>
@ -14,18 +14,18 @@
<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
<span class="current-price">¥{{ product.price / 100 }}</span>
<span class="original-price" v-if="product.market_price"
>¥{{ product.market_price / 100 }}</span
>
</div>
<div class="product-sales" v-if="product.sales">
<span>已售 {{ product.sales }} </span>
<span>已售 {{ product.sales_number }} </span>
</div>
</div>
<h3 class="product-name">
<a :href="`/detail/${product.id}`" :to="`/detail/${product.id}`">
{{ product.name }}
{{ product.title }}
</a>
</h3>
<div class="product-actions">

14
src/libs/utils.js

@ -55,24 +55,12 @@ export default {
let para = arrUrl[1];
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) {
if(!img) return;
if (img.indexOf('https://') != -1 || img.indexOf('http://') != -1) {
return img;
} else {
return 'https://changshu.js-dyyj.com' + img;
return 'https://static.ticket.sz-trip.com' + img;
}
},
gotoPath(path) {

2
src/main.js

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

13
src/store/index.js

@ -33,7 +33,8 @@ export default new Vuex.Store({
// 分类数据
categories: [],
// 全局加载状态
loading: false
loading: false,
searchText: '' // 存储搜索词
},
mutations: {
// 更新分类数据
@ -108,6 +109,10 @@ export default new Vuex.Store({
state.cart.totalPrice = state.cart.items.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0)
},
setSearchText(state, text) {
state.searchText = text; // 修改搜索词
}
},
actions: {
@ -197,6 +202,10 @@ export default new Vuex.Store({
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"
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
:src="img"
:alt="`商品图片${index + 1}`"
@ -24,7 +24,7 @@
<!-- 自定义图片指示器 -->
<div class="image-indicators">
<div
v-for="(img, index) in productImages"
v-for="(img, index) in info.listimg"
:key="index"
class="indicator-item"
:class="{ active: activeIndex === index }"
@ -38,19 +38,26 @@
<!-- 右侧商品信息区域保持不变 -->
<div class="right-section">
<h2 class="product-title">
{{ productTitle }}
<span class="product-count">[{{ productCount }}]</span>
{{ info.title }}
<!-- <span class="product-count">[{{ productCount }}]</span> -->
</h2>
<div class="product-tags">
<el-tag type="info" size="mini">[产品标签]</el-tag>
<span class="subtitle">{{ productSubtitle }}</span>
<el-tag
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 class="price-info">
<span class="price-label">售价</span>
<span class="price-amount">¥{{ productPrice }}</span>
<span class="sales-volume">已售 {{ salesVolume }}</span>
<span class="price-amount">¥{{ info.price / 100 }}</span>
<span class="sales-volume">已售 {{ info.sales_number }}</span>
</div>
<div class="product-attr">
@ -68,20 +75,20 @@
<span class="attr-label">商品规格</span>
<div class="custom-radio-group">
<label
v-for="(spec, idx) in productSpecs"
v-for="(spec, idx) in info.sku"
:key="idx"
class="custom-radio"
:class="{ 'is-checked': selectedSpec === spec }"
@click="selectedSpec = spec"
:class="{ 'is-checked': selectedSpec === idx }"
@click="selectedSpec = idx"
>
<span class="radio-text">{{ spec }}</span>
<span class="radio-text">{{ spec.sku_name }}</span>
</label>
</div>
</div>
<div class="attr-item">
<span class="attr-label">发货地</span>
<span class="attr-value">{{ origin }}</span>
<span class="attr-value">{{ info.supplier_address }}</span>
</div>
<div class="attr-item">
@ -151,7 +158,11 @@
</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" />
@ -169,23 +180,13 @@ export default {
},
data() {
return {
productImages: [
"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",
],
info: {},
activeIndex: 0, //
productTitle: "面包",
productCount: "52个",
productSubtitle: "副标题",
productPrice: 509,
salesVolume: 1.22,
moq: 1,
deliveryMethod: "邮寄",
productSpecs: ["规格一", "规格二", "规格三", "规格四", "规格五"],
selectedSpec: "规格一",
origin: "江苏省苏州市吴中区",
selectedSpec: 0,
otherInfo: "下单填写留言,即免费赠送精美贺卡!",
deliveryRange: "全国(可配送至全国1000多个城市,苏州市区内免配送费)",
quantity: 1,
@ -257,6 +258,13 @@ export default {
if (!this.$refs.carousel) {
console.warn("轮播组件未正确加载,请检查ref属性是否设置");
}
this.get(
{ id: this.$route.params.id },
"/api/product/get_product_detail"
).then((res) => {
this.info = res.data;
});
},
};
</script>
@ -341,7 +349,6 @@ export default {
margin-bottom: 15px;
.subtitle {
margin-left: 5px;
color: #666;
}
}

56
src/views/Index.vue

@ -1,20 +1,20 @@
<template>
<div class="bg">
<HomeLayout />
<HomeLayout :topBanner="topBanner" :tagList="tagList" />
<div class="product-box">
<h2>今日推荐</h2>
<ProductList :products="newProducts" />
<ProductList :products="prouctList[0].list" />
</div>
<div class="product-box">
<h2>热销排行</h2>
<ProductList :products="newProducts" />
<ProductList :products="prouctList[1].list" />
</div>
<div class="product-box">
<h2>新品上市</h2>
<ProductList :products="newProducts" />
<ProductList :products="prouctList[2].list" />
</div>
</div>
</template>
@ -31,6 +31,24 @@ export default {
data() {
return {
topBanner: [],
tagList: [],
prouctList: [
{
id: 670,
title: "今日推荐",
list: [],
},
{
id: 671,
title: "热销排行",
list: [],
},
{
id: 672,
title: "新品上市",
list: [],
},
],
newProducts: [
{
id: 7,
@ -77,10 +95,12 @@ export default {
},
created() {
this.getTopBanner();
this.getTags();
this.getProductList();
},
methods: {
// banner
getTopBanner() {
// banner
this.post(
{
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>

24
src/views/Login.vue

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

514
src/views/ProductPage/Index.vue

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