Feature/task1.0.0 xg See merge request yanxuan-frontend/shop-pc!41merge-requests/42/head
@ -0,0 +1,16 @@
|
||||
{
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
@ -0,0 +1,94 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.history/
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
env.js
|
@ -0,0 +1,2 @@
|
||||
registry=https://registry.npm.taobao.org/
|
||||
sass_binary_site="https://npm.taobao.org/mirrors/node-sass"
|
@ -0,0 +1,12 @@
|
||||
FROM node:12.13.1
|
||||
WORKDIR /workload
|
||||
|
||||
COPY nuxt.config.js /workload/nuxt.config.js
|
||||
COPY package.json /workload/package.json
|
||||
COPY .nuxt /workload/.nuxt
|
||||
|
||||
RUN npm config set registry https://registry.npm.taobao.org \
|
||||
&& npm install
|
||||
|
||||
EXPOSE 3000
|
||||
CMD npm run start
|
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 928 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 763 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 812 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 904 B |
After Width: | Height: | Size: 689 B |
After Width: | Height: | Size: 671 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 728 B |
After Width: | Height: | Size: 369 B |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 928 B |
After Width: | Height: | Size: 375 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 133 B |
After Width: | Height: | Size: 135 B |
After Width: | Height: | Size: 135 B |
After Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 553 B |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 215 B |
After Width: | Height: | Size: 567 B |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 372 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 815 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 706 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 710 B |
@ -0,0 +1,7 @@
|
||||
/* 改变主题色变量 */
|
||||
$--color-primary: #FF512B;
|
||||
|
||||
/* 改变 icon 字体路径变量,必需 */
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import "~element-ui/packages/theme-chalk/src/index";
|
@ -0,0 +1,118 @@
|
||||
|
||||
/* ========================================================================
|
||||
Component: Flex
|
||||
========================================================================== */
|
||||
|
||||
.flex { display: flex; }
|
||||
.flex-inline { display: inline-flex; }
|
||||
|
||||
/*
|
||||
* Remove pseudo elements created by micro clearfix as precaution
|
||||
*/
|
||||
|
||||
.flex::before,
|
||||
.flex::after,
|
||||
.flex-inline::before,
|
||||
.flex-inline::after { display: none; }
|
||||
|
||||
|
||||
/* Alignment
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Align items along the main axis of the current line of the flex container
|
||||
* Row: Horizontal
|
||||
*/
|
||||
|
||||
// Default
|
||||
.flex-left { justify-content: flex-start; }
|
||||
.flex-center { justify-content: center; }
|
||||
.flex-right { justify-content: flex-end; }
|
||||
.flex-between { justify-content: space-between; }
|
||||
.flex-around { justify-content: space-around; }
|
||||
|
||||
|
||||
/*
|
||||
* Align items in the cross axis of the current line of the flex container
|
||||
* Row: Vertical
|
||||
*/
|
||||
|
||||
// Default
|
||||
.flex-stretch { align-items: stretch; }
|
||||
.flex-top { align-items: flex-start; }
|
||||
.flex-middle { align-items: center; }
|
||||
.flex-bottom { align-items: flex-end; }
|
||||
.flex-baseline { align-items: baseline; }
|
||||
|
||||
|
||||
/* Direction
|
||||
========================================================================== */
|
||||
|
||||
// Default
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-row-reverse { flex-direction: row-reverse; }
|
||||
.flex-column { flex-direction: column; }
|
||||
.flex-column-reverse { flex-direction: column-reverse; }
|
||||
|
||||
|
||||
/* Wrap
|
||||
========================================================================== */
|
||||
|
||||
// Default
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-wrap-reverse { flex-wrap: wrap-reverse; }
|
||||
|
||||
/*
|
||||
* Aligns items within the flex container when there is extra space in the cross-axis
|
||||
* Only works if there is more than one line of flex items
|
||||
*/
|
||||
|
||||
// Default
|
||||
.flex-wrap-stretch { align-content: stretch; }
|
||||
.flex-wrap-top { align-content: flex-start; }
|
||||
.flex-wrap-middle { align-content: center; }
|
||||
.flex-wrap-bottom { align-content: flex-end; }
|
||||
.flex-wrap-between { align-content: space-between; }
|
||||
.flex-wrap-around { align-content: space-around; }
|
||||
|
||||
|
||||
/* Item ordering
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Default is 0
|
||||
*/
|
||||
|
||||
.flex-first { order: -1;}
|
||||
.flex-last { order: 99;}
|
||||
|
||||
|
||||
/* Item dimensions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Initial: 0 1 auto
|
||||
* Content dimensions, but shrinks
|
||||
*/
|
||||
|
||||
/*
|
||||
* No Flex: 0 0 auto
|
||||
* Content dimensions
|
||||
*/
|
||||
|
||||
.flex-none { flex: none; }
|
||||
|
||||
/*
|
||||
* Relative Flex: 1 1 auto
|
||||
* Space is allocated considering content
|
||||
*/
|
||||
|
||||
.flex-auto { flex: auto; }
|
||||
|
||||
/*
|
||||
* Absolute Flex: 1 1 0%
|
||||
* Space is allocated solely based on flex
|
||||
*/
|
||||
|
||||
.flex-1 { flex: 1; }
|
@ -0,0 +1,53 @@
|
||||
|
||||
@import './flex.scss';
|
||||
@import './util.scss';
|
||||
|
||||
* {
|
||||
-webkit-box-sizing: border-box; box-sizing: border-box;
|
||||
|
||||
-webkit-touch-callout:none; /*系统默认菜单被禁用*/
|
||||
-webkit-user-select:none; /*webkit浏览器*/
|
||||
-khtml-user-select:none; /*早期浏览器*/
|
||||
-moz-user-select:none;/*火狐*/
|
||||
-ms-user-select:none; /*IE10*/
|
||||
user-select:none;
|
||||
&:after, &:before {
|
||||
@extend *;
|
||||
}
|
||||
}
|
||||
body, dl, dd, h1, h2, h3, h4, h5, p, figure, form, ul, ol { margin: 0; font-size: 14px;}
|
||||
ul, ol, input, button { padding: 0;}
|
||||
ul, ol, li { list-style: none;list-style-type: none}
|
||||
h1, h2, h3, h4, h5 { font-size: 100%;}
|
||||
input { border: 0; margin: 0;}
|
||||
img { width: 100%;}
|
||||
a, button, input, optgroup, select, textarea, img { outline:none; -webkit-tap-highlight-color: rgba(0,0,0,0); }
|
||||
a, img { -webkit-touch-callout: none; border: 0;}
|
||||
html, body { position: absolute; min-height: 100%; height: 100%; width: 100%;}
|
||||
input, textarea, img, button { vertical-align: middle; outline: none;}
|
||||
body {
|
||||
font-family: PingFang SC, Microsoft YaHei, Helvetica, STHeiTi, sans-serif !important;
|
||||
color: #333; -webkit-font-smoothing: antialiased; font-smoothing: antialiased;
|
||||
overflow-anchor: none}
|
||||
table {border-collapse: collapse; border-spacing: 0;
|
||||
color: #333;}
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
$baseFontSize: 100 !default;
|
||||
/**/
|
||||
|
||||
@mixin adj ($styleName, $value...) {
|
||||
#{$styleName}: $value;
|
||||
-webkit-#{$styleName}: $value;
|
||||
-moz-#{$styleName}: $value;
|
||||
-o-#{$styleName}: $value;
|
||||
-ms-#{$styleName}: $value;
|
||||
}
|
||||
// @mixin transition($value...) {
|
||||
// -webkit-transiton: $value;
|
||||
// -moz-transtion: $value;
|
||||
// -ms-transtion: $value;
|
||||
// transition: $value;
|
||||
// }
|
||||
// @mixin transition-delay($value...) {
|
||||
// transition-delay: $value;
|
||||
// -moz-transition-delay: $value; /* Firefox 4 */
|
||||
// -webkit-transition-delay: $value; /* Safari 和 Chrome */
|
||||
// -o-transition-delay: $value; /* Opera */
|
||||
// }
|
||||
|
||||
// @mixin transform($value...) {
|
||||
// transform: $value;
|
||||
// -webkit-transform: $value;
|
||||
// -moz-transform: $value;
|
||||
// -o-transform: $value;
|
||||
// -ms-transform: $value;
|
||||
// }
|
||||
|
||||
// @mixin animation($value...) {
|
||||
// -webkit-animation: $value;
|
||||
// -moz-animation: $value;
|
||||
// -ms-animation: $value;
|
||||
// animation: $value;
|
||||
// }
|
||||
@mixin keyframes($animationName) {
|
||||
@-webkit-keyframes #{$animationName} {
|
||||
@content;
|
||||
}
|
||||
@-moz-keyframes #{$animationName} {
|
||||
@content;
|
||||
}
|
||||
@-o-keyframes #{$animationName} {
|
||||
@content;
|
||||
}
|
||||
@keyframes #{$animationName} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// @mixin filter($value...) {
|
||||
// -webkit-filter: $value;
|
||||
// -moz-filter: $value;
|
||||
// -ms-filter: $value;
|
||||
// filter: $value;
|
||||
// }
|
||||
|
||||
@mixin linear-gradient($value...) {
|
||||
background: -webkit-linear-gradient($value); /* Safari 5.1 - 6.0 */
|
||||
background: -o-linear-gradient($value); /* Opera 11.1 - 12.0 */
|
||||
background: -moz-linear-gradient($value); /* Firefox 3.6 - 15 */
|
||||
background: linear-gradient(t$value); /* 标准的语法 */
|
||||
}
|
||||
|
||||
// @mixin boxShow($value...) {
|
||||
// -webkit-box-shadow: $value;
|
||||
// -moz-box-shadow: $value;
|
||||
// -ms-box-shadow: $value;
|
||||
// box-shadow: $value;
|
||||
// }
|
||||
|
||||
@function torem($value) {
|
||||
@return ($value / $baseFontSize * 1rem);
|
||||
}
|
||||
|
||||
%overflow-scrolling {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-scrolling: touch;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
%els {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/*内容居中布局*/
|
||||
@mixin layout-box {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/*单行溢出*/
|
||||
@mixin ellipsis {
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap
|
||||
}
|
||||
|
||||
/*多行溢出*/
|
||||
@mixin ellipses($line) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $line;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/*可点击文字hover*/
|
||||
.hover-text:hover {
|
||||
color: #FF875B;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-08 14:41:42
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-12 14:37:00
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<div class="ui-goods-info" @click="$router.push(`/goods/detail/${goods.productId}`)">
|
||||
<div class="ui-goods-info--img">
|
||||
<img :src="goods.productImageUrl || goods.productMainPicture"/>
|
||||
</div>
|
||||
<p>
|
||||
<b>{{goods.productName}}</b>
|
||||
<span>{{goods.skuDescribe}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props : {
|
||||
goods : {
|
||||
type : Object,
|
||||
default : () => ({})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ui-goods-info{
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
&--img{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
p{
|
||||
width: 270px;
|
||||
margin: 7px 0 0 18px;
|
||||
text-align: left;
|
||||
b{
|
||||
display: block;
|
||||
line-height: 22px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
display:-webkit-box;
|
||||
-webkit-box-orient:vertical;
|
||||
-webkit-line-clamp:2;
|
||||
}
|
||||
span{
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="chosen">
|
||||
<div class="chosen-title flex flex-between flex-middle">
|
||||
<h3 class="chosen-title--txt">为你精选</h3>
|
||||
<div class="chosen-title--btn flex" @click="getRecommendedGoodsList()">
|
||||
<img src="@/assets/img/goods/each.png" alt="切换推荐" />
|
||||
<span>换一组</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chosen-list">
|
||||
<UiGoodsItem
|
||||
:item="item"
|
||||
v-for="item in recommendedData"
|
||||
:key="item.id"
|
||||
></UiGoodsItem>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ApiGetRecommendedGoodsList } from "@/plugins/api/goods";
|
||||
import UiGoodsItem from "@/components/UiGoodsItem.vue";
|
||||
export default {
|
||||
components: { UiGoodsItem },
|
||||
data() {
|
||||
return {
|
||||
recommendedData: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getRecommendedGoodsList();
|
||||
},
|
||||
methods: {
|
||||
async getRecommendedGoodsList() {
|
||||
let vm = this;
|
||||
let res = await ApiGetRecommendedGoodsList();
|
||||
vm.recommendedData = res.result;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chosen {
|
||||
width: 100%;
|
||||
padding: 30px 0 40px;
|
||||
background: #f8f8f8;
|
||||
|
||||
&-title {
|
||||
@include layout-box;
|
||||
&--txt {
|
||||
font-size: 24px;
|
||||
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
&--btn {
|
||||
width: 140px;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
span {
|
||||
font-size: 18px;
|
||||
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-list {
|
||||
@include layout-box;
|
||||
padding-top: 40px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 232px);
|
||||
justify-content: space-between;
|
||||
grid-row-gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
width="16%"
|
||||
center
|
||||
:visible.sync="dialogVisible"
|
||||
:show-close="false"
|
||||
class="bs-order-ensure"
|
||||
>
|
||||
<div class="dialog-content flex flex-middle">
|
||||
<img src="~/assets/img/common/icon-warning.png" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<div class="dialog-footer flex flex-between">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button class="dialog-footer__btn--ensure" @click="onConfirm"
|
||||
>确认</el-button
|
||||
>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import UiButton from "./UiButton.vue";
|
||||
export default {
|
||||
components: { UiButton },
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("update:visible", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onConfirm() {
|
||||
this.$emit("confirm");
|
||||
this.dialogVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/deep/.el-dialog {
|
||||
.el-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 41px 60px;
|
||||
.dialog-content {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 16px;
|
||||
padding: 0 2px 42px 2px;
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 17px;
|
||||
}
|
||||
}
|
||||
.dialog-footer {
|
||||
.el-button {
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dialog-footer__btn--ensure {
|
||||
background: #ff875b;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,27 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-09 20:00:57
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-09 20:04:59
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<el-dialog :visible="visible" @close="close">
|
||||
<slot></slot>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible : {
|
||||
type : Boolean,
|
||||
default : false
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
close(...args){
|
||||
this.$emit('close', args)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,37 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-12 10:30:07
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-12 11:01:57
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<div class="ui-empty">
|
||||
<img class="ui-empty--icon" :src="icon"/>
|
||||
<p class="ui-empty--desc">{{desc}}</p>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props : {
|
||||
title : String,
|
||||
desc : String,
|
||||
icon : String
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ui-empty{
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
padding: 80px 0;
|
||||
&--icon{
|
||||
width: 228px;
|
||||
}
|
||||
&--desc{
|
||||
margin: 20px 0;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="goods-item" @click="onItem">
|
||||
<img class="goods-item__img" :src="item.mainPicture" alt="商品图片" />
|
||||
<div class="goods-item__title">
|
||||
<span class="goods-item__title-label" v-if="isLabel(item.labelList)">
|
||||
{{ getLabel(item.labelList) }}
|
||||
</span>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="goods-item__price">
|
||||
<UiMoney :money="item.startingPrice"></UiMoney>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import UiMoney from "@/components/UiMoney.vue";
|
||||
export default {
|
||||
name: "UiGoodsItem",
|
||||
componetns: { UiMoney },
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
isLabel(arr) {
|
||||
return arr.some((item) => item.code);
|
||||
},
|
||||
getLabel(arr) {
|
||||
let str = "";
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].code != "miaosha") {
|
||||
str = arr[i].text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
onItem() {
|
||||
this.$router.push({
|
||||
path: "/goods/detail/" + this.item.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.goods-item {
|
||||
width: 232px;
|
||||
height: 340px;
|
||||
cursor: pointer;
|
||||
background: #ffffff;
|
||||
|
||||
&__img {
|
||||
width: 232px;
|
||||
height: 232px;
|
||||
}
|
||||
&__title {
|
||||
width: 200px;
|
||||
height: 45px;
|
||||
line-height: 22px;
|
||||
margin: 17px auto 10px;
|
||||
font-size: 14px;
|
||||
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
|
||||
font-weight: 400;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
&-label {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: rgba(255, 135, 91, 0.1);
|
||||
font-size: 12px;
|
||||
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
|
||||
font-weight: 400;
|
||||
color: #ff875b;
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
&__price {
|
||||
width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-07 22:40:55
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-10 13:48:39
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<div class="ui-line-box">
|
||||
<div class="ui-line-box--head">
|
||||
<slot name="head">
|
||||
<b class="ui-line-box--title">{{title}}</b>
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props : {
|
||||
title : {
|
||||
type : String,
|
||||
default : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ui-line-box{
|
||||
border:1px solid #ddd;
|
||||
&--head{
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
padding: 0 30px;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
&--title{
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-12 16:52:52
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-12 17:02:52
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<div class="loading" >正在查询数据...</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.loading{
|
||||
height: 300px;
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,10 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-07 22:57:24
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-07 22:57:39
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<el-radio/>
|
||||
</template>
|
@ -0,0 +1,54 @@
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
labels:
|
||||
app: $IMAGES
|
||||
name: $IMAGES
|
||||
namespace: yanxuan
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: $IMAGES
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 25%
|
||||
maxSurge: 25%
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: $IMAGES
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: aliyun-docker-hub
|
||||
containers:
|
||||
- image: '$REGISTRY/$DOCKERHUB_NAMESPACE/$IMAGES:$BUILD_NUMBER'
|
||||
name: app
|
||||
ports:
|
||||
- containerPort: $JAR_PORD
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: '0.5'
|
||||
memory: 500Mi
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 30
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: $IMAGES
|
||||
namespace: yanxuan
|
||||
spec:
|
||||
ports:
|
||||
- port: 3000
|
||||
protocol: TCP
|
||||
targetPort: 3000
|
||||
selector:
|
||||
app: $IMAGES
|
||||
type: ClusterIP
|
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/$1',
|
||||
'^~/(.*)$': '<rootDir>/$1',
|
||||
'^vue$': 'vue/dist/vue.common.js'
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'vue',
|
||||
'json'
|
||||
],
|
||||
transform: {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'.*\\.(vue)$': 'vue-jest'
|
||||
},
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/components/**/*.vue',
|
||||
'<rootDir>/pages/**/*.vue'
|
||||
],
|
||||
testEnvironment: 'jsdom'
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<!--
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-04 17:56:39
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-08 15:53:49
|
||||
* @Description: file content
|
||||
-->
|
||||
<template>
|
||||
<div class="layout">
|
||||
<BsLogin :visible.sync="loginVisible" />
|
||||
<Header :is-sticky="isSticky" />
|
||||
<Nuxt />
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BsLogin from "@/components/BsLogin.vue";
|
||||
import Header from "./module/header/index.vue";
|
||||
import Footer from "./module/footer/index.vue";
|
||||
|
||||
export default {
|
||||
name: "Layout",
|
||||
components: { Header, Footer, BsLogin },
|
||||
data() {
|
||||
return {
|
||||
isSticky: false,
|
||||
ticking: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loginVisible: {
|
||||
get() {
|
||||
return this.$store.state.loginVisible;
|
||||
},
|
||||
set(val) {
|
||||
this.$store.commit("setLoginVisible", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 监听滚动事件
|
||||
window.addEventListener("scroll", this.scrollEventMethod);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("scroll", this.scrollEventMethod);
|
||||
},
|
||||
methods: {
|
||||
scrollEventMethod(e) {
|
||||
const that = this;
|
||||
// 节流
|
||||
if (!that.ticking) {
|
||||
window.requestAnimationFrame(function () {
|
||||
that.ticking = false;
|
||||
that.isSticky = window.scrollY > 300;
|
||||
});
|
||||
that.ticking = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.layout-footer {
|
||||
height: 189px;
|
||||
background: #ddd;
|
||||
}
|
||||
</style>
|
@ -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>
|
@ -0,0 +1,334 @@
|
||||
<template>
|
||||
<div class="layout-header">
|
||||
<!-- 滚动吸顶头部 -->
|
||||
<div v-show="isSticky">
|
||||
<div
|
||||
class="sticky-bar-header"
|
||||
:class="{ 'sticky-bar-header--hide-shadow': hideBarShadow }"
|
||||
>
|
||||
<div class="sticky-bar-header__wrap flex flex-middle flex-between">
|
||||
<div class="flex flex-middle">
|
||||
<img
|
||||
class="bar-header-wrap__logo"
|
||||
src="~/assets/img/layout/logo-sticky.png"
|
||||
/>
|
||||
<el-menu
|
||||
:default-active="tabPath"
|
||||
mode="horizontal"
|
||||
@select="onTabSelect"
|
||||
>
|
||||
<el-menu-item
|
||||
v-for="item in tabList"
|
||||
:key="item.value"
|
||||
:index="item.value"
|
||||
>{{ item.label }}</el-menu-item
|
||||
>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="bar-header-wrap__icons flex flex-middle">
|
||||
<img
|
||||
src="~/assets/img/layout/icon-search-sticky.png"
|
||||
@click="$router.push('/goods/list')"
|
||||
/>
|
||||
<div class="header-wrap-icons__shop" @click="$router.push('/cart')">
|
||||
<img src="~/assets/img/layout/icon-shop-sticky.png" />
|
||||
<span v-if="cartCount > 0" class="">{{ cartCount }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="token"
|
||||
class="header-wrap-icons__login"
|
||||
@click="$router.push('/account/home')"
|
||||
>
|
||||
<img :src="userInfo.avatar" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="header-wrap-icons__unlogin"
|
||||
@click="onLoginClick"
|
||||
>
|
||||
登录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template>
|
||||
<HeaderInfoBar />
|
||||
<div class="default-bar-header">
|
||||
<div class="bar-header-box">
|
||||
<div class="bar-header-box__wrap flex flex-between flex-middle">
|
||||
<img
|
||||
class="header-box-wrap__logo"
|
||||
src="~/assets/img/layout/logo.png"
|
||||
/>
|
||||
<div class="header-box-wrap__right flex flex-middle">
|
||||
<div class="box-wrap-right__search flex">
|
||||
<div class="search-input">
|
||||
<el-input
|
||||
v-model="searchContent"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
></el-input>
|
||||
</div>
|
||||
<div
|
||||
class="search-icon flex flex-center flex-middle"
|
||||
@click="onSearch"
|
||||
>
|
||||
<img src="~/assets/img/layout/icon-search.png" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 购物车 -->
|
||||
<HeaderCart />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bar-header-box__tab flex flex-middle">
|
||||
<HeaderCategory />
|
||||
<div
|
||||
v-for="item in tabList"
|
||||
:key="item.value"
|
||||
class="header-box-tab__common flex flex-center flex-middle"
|
||||
:class="{
|
||||
'header-box-tab__common--light':
|
||||
item.value === $nuxt.$route.fullPath,
|
||||
}"
|
||||
@click="onTabSelect(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hideBarLine" class="layout-header-line"></div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
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, HeaderCategory, HeaderCart },
|
||||
props: {
|
||||
// 是否置顶
|
||||
isSticky: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
CATEGROY_LEVEL,
|
||||
searchContent: "",
|
||||
tabPath: "/",
|
||||
cartCount: 0, // 购物车商品数
|
||||
cartProductList: [], // 购物车列表
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["userInfo", "token", "seckillTabVisible"]),
|
||||
tabList() {
|
||||
const defaultList = [
|
||||
{ label: "首页", value: "/" },
|
||||
{
|
||||
label: "开发书籍",
|
||||
value: `/goods/list?id=6&levelType=${CATEGROY_LEVEL.ONE}`,
|
||||
},
|
||||
];
|
||||
if (this.seckillTabVisible) {
|
||||
return [...defaultList, { label: "限时秒杀", value: "/seckill" }];
|
||||
}
|
||||
return defaultList;
|
||||
},
|
||||
|
||||
// 是否隐藏吸顶tab底部阴影
|
||||
hideBarShadow() {
|
||||
return ["/seckill"].includes(this.$route.path);
|
||||
},
|
||||
|
||||
// 是否隐藏底部黄色边框
|
||||
hideBarLine() {
|
||||
return ["/", "/seckill"].includes(this.$route.path);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"$route.path"(val) {
|
||||
if (val !== "/goods/list") {
|
||||
this.searchContent = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onLoginClick() {
|
||||
this.$isLoginValidate();
|
||||
},
|
||||
onTabSelect(value) {
|
||||
this.tabPath = value;
|
||||
this.searchContent = "";
|
||||
this.$router.push({ path: value });
|
||||
},
|
||||
onSearch() {
|
||||
this.$router.push({
|
||||
path: "/goods/list",
|
||||
query: {
|
||||
keyword: this.searchContent,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.layout-header-popover {
|
||||
&__cart-content {
|
||||
}
|
||||
}
|
||||
.sticky-bar-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
z-index: 10;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 4px 10px 1px rgba(0, 0, 0, 0.1);
|
||||
&--hide-shadow {
|
||||
box-shadow: none;
|
||||
/deep/.el-menu {
|
||||
.is-active {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sticky-bar-header__wrap {
|
||||
@include layout-box;
|
||||
height: 100%;
|
||||
.bar-header-wrap__logo {
|
||||
width: 164px;
|
||||
height: 28px;
|
||||
margin-right: 50px;
|
||||
}
|
||||
.bar-header-wrap__icons {
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.header-wrap-icons__shop {
|
||||
position: relative;
|
||||
margin-left: 14px;
|
||||
span {
|
||||
position: absolute;
|
||||
right: -6px;
|
||||
top: -4px;
|
||||
display: block;
|
||||
height: 14px;
|
||||
padding: 0 2px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
background: #ff512b;
|
||||
font-size: 10px;
|
||||
color: #ffffff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.header-wrap-icons__login {
|
||||
margin-left: 47px;
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.header-wrap-icons__unlogin {
|
||||
font-size: 16px;
|
||||
color: #909399;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
/deep/ .el-menu {
|
||||
height: 50px;
|
||||
color: #666666;
|
||||
.is-active {
|
||||
color: #ff7f39;
|
||||
border-bottom: 3px solid #ff823c;
|
||||
}
|
||||
.el-menu-item:hover {
|
||||
color: #ff7f39;
|
||||
}
|
||||
.el-menu-item {
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
margin: 0 30px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.default-bar-header {
|
||||
padding-top: 32px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
.bar-header-box {
|
||||
@include layout-box;
|
||||
background: #ffffff;
|
||||
.bar-header-box__wrap {
|
||||
height: 42px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 38px;
|
||||
padding-right: 50px;
|
||||
.header-box-wrap__logo {
|
||||
width: 244px;
|
||||
height: 100%;
|
||||
}
|
||||
.header-box-wrap__right {
|
||||
.box-wrap-right__search {
|
||||
margin-right: 23px;
|
||||
.search-input {
|
||||
width: 551px;
|
||||
z-index: 1;
|
||||
/deep/.el-input__inner:focus {
|
||||
border-color: #ff512b;
|
||||
}
|
||||
}
|
||||
.search-icon {
|
||||
width: 77px;
|
||||
margin-left: -2px;
|
||||
background: linear-gradient(270deg, #ffa25a 0%, #ff7f39 100%);
|
||||
border-radius: 0px 8px 8px 0px;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bar-header-box__tab {
|
||||
height: 38px;
|
||||
.header-box-tab__common--light {
|
||||
color: #ff7f39 !important;
|
||||
}
|
||||
.header-box-tab__common {
|
||||
width: 160px;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.layout-header-line {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #ff875b;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* @Author: ch
|
||||
* @Date: 2022-05-03 22:14:16
|
||||
* @LastEditors: ch
|
||||
* @LastEditTime: 2022-05-10 14:18:39
|
||||
* @Description: file content
|
||||
*/
|
||||
export default {
|
||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||
head: {
|
||||
title: 'shop-pc',
|
||||
htmlAttrs: {
|
||||
lang: 'zh'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
|
||||
]
|
||||
},
|
||||
router: {
|
||||
extendRoutes(routes, resolve) {
|
||||
routes.push({
|
||||
name: 'custom',
|
||||
path: '/',
|
||||
component: resolve(__dirname, 'pages/index/index.vue')
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: [
|
||||
'@assets/scss/global.scss',
|
||||
'element-ui/lib/theme-chalk/index.css'
|
||||
],
|
||||
|
||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||
plugins: [
|
||||
'@/plugins/element-ui',
|
||||
'@/plugins/axios',
|
||||
'@plugins/axiosTk.js',
|
||||
'@plugins/vue-inject.js',
|
||||
'@/plugins/v-distpicker',
|
||||
],
|
||||
|
||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||
components: true,
|
||||
|
||||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
|
||||
buildModules: [
|
||||
],
|
||||
|
||||
build: {
|
||||
vendor: ['v-distpicker']
|
||||
},
|
||||
|
||||
styleResources: {
|
||||
scss: '@/assets/scss/global.scss'
|
||||
},
|
||||
|
||||
// Modules: https://go.nuxtjs.dev/config-modules
|
||||
modules: [
|
||||
'@nuxtjs/axios',
|
||||
'cookie-universal-nuxt',
|
||||
'@nuxtjs/style-resources'
|
||||
],
|
||||
|
||||
// Build Configuration: https://go.nuxtjs.dev/config-build
|
||||
build: {
|
||||
transpile: [/^element-ui/],
|
||||
},
|
||||
axios: {
|
||||
// 表示开启代理
|
||||
proxy: true,
|
||||
},
|
||||
proxy: {
|
||||
'/mall/': {
|
||||
// target: 'http://114.55.64.39:3004', // 目标接口域名
|
||||
target: 'https://you-gateway.mashibing.com/', // 目标接口域名
|
||||
pathRewrite: {
|
||||
changeOrigin: true, // 表示是否跨域
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000, // default: 3000
|
||||
host: '0.0.0.0' // default: localhost,
|
||||
},
|
||||
}
|