feat: 公共页头添加购物车面板

merge-requests/41/head
xiaoguang 2 years ago
parent 191f13df5a
commit a184f61263

@ -34,4 +34,20 @@ table {border-collapse: collapse; border-spacing: 0;
a { text-decoration:none;
color: #333;
&:hover { text-decoration:none; color: #FF512B;}
}
}
/* 自定义滚动条样式 */
.scrollbar-self {
&::-webkit-scrollbar {
width: 4px;
background-color: none;
}
&::-webkit-scrollbar-track {
background-color: none;
}
&::-webkit-scrollbar-thumb {
background: #dddddd;
border-radius: 10px;
}
}

@ -8,13 +8,7 @@
<template>
<div class="layout">
<BsLogin :visible.sync="loginVisible" />
<Header
:is-categroy-open="isHomePage"
:hide-bar-line="isHideBarLinePage"
:hide-sticky-shadow="isHideSeckillPage"
:show-categroy-tab="showCategroyTab"
:is-sticky="isSticky"
/>
<Header :is-sticky="isSticky" />
<Nuxt />
<Footer />
</div>
@ -23,7 +17,6 @@
import BsLogin from "@/components/BsLogin.vue";
import Header from "./module/header/index.vue";
import Footer from "./module/footer/index.vue";
const CATEGROY_HIDE_PAGES = [/\/account/]; // tab
export default {
name: "Layout",
@ -43,20 +36,6 @@ export default {
this.$store.commit("setLoginVisible", val);
},
},
isHomePage() {
return this.$route.path === "/";
},
isHideBarLinePage() {
return ["/", "/seckill"].includes(this.$route.path);
},
isHideSeckillPage() {
return this.$route.path === "/seckill";
},
showCategroyTab() {
return !CATEGROY_HIDE_PAGES.some((reg) => {
return reg.test(this.$route.path);
});
},
},
mounted() {
//

@ -0,0 +1,263 @@
<template>
<!-- 购物车 -->
<el-popover
popper-class="header-cart-popover"
trigger="hover"
placement="bottom"
width="330"
>
<div
slot="reference"
class="header-cart-popover__refrence flex flex-middle"
@click="onJumpCart"
>
<img src="~/assets/img/layout/icon-shop.png" />
<span>购物车</span>
<div v-if="cartProducts.length > 0" class="wrap-right-cart__tip">
{{ cartProducts.length }}
</div>
</div>
<div class="header-cart-products scrollbar-self">
<div
v-for="item in products"
:key="item.id"
@click="onJumpGoodsDetail"
class="header-cart-products__wrap flex flex-middle flex-between"
>
<div class="cart-products-wrap__left flex felx-middle">
<div class="products-wrap-left__cover">
<img :src="item.productMainPicture" />
</div>
<div class="products-wrap-left__info">
<p class="wrap-left-info__title">{{ item.productName }}</p>
<div class="wrap-left-info__detail flex">
<span class="left-info-detail__skuname">{{
item.productSku.name
}}</span>
<span class="left-info-detail__count">{{ item.number }}</span>
</div>
</div>
</div>
<UiMoney
class="cart-products-wrap__right"
:float="true"
:money="item.product.startingPrice"
/>
</div>
<!-- 失效商品 -->
<template v-if="failureProducts.length > 0">
<div class="header-cart-products__bar">以下商品已失效</div>
<div
v-for="item in failureProducts"
:key="item.id"
@click="onJumpGoodsDetail"
class="header-cart-products__wrap flex flex-middle flex-between"
>
<div class="cart-products-wrap__left flex felx-middle">
<div class="products-wrap-left__cover">
<img :src="item.productMainPicture" />
</div>
<div class="products-wrap-left__info">
<p
class="wrap-left-info__title header-cart-products--failure-color"
>
{{ item.productName }}
</p>
<div class="wrap-left-info__detail flex">
<span class="left-info-detail__skuname">{{
item.productSku && item.productSku.name
}}</span>
<span class="left-info-detail__count">{{ item.number }}</span>
</div>
</div>
</div>
<UiMoney
class="header-cart-products--failure-color"
:float="true"
:money="item.product.startingPrice"
/>
</div>
</template>
</div>
<div class="header-cart-bottom flex flex-middle flex-between">
<p>{{ cartProducts.length }}件商品</p>
<UiButton type="red_panel" :radius="true" @click="onJumCartPage"
>去购物车</UiButton
>
</div>
</el-popover>
</template>
<script>
import { mapState } from "vuex";
import UiButton from "@/components/UiButton.vue";
export default {
name: "HeaderCart",
components: { UiButton },
data() {
return {
products: [],
failureProducts: [], //
};
},
computed: {
...mapState(["cartProducts"]),
},
watch: {
cartProducts: {
immediate: true,
deep: true,
handler(val) {
this.products = [];
this.failureProducts = [];
val.forEach((item) => {
if (item.product.isEnable) {
if (item.productSku && item.productSku.stock > 0) {
//
this.products.push(item);
return;
}
this.failureProducts.push(item);
return;
}
this.failureProducts.push(item);
});
},
},
},
created() {
this.$store.dispatch("getCartProducts");
},
methods: {
onJumpCart() {
if (!this.$isLoginValidate()) {
return;
}
this.$router.push("/cart");
},
onJumpGoodsDetail(id) {
this.$router.push(`/goods/detail/${id}`);
},
onJumCartPage() {
this.$router.push("/cart");
},
},
};
</script>
<style lang="scss">
.header-cart-popover {
padding: 16px 20px;
}
</style>
<style lang="scss" scoped>
.header-cart-popover__refrence {
padding: 0 18px;
height: 42px;
line-height: 42px;
color: #999999;
border-radius: 8px 8px 8px 8px;
border: 1px solid #eeeeee;
cursor: pointer;
.wrap-right-cart__tip {
min-width: 14px;
height: 14px;
padding: 0 3px;
font-size: 10px;
line-height: 14px;
text-align: center;
background: #ff512b;
border-radius: 50%;
color: #ffffff;
}
img {
width: 16px;
height: 16px;
margin: 0 4px 0 10px;
}
}
.header-cart-popover {
padding: 20px 16px;
.header-cart-products {
padding: 0 10px 50px 0;
max-height: 360px;
overflow: auto;
&--failure-color {
color: #999999 !important;
}
.header-cart-products__wrap {
margin-bottom: 20px;
cursor: pointer;
.cart-products-wrap__left {
.products-wrap-left__cover {
width: 54px;
height: 54px;
padding: 3px;
border: 1px solid #eeeeee;
border-radius: 4px;
margin-right: 11px;
img {
width: 100%;
height: 100%;
}
}
.products-wrap-left__info {
font-size: 12px;
color: #999999;
.wrap-left-info__title {
display: block;
width: 120px;
@include ellipsis;
font-size: 14px;
color: #333333;
margin-bottom: 10px;
}
.wrap-left-info__detail {
.left-info-detail__skuname {
display: block;
width: 70px;
@include ellipsis;
}
.left-info-detail__count {
&::before {
content: "X";
font-size: 8px;
}
}
}
}
}
.cart-products-wrap__right {
color: #ff512b;
}
}
.header-cart-products__bar {
font-size: 14px;
width: 298px;
height: 40px;
line-height: 40px;
padding: 0 11px;
background: #f8f8f8;
color: #999999;
margin-bottom: 20px;
}
}
.header-cart-bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
background: #eeeeee;
padding: 0 16px;
font-size: 14px;
color: #666666;
/deep/.ui-button {
width: 84px;
height: 30px;
font-size: 14px;
font-weight: normal;
padding: 0;
}
}
}
</style>

@ -0,0 +1,219 @@
<template>
<div class="header-category">
<!-- 热门分类 -->
<div
v-show="showCategroyTab"
class="header-box-tab__category"
@mouseenter="handleCategoryChange(true)"
@mouseleave="handleCategoryChange(false)"
>
<div class="tab-category__label flex flex-center flex-middle">
<img src="~/assets/img/layout/icon-category.png" />
<span>热门分类</span>
</div>
<div
v-show="isCategroyOpen || categroyVisible"
class="tab-category__menu flex"
@mouseenter="handleCategoryTwoChange(true)"
@mouseleave="handleCategoryTwoChange(false)"
>
<!-- 左侧一级分类 -->
<div class="tab-category-menu__left">
<div
v-for="item in categroyData"
:key="item.id"
@mouseenter="handleCategoryHover(item.id)"
@click="onCategoryClick(item.id, CATEGROY_LEVEL.ONE)"
class="menu-left__item flex flex-middle"
:class="{
'menu-left__item--light': item.id === currentCategroyId,
}"
>
<img />
<span>{{ item.name }}</span>
</div>
</div>
<!-- 右侧二级分类 -->
<div
v-show="categroyTwoVisible"
class="tab-category-menu__right flex-1"
>
<div
v-for="item in categroyData"
:key="item.id"
@mouseenter="handleCategoryHover(item.id)"
class="category-menu-right__wrap"
:class="{
'category-menu-right__wrap--light': item.id === currentCategroyId,
}"
>
<span
v-for="itemList in item.list"
:key="itemList.id"
class="menu-right-wrap__item"
@click="onCategoryClick(itemList.id, CATEGROY_LEVEL.TWO)"
>{{ itemList.name }}</span
>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
ApiGetCategoryOneList,
ApiGetCategoryTwoAndGoods,
} from "@/plugins/api/goods";
const CATEGROY_HIDE_PAGES = [/\/account/]; // tab
export default {
name: "HeaderCategory",
data() {
return {
categroyTwoVisible: false, //
categroyVisible: false, //
currentCategroyId: 0, // id
categroyData: [],
};
},
computed: {
showCategroyTab() {
return !CATEGROY_HIDE_PAGES.some((reg) => {
return reg.test(this.$route.path);
});
},
//
isCategroyOpen() {
return this.$route.path === "/";
},
},
created() {
this.getCategroyData();
},
methods: {
//
async getCategroyData() {
const { result } = await ApiGetCategoryOneList();
if (result && result.length > 0) {
this.categroyData = await Promise.all(
result.map(async (item) => {
const { result: resultGoods } = await ApiGetCategoryTwoAndGoods({
categoryId: item.id,
});
if (resultGoods && resultGoods.length > 0) {
return {
...item,
list: resultGoods,
};
}
})
);
}
},
//
handleCategoryHover(id) {
this.currentCategroyId = id;
},
//
onCategoryClick(id, levelType) {
this.categroyVisible = false;
this.categroyTwoVisible = false;
this.$router.push({
path: "/goods/list",
query: {
id,
levelType,
},
});
},
//
handleCategoryChange(val) {
this.categroyVisible = val;
if (!val) {
this.currentCategroyId = 0;
}
},
//
handleCategoryTwoChange(val) {
this.categroyTwoVisible = val;
},
},
};
</script>
<style lang="scss" scoped>
.header-category {
height: 100%;
.header-box-tab__category {
position: relative;
height: 100%;
.tab-category__label {
width: 190px;
height: 100%;
background: linear-gradient(270deg, #ffa25a 0%, #ff7f39 100%);
border-radius: 4px 4px 0px 0px;
font-weight: bold;
color: #ffffff;
cursor: pointer;
img {
width: 20px;
height: 20px;
margin-right: 16px;
}
}
.tab-category__menu {
position: absolute;
top: 38px;
left: 0;
font-size: 14px;
color: #333333;
.tab-category-menu__left {
width: 190px;
padding: 15px 0;
background: #ffffff;
.menu-left__item {
height: 50px;
cursor: pointer;
padding: 0 24px 0 41px;
&:hover,
&--light {
color: #ff875b;
}
img {
width: 20px;
height: 20px;
margin-right: 16px;
}
}
}
.tab-category-menu__right {
padding: 15px 26px;
box-shadow: 7px 0px 10px 1px rgba(0, 0, 0, 0.1);
border: 1px solid #eeeeee;
background: #ffffff;
.category-menu-right__wrap {
height: 50px;
line-height: 50px;
padding: 0 16px;
font-size: 12px;
color: #999999;
white-space: nowrap;
&:hover,
&--light {
background: #f8f8f8;
}
.menu-right-wrap__item {
color: #999999;
margin-right: 20px;
cursor: pointer;
&:hover {
color: #ff875b;
}
}
}
}
}
}
}
</style>

@ -155,13 +155,18 @@ export default {
};
</script>
<style lang="scss">
.header-info-bar__dropdown,
.header-info-bar__popover {
.popper__arrow {
display: none;
}
}
</style>
<style lang="scss" scoped>
.header-info-bar__dropdown {
width: 200px;
margin-top: 0 !important;
padding-bottom: 0;
.popper__arrow {
display: none;
}
.el-dropdown-menu__item:hover {
background: #f8f9fb;
color: #666666;
@ -203,12 +208,7 @@ export default {
margin-bottom: 15px;
}
}
.popper__arrow {
display: none !important;
}
}
</style>
<style lang="scss" scoped>
.info-bar-header {
height: 30px;
color: #999999;

@ -4,7 +4,7 @@
<div v-show="isSticky">
<div
class="sticky-bar-header"
:class="{ 'sticky-bar-header--hide-shadow': hideStickyShadow }"
:class="{ 'sticky-bar-header--hide-shadow': hideBarShadow }"
>
<div class="sticky-bar-header__wrap flex flex-middle flex-between">
<div class="flex flex-middle">
@ -77,78 +77,12 @@
<img src="~/assets/img/layout/icon-search.png" />
</div>
</div>
<div
class="box-wrap-right__cart flex flex-middle"
@click="onJumpCart"
>
<img src="~/assets/img/layout/icon-shop.png" />
<span>购物车</span>
<div v-if="cartCount > 0" class="wrap-right-cart__tip">
{{ cartCount }}
</div>
</div>
<!-- 购物车 -->
<HeaderCart />
</div>
</div>
<!-- 热门分类 -->
<div class="bar-header-box__tab flex flex-middle">
<div
v-show="showCategroyTab"
class="header-box-tab__category"
@mouseenter="handleCategoryChange(true)"
@mouseleave="handleCategoryChange(false)"
>
<div class="tab-category__label flex flex-center flex-middle">
<img src="~/assets/img/layout/icon-category.png" />
<span>热门分类</span>
</div>
<div
v-show="isCategroyOpen || categroyVisible"
class="tab-category__menu flex"
@mouseenter="handleCategoryTwoChange(true)"
@mouseleave="handleCategoryTwoChange(false)"
>
<!-- 左侧一级分类 -->
<div class="tab-category-menu__left">
<div
v-for="item in categroyData"
:key="item.id"
@mouseenter="handleCategoryHover(item.id)"
@click="onCategoryClick(item.id, CATEGROY_LEVEL.ONE)"
class="menu-left__item flex flex-middle"
:class="{
'menu-left__item--light': item.id === currentCategroyId,
}"
>
<img />
<span>{{ item.name }}</span>
</div>
</div>
<!-- 右侧二级分类 -->
<div
v-show="categroyTwoVisible"
class="tab-category-menu__right flex-1"
>
<div
v-for="item in categroyData"
:key="item.id"
@mouseenter="handleCategoryHover(item.id)"
class="category-menu-right__wrap"
:class="{
'category-menu-right__wrap--light':
item.id === currentCategroyId,
}"
>
<span
v-for="itemList in item.list"
:key="itemList.id"
class="menu-right-wrap__item"
@click="onCategoryClick(itemList.id, CATEGROY_LEVEL.TWO)"
>{{ itemList.name }}</span
>
</div>
</div>
</div>
</div>
<HeaderCategory />
<div
v-for="item in tabList"
:key="item.value"
@ -170,58 +104,28 @@
</template>
<script>
import { mapState } from "vuex";
import {
ApiGetCategoryOneList,
ApiGetCategoryTwoAndGoods,
} from "@/plugins/api/goods";
import { ApiGetCartList } from "@/plugins/api/cart";
import { CATEGROY_LEVEL } from "@/constants";
import HeaderInfoBar from "./HeaderInfoBar.vue";
import HeaderCategory from "./HeaderCategory.vue";
import HeaderCart from "./HeaderCart.vue";
export default {
name: "DefaultHeader",
components: { HeaderInfoBar },
components: { HeaderInfoBar, HeaderCategory, HeaderCart },
props: {
//
isSticky: {
type: Boolean,
default: false,
},
//
isCategroyOpen: {
type: Boolean,
default: false,
},
// tab
showCategroyTab: {
type: Boolean,
default: true,
},
//
hideBarLine: {
type: Boolean,
default: false,
},
// tab
hideStickyShadow: {
type: Boolean,
default: true,
},
},
data() {
return {
CATEGROY_LEVEL,
searchContent: "",
tabPath: "/",
categroyTwoVisible: false, //
categroyVisible: false, //
currentCategroyId: 0, // id
categroyData: [],
cartCount: 0, //
cartProductList: [], //
};
},
computed: {
@ -239,6 +143,16 @@ export default {
}
return defaultList;
},
// tab
hideBarShadow() {
return ["/seckill"].includes(this.$route.path);
},
//
hideBarLine() {
return ["/", "/seckill"].includes(this.$route.path);
},
},
watch: {
"$route.path"(val) {
@ -247,79 +161,15 @@ export default {
}
},
},
created() {
this.getCategroyData();
this.getCartInfo();
},
methods: {
onLoginClick() {
this.$isLoginValidate();
},
//
handleCategoryHover(id) {
this.currentCategroyId = id;
},
//
onCategoryClick(id, levelType) {
this.categroyVisible = false;
this.categroyTwoVisible = false;
this.$router.push({
path: "/goods/list",
query: {
id,
levelType,
},
});
},
//
handleCategoryChange(val) {
this.categroyVisible = val;
if (!val) {
this.currentCategroyId = 0;
}
},
//
handleCategoryTwoChange(val) {
this.categroyTwoVisible = val;
},
onTabSelect(value) {
this.tabPath = value;
this.searchContent = "";
this.$router.push({ path: value });
},
onJumpCart() {
if (!this.$isLoginValidate()) {
return;
}
this.$router.push("/cart");
},
//
async getCartInfo() {
const { result } = await ApiGetCartList();
if (result) {
this.cartCount = result.number;
}
},
//
async getCategroyData() {
const { result } = await ApiGetCategoryOneList();
if (result && result.length > 0) {
this.categroyData = await Promise.all(
result.map(async (item) => {
const { result: resultGoods } = await ApiGetCategoryTwoAndGoods({
categoryId: item.id,
});
if (resultGoods && resultGoods.length > 0) {
return {
...item,
list: resultGoods,
};
}
})
);
}
},
onSearch() {
this.$router.push({
path: "/goods/list",
@ -332,6 +182,10 @@ export default {
};
</script>
<style lang="scss" scoped>
.layout-header-popover {
&__cart-content {
}
}
.sticky-bar-header {
position: fixed;
top: 0;
@ -455,105 +309,10 @@ export default {
}
}
}
.box-wrap-right__cart {
padding: 0 18px;
height: 42px;
line-height: 42px;
color: #999999;
border-radius: 8px 8px 8px 8px;
border: 1px solid #eeeeee;
cursor: pointer;
.wrap-right-cart__tip {
min-width: 14px;
height: 14px;
padding: 0 3px;
font-size: 10px;
line-height: 14px;
text-align: center;
background: #ff512b;
border-radius: 50%;
color: #ffffff;
}
img {
width: 16px;
height: 16px;
margin: 0 4px 0 10px;
}
}
}
}
.bar-header-box__tab {
height: 38px;
.header-box-tab__category {
position: relative;
height: 100%;
.tab-category__label {
width: 190px;
height: 100%;
background: linear-gradient(270deg, #ffa25a 0%, #ff7f39 100%);
border-radius: 4px 4px 0px 0px;
font-weight: bold;
color: #ffffff;
cursor: pointer;
img {
width: 20px;
height: 20px;
margin-right: 16px;
}
}
.tab-category__menu {
position: absolute;
top: 38px;
left: 0;
font-size: 14px;
color: #333333;
.tab-category-menu__left {
width: 190px;
padding: 15px 0;
background: #ffffff;
.menu-left__item {
height: 50px;
cursor: pointer;
padding: 0 24px 0 41px;
&:hover,
&--light {
color: #ff875b;
}
img {
width: 20px;
height: 20px;
margin-right: 16px;
}
}
}
.tab-category-menu__right {
padding: 15px 26px;
box-shadow: 7px 0px 10px 1px rgba(0, 0, 0, 0.1);
border: 1px solid #eeeeee;
background: #ffffff;
.category-menu-right__wrap {
height: 50px;
line-height: 50px;
padding: 0 16px;
font-size: 12px;
color: #999999;
white-space: nowrap;
&:hover,
&--light {
background: #f8f8f8;
}
.menu-right-wrap__item {
color: #999999;
margin-right: 20px;
cursor: pointer;
&:hover {
color: #ff875b;
}
}
}
}
}
}
.header-box-tab__common--light {
color: #ff7f39 !important;
}

@ -18,7 +18,10 @@
</div>
<!-- 有物流信息 -->
<div v-else v-loading="loading">
<div v-infinite-scroll="handleListload" class="home-logisitcs-content">
<div
v-infinite-scroll="handleListload"
class="home-logisitcs-content scrollbar-self"
>
<div
v-for="item in list"
:key="item.orderId"
@ -165,17 +168,6 @@ export default {
max-height: 620px;
overflow: auto;
padding: 30px;
&::-webkit-scrollbar {
width: 4px;
background-color: none;
}
&::-webkit-scrollbar-track {
background-color: none;
}
&::-webkit-scrollbar-thumb {
background: #dddddd;
border-radius: 10px;
}
.home-logisitcs-content__item {
margin-bottom: 30px;
cursor: pointer;

@ -7,6 +7,7 @@
*/
import { TOKEN_KEY } from "@/constants";
import { ApiGetCurrentUser, ApiPostLogout } from "@/plugins/api/account";
import { ApiGetCartList } from "@/plugins/api/cart";
const ONE_DAY = 86400000; // 一天的毫秒数 24 * 60 * 60 * 1000;
const state = () => ({
@ -14,6 +15,7 @@ const state = () => ({
userInfo: {},
loginVisible: false, // 是否展示登录弹窗
seckillTabVisible: false, // 公共头是否展示秒杀tab
cartProducts: [], // 购物车列表
});
const mutations = {
setUserInfo(state, info) {
@ -37,6 +39,9 @@ const mutations = {
setSeckillTabVisible(state, visible) {
state.seckillTabVisible = visible;
},
setCartProducts(state, val) {
state.cartProducts = val;
},
};
const actions = {
async nuxtServerInit({ state, commit, dispatch }) {
@ -58,6 +63,14 @@ const actions = {
await ApiPostLogout();
commit("setLoginOut");
},
// 获取购物车数据
async getCartProducts({ commit }) {
const { result } = await ApiGetCartList();
if (result) {
commit("setCartProducts", result);
}
},
};
export { state, mutations, actions };

Loading…
Cancel
Save