mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-12 20:08:59 +08:00
feat(buyer): 增强搜索功能并提升用户体验
- 新增 `getStoreById` 接口方法,用于获取包含评分在内的店铺信息 - `Search.vue` 新增搜索建议面板,展示搜索历史和热词,优化搜索体验 - 新增 `SearchSuggest.vue` 组件,用于展示搜索建议和搜索历史 - `App.vue` 根据路由元信息按条件设置页面标题 - 优化多个组件的布局与样式,让整体风格更统一 - 重构多个组件的跳转逻辑,改用路由跳转(`router.push`)替代新开窗口(`window.open`),提升用户体验
This commit is contained in:
@@ -23,7 +23,9 @@ export default {
|
|||||||
storage.setItem("siteIcon", data.buyerSideIcon);
|
storage.setItem("siteIcon", data.buyerSideIcon);
|
||||||
this.$store.commit("SET_SITENAME", data.siteName);
|
this.$store.commit("SET_SITENAME", data.siteName);
|
||||||
this.$store.commit("SET_LOGOIMG", data.buyerSideLogo);
|
this.$store.commit("SET_LOGOIMG", data.buyerSideLogo);
|
||||||
window.document.title = data.siteName;
|
if (this.$route.meta.title === undefined) {
|
||||||
|
window.document.title = data.siteName;
|
||||||
|
}
|
||||||
let link =
|
let link =
|
||||||
document.querySelector("link[rel*='icon']") ||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
document.createElement("link");
|
document.createElement("link");
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ export function applyThird (params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 店铺信息(含评分)
|
||||||
|
export function getStoreById (id) {
|
||||||
|
return request({
|
||||||
|
url: `/buyer/store/store/store/${id}`,
|
||||||
|
needToken: false,
|
||||||
|
method: Method.GET
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 店铺详情
|
// 店铺详情
|
||||||
export function getDetailById (id) {
|
export function getDetailById (id) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -7,11 +7,24 @@
|
|||||||
size="large"
|
size="large"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
placeholder="输入你想查找的商品"
|
placeholder="输入你想查找的商品"
|
||||||
|
@focus="openPanel"
|
||||||
|
@input="queueSuggest"
|
||||||
@keyup.enter="search"
|
@keyup.enter="search"
|
||||||
/>
|
/>
|
||||||
<div class="search-btn" @click="search">
|
<div class="search-btn" @click="search">
|
||||||
<el-icon :size="21"><SearchIcon /></el-icon>
|
<el-icon :size="21"><SearchIcon /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="showPanel" class="suggest-panel" @mousedown.prevent>
|
||||||
|
<SearchSuggest
|
||||||
|
:keyword="trimmedKeyword"
|
||||||
|
:history="history"
|
||||||
|
:hot-words="promotionTags"
|
||||||
|
:suggestions="suggestions"
|
||||||
|
:suggesting="suggesting"
|
||||||
|
@pick="selectTags"
|
||||||
|
@clear="clearHistory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -30,11 +43,24 @@
|
|||||||
size="large"
|
size="large"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
placeholder="输入你想查找的商品"
|
placeholder="输入你想查找的商品"
|
||||||
|
@focus="openPanel"
|
||||||
|
@input="queueSuggest"
|
||||||
@keyup.enter="search"
|
@keyup.enter="search"
|
||||||
/>
|
/>
|
||||||
<div class="search-btn" @click="search">
|
<div class="search-btn" @click="search">
|
||||||
<el-icon :size="21"><SearchIcon /></el-icon>
|
<el-icon :size="21"><SearchIcon /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="showPanel" class="suggest-panel" @mousedown.prevent>
|
||||||
|
<SearchSuggest
|
||||||
|
:keyword="trimmedKeyword"
|
||||||
|
:history="history"
|
||||||
|
:hot-words="promotionTags"
|
||||||
|
:suggestions="suggestions"
|
||||||
|
:suggesting="suggesting"
|
||||||
|
@pick="selectTags"
|
||||||
|
@clear="clearHistory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="showTag">
|
<template v-if="showTag">
|
||||||
<div v-if="promotionTags.length === 0"></div>
|
<div v-if="promotionTags.length === 0"></div>
|
||||||
@@ -56,22 +82,49 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import storage from '@/plugins/storage.js'
|
import storage from '@/plugins/storage.js'
|
||||||
import {hotWords} from '@/api/goods.js'
|
import { goodsList, hotWords } from '@/api/goods.js'
|
||||||
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
||||||
|
import SearchSuggest from '@/components/SearchSuggest.vue'
|
||||||
|
|
||||||
|
const HISTORY_KEY = 'buyerSearchHistory'
|
||||||
|
const HISTORY_LIMIT = 10
|
||||||
|
|
||||||
|
function readHistory() {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(HISTORY_KEY)
|
||||||
|
const list = raw ? JSON.parse(raw) : []
|
||||||
|
return Array.isArray(list) ? list.filter((item) => typeof item === 'string' && item.trim()) : []
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHotWords(raw) {
|
||||||
|
if (!raw) return []
|
||||||
|
if (Array.isArray(raw)) return raw.filter(Boolean)
|
||||||
|
if (typeof raw !== 'string') return []
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw)
|
||||||
|
if (Array.isArray(parsed)) return parsed.filter(Boolean)
|
||||||
|
} catch {
|
||||||
|
// 旧数据可能是逗号拼接
|
||||||
|
}
|
||||||
|
return raw.split(',').map((item) => item.trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'search',
|
name: 'search',
|
||||||
components: { SearchIcon },
|
components: { SearchIcon, SearchSuggest },
|
||||||
props: {
|
props: {
|
||||||
showTag: { // 是否展示搜索栏下方热门搜索
|
showTag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
showLogo: { // 是否展示左侧logo
|
showLogo: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
storeId: { // 是否为店铺页面
|
storeId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
@@ -86,64 +139,148 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchData: '', // 搜索内容
|
searchData: '',
|
||||||
|
history: [],
|
||||||
|
panelVisible: false,
|
||||||
|
suggestions: [],
|
||||||
|
suggesting: false,
|
||||||
|
suggestTimer: null,
|
||||||
|
suggestSeq: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectTags(item) { // 选择热门标签
|
selectTags(item) {
|
||||||
this.searchData = item;
|
this.searchData = item;
|
||||||
this.search();
|
this.search();
|
||||||
},
|
},
|
||||||
search () { // 全平台搜索商品
|
remember(keyword) {
|
||||||
|
const next = [keyword, ...this.history.filter((item) => item !== keyword)].slice(0, HISTORY_LIMIT)
|
||||||
|
this.history = next
|
||||||
|
localStorage.setItem(HISTORY_KEY, JSON.stringify(next))
|
||||||
|
},
|
||||||
|
clearHistory() {
|
||||||
|
this.history = []
|
||||||
|
localStorage.removeItem(HISTORY_KEY)
|
||||||
|
},
|
||||||
|
openPanel() {
|
||||||
|
this.panelVisible = true
|
||||||
|
if (this.trimmedKeyword) this.queueSuggest()
|
||||||
|
},
|
||||||
|
closePanel() {
|
||||||
|
this.panelVisible = false
|
||||||
|
},
|
||||||
|
onDocumentClick(event) {
|
||||||
|
if (this.$el && !this.$el.contains(event.target)) this.closePanel()
|
||||||
|
},
|
||||||
|
queueSuggest() {
|
||||||
|
this.panelVisible = true
|
||||||
|
clearTimeout(this.suggestTimer)
|
||||||
|
const keyword = this.trimmedKeyword
|
||||||
|
if (!keyword) {
|
||||||
|
this.suggestions = []
|
||||||
|
this.suggesting = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.suggesting = true
|
||||||
|
this.suggestTimer = setTimeout(() => this.fetchSuggest(keyword), 300)
|
||||||
|
},
|
||||||
|
fetchSuggest(keyword) {
|
||||||
|
const seq = ++this.suggestSeq
|
||||||
|
const params = { keyword, pageNumber: 1, pageSize: 8 }
|
||||||
|
if (this.storeId) params.storeId = this.storeId
|
||||||
|
goodsList(params).then((res) => {
|
||||||
|
if (seq !== this.suggestSeq || keyword !== this.trimmedKeyword) return
|
||||||
|
const records = res && res.success ? (res.result.records || []) : []
|
||||||
|
const seen = new Set()
|
||||||
|
const names = []
|
||||||
|
this.promotionTags.forEach((word) => {
|
||||||
|
if (word && String(word).toLowerCase().includes(keyword.toLowerCase()) && !seen.has(word)) {
|
||||||
|
seen.add(word)
|
||||||
|
names.push(word)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
records.forEach((item) => {
|
||||||
|
const name = item && item.goodsName
|
||||||
|
if (name && !seen.has(name)) {
|
||||||
|
seen.add(name)
|
||||||
|
names.push(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.suggestions = names.slice(0, 8)
|
||||||
|
}).catch(() => {
|
||||||
|
if (seq === this.suggestSeq) this.suggestions = []
|
||||||
|
}).finally(() => {
|
||||||
|
if (seq === this.suggestSeq) this.suggesting = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
search () {
|
||||||
|
const keyword = this.trimmedKeyword
|
||||||
|
this.searchData = keyword
|
||||||
|
this.closePanel()
|
||||||
|
if (keyword) this.remember(keyword)
|
||||||
const url = this.$route.path;
|
const url = this.$route.path;
|
||||||
if(url == '/goodsList'){
|
if(url == '/goodsList'){
|
||||||
this.$emit('search', this.searchData)
|
this.$emit('search', keyword)
|
||||||
}else{
|
}else{
|
||||||
const pushData = {
|
const pushData = {
|
||||||
path:'/goodsList',
|
path:'/goodsList',
|
||||||
query: { keyword: this.searchData },
|
query: { keyword },
|
||||||
}
|
}
|
||||||
if(this.storeId) pushData.query.storeId = this.storeId
|
if(this.storeId) pushData.query.storeId = this.storeId
|
||||||
|
|
||||||
this.$router.push(pushData);
|
this.$router.push(pushData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchStore() { // 店铺搜索商品
|
searchStore() {
|
||||||
this.$emit('search', this.searchData)
|
this.$emit('search', this.trimmedKeyword)
|
||||||
|
},
|
||||||
|
applyHotWords(result) {
|
||||||
|
const list = parseHotWords(result)
|
||||||
|
this.$store.commit('SET_HOTWORDS', list)
|
||||||
|
storage.setItem('hotWordsList', JSON.stringify(list))
|
||||||
|
},
|
||||||
|
loadHotWords() {
|
||||||
|
hotWords({ count: 5 }).then((res) => {
|
||||||
|
if (res.success && res.result) this.applyHotWords(res.result)
|
||||||
|
}).catch(() => {})
|
||||||
|
storage.setItem('hotWordsReloadTime', new Date().getTime())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
trimmedKeyword() {
|
||||||
|
return (this.searchData || '').trim()
|
||||||
|
},
|
||||||
promotionTags() {
|
promotionTags() {
|
||||||
const raw = this.$store.state.hotWordsList;
|
return parseHotWords(this.$store.state.hotWordsList)
|
||||||
if (!raw) {
|
},
|
||||||
return [];
|
showPanel() {
|
||||||
}
|
if (!this.panelVisible) return false
|
||||||
try {
|
if (this.trimmedKeyword) return true
|
||||||
return typeof raw === "string" ? JSON.parse(raw) : raw;
|
return this.history.length > 0 || this.promotionTags.length > 0
|
||||||
} catch {
|
}
|
||||||
return [];
|
},
|
||||||
}
|
watch: {
|
||||||
|
'$route.query.keyword'(value) {
|
||||||
|
this.searchData = value || ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.searchData = this.$route.query.keyword
|
this.searchData = this.$route.query.keyword || ''
|
||||||
|
this.history = readHistory()
|
||||||
|
|
||||||
if (!this.hover) { // 首页顶部固定搜索栏不调用热词接口
|
if (!this.hover) {
|
||||||
// 搜索热词每5分钟请求一次
|
|
||||||
const reloadTime = storage.getItem('hotWordsReloadTime')
|
const reloadTime = storage.getItem('hotWordsReloadTime')
|
||||||
const time = new Date().getTime() - 5 * 60 * 1000
|
const time = new Date().getTime() - 5 * 60 * 1000
|
||||||
if (!reloadTime) {
|
if (!reloadTime || time > reloadTime) this.loadHotWords()
|
||||||
hotWords({count: 5}).then(res => {
|
|
||||||
if (res.success && res.result) storage.setItem('hotWordsList', res.result)
|
|
||||||
}).catch(() => {})
|
|
||||||
storage.setItem('hotWordsReloadTime', new Date().getTime())
|
|
||||||
} else if (reloadTime && time > reloadTime) {
|
|
||||||
hotWords({count: 5}).then(res => {
|
|
||||||
if (res.success && res.result) storage.setItem('hotWordsList', res.result)
|
|
||||||
}).catch(() => {})
|
|
||||||
storage.setItem('hotWordsReloadTime', new Date().getTime())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.onDocumentClick = this.onDocumentClick.bind(this)
|
||||||
|
document.addEventListener('click', this.onDocumentClick)
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
clearTimeout(this.suggestTimer)
|
||||||
|
document.removeEventListener('click', this.onDocumentClick)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -169,7 +306,6 @@ export default {
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +353,11 @@ export default {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-embedded.search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.search-box{
|
.search-box{
|
||||||
margin-left: 28px;
|
margin-left: 28px;
|
||||||
}
|
}
|
||||||
@@ -225,28 +366,7 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-search {
|
|
||||||
width: 55.6px;
|
|
||||||
padding: 0 9px;
|
|
||||||
border-radius: 0;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
width: 55px;
|
|
||||||
margin-left: -2px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-div {
|
|
||||||
position: relative;
|
|
||||||
height: 0px;
|
|
||||||
top: -38px;
|
|
||||||
left: 352px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-list {
|
.history-list {
|
||||||
|
|
||||||
margin-left: 28px;
|
margin-left: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,4 +379,18 @@ export default {
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.suggest-panel {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
z-index: 30;
|
||||||
|
padding: 8px 0;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
151
buyer/src/components/SearchSuggest.vue
Normal file
151
buyer/src/components/SearchSuggest.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="keyword" class="suggest-section">
|
||||||
|
<div v-if="suggesting && !suggestions.length" class="suggest-empty">正在查找...</div>
|
||||||
|
<button
|
||||||
|
v-for="item in suggestions"
|
||||||
|
:key="item"
|
||||||
|
type="button"
|
||||||
|
class="suggest-item"
|
||||||
|
@click="$emit('pick', item)"
|
||||||
|
>
|
||||||
|
<span v-for="(part, index) in parts(item)" :key="index" :class="{ hit: part.hit }">{{ part.text }}</span>
|
||||||
|
</button>
|
||||||
|
<div v-if="!suggesting && !suggestions.length" class="suggest-empty">没有匹配的商品,回车查看全部结果</div>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div v-if="history.length" class="suggest-section">
|
||||||
|
<div class="suggest-head">
|
||||||
|
<span>搜索历史</span>
|
||||||
|
<button type="button" class="suggest-clear" @click="$emit('clear')">清空</button>
|
||||||
|
</div>
|
||||||
|
<div class="suggest-tags">
|
||||||
|
<button
|
||||||
|
v-for="item in history"
|
||||||
|
:key="item"
|
||||||
|
type="button"
|
||||||
|
class="suggest-tag"
|
||||||
|
@click="$emit('pick', item)"
|
||||||
|
>{{ item }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="hotWords.length" class="suggest-section">
|
||||||
|
<div class="suggest-head"><span>热门搜索</span></div>
|
||||||
|
<div class="suggest-tags">
|
||||||
|
<button
|
||||||
|
v-for="item in hotWords"
|
||||||
|
:key="item"
|
||||||
|
type="button"
|
||||||
|
class="suggest-tag"
|
||||||
|
@click="$emit('pick', item)"
|
||||||
|
>{{ item }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SearchSuggest",
|
||||||
|
props: {
|
||||||
|
keyword: { type: String, default: "" },
|
||||||
|
history: { type: Array, default: () => [] },
|
||||||
|
hotWords: { type: Array, default: () => [] },
|
||||||
|
suggestions: { type: Array, default: () => [] },
|
||||||
|
suggesting: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
emits: ["pick", "clear"],
|
||||||
|
methods: {
|
||||||
|
parts(text) {
|
||||||
|
const value = String(text || "");
|
||||||
|
const key = this.keyword;
|
||||||
|
if (!key) return [{ text: value, hit: false }];
|
||||||
|
const index = value.toLowerCase().indexOf(key.toLowerCase());
|
||||||
|
if (index < 0) return [{ text: value, hit: false }];
|
||||||
|
return [
|
||||||
|
{ text: value.slice(0, index), hit: false },
|
||||||
|
{ text: value.slice(index, index + key.length), hit: true },
|
||||||
|
{ text: value.slice(index + key.length), hit: false },
|
||||||
|
].filter((part) => part.text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.suggest-section + .suggest-section {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 16px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-clear {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 16px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-tag {
|
||||||
|
max-width: 180px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit {
|
||||||
|
color: $theme_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggest-empty {
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
v-for="(img,imgIndex) in order.orderItems"
|
v-for="(img,imgIndex) in order.orderItems"
|
||||||
:key="imgIndex" width="40" height="40" alt="">
|
:key="imgIndex" width="40" height="40" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="order-handle"><span>{{ $filters.unitPrice(order.flowPrice, "¥") }}</span><span class="hover-color" @click="linkTo(`home/OrderDetail?sn=${order.sn}`)">查看订单</span></div>
|
<div class="order-handle"><span>{{ $filters.unitPrice(order.flowPrice, "¥") }}</span><span class="hover-color" @click="linkTo(`/home/OrderDetail?sn=${order.sn}`)">查看订单</span></div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<el-button type="primary" @click="linkTo('/home/MyOrder')" style="width:100%">查看全部订单</el-button>
|
<el-button type="primary" @click="linkTo('/home/MyOrder')" style="width:100%">查看全部订单</el-button>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<el-icon :size="20"><component :is="resolveIcon(item.icon)" /></el-icon>
|
<el-icon :size="20"><component :is="resolveIcon(item.icon)" /></el-icon>
|
||||||
<p class="barTitle" v-if="item.titleShow"> {{item.title}}</p>
|
<p class="barTitle" v-if="item.titleShow"> {{item.title}}</p>
|
||||||
<div class="circle" v-if="item.title === '购物车'">
|
<div class="circle" v-if="item.title === '购物车'">
|
||||||
{{cartNum < 100 ? cartNum : 99}}
|
{{ Number(cartNum) > 99 ? '99+' : cartNum }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,21 +79,15 @@ export default {
|
|||||||
this.handleDrawer = false
|
this.handleDrawer = false
|
||||||
switch (val.title) {
|
switch (val.title) {
|
||||||
case '会员中心':
|
case '会员中心':
|
||||||
this.openBlank('/home')
|
this.$router.push('/home')
|
||||||
break;
|
break;
|
||||||
case '我的资产':
|
case '我的资产':
|
||||||
this.openBlank('/home/MoneyManagement')
|
this.$router.push('/home/MoneyManagement')
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openBlank (path) { // 新页面打开地址
|
|
||||||
let routerUrl = this.$router.resolve({
|
|
||||||
path: path
|
|
||||||
})
|
|
||||||
window.open(routerUrl.href, '_blank')
|
|
||||||
},
|
|
||||||
getCartList () { // 获取购物车列表
|
getCartList () { // 获取购物车列表
|
||||||
cartCount().then(res => {
|
cartCount().then(res => {
|
||||||
this.$store.commit('SET_CARTNUM', res.result)
|
this.$store.commit('SET_CARTNUM', res.result)
|
||||||
@@ -131,11 +125,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
width: 20px;
|
min-width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
padding: 0 4px;
|
||||||
|
border-radius: 10px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: $theme_color;
|
background: $theme_color;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
|
|||||||
@@ -90,10 +90,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
goArticle() {
|
goArticle() {
|
||||||
// 跳转文章页
|
// 跳转文章页
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push("/article");
|
||||||
path: "/article",
|
|
||||||
});
|
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
118
buyer/src/components/goods/GoodsThumb.vue
Normal file
118
buyer/src/components/goods/GoodsThumb.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div class="goods-thumb" :class="{ 'is-loaded': loaded, 'is-error': error }">
|
||||||
|
<img v-if="currentSrc" :src="currentSrc" :alt="alt" @load="loaded = true" @error="onError" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "GoodsThumb",
|
||||||
|
props: {
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
alt: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentSrc: "",
|
||||||
|
loaded: false,
|
||||||
|
error: false,
|
||||||
|
observer: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
src() {
|
||||||
|
this.observe();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.observe();
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.disconnect();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
disconnect() {
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect();
|
||||||
|
this.observer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
observe() {
|
||||||
|
this.disconnect();
|
||||||
|
this.currentSrc = "";
|
||||||
|
this.loaded = false;
|
||||||
|
this.error = false;
|
||||||
|
if (!this.src) {
|
||||||
|
this.error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof IntersectionObserver === "undefined") {
|
||||||
|
this.currentSrc = this.src;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (!entries.some((entry) => entry.isIntersecting)) return;
|
||||||
|
this.currentSrc = this.src;
|
||||||
|
this.disconnect();
|
||||||
|
},
|
||||||
|
{ rootMargin: "160px 0px" }
|
||||||
|
);
|
||||||
|
this.observer.observe(this.$el);
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
this.error = true;
|
||||||
|
this.loaded = false;
|
||||||
|
this.currentSrc = "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.goods-thumb {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-thumb::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(90deg, #f3f3f3 25%, #e8e8e8 37%, #f3f3f3 63%);
|
||||||
|
background-size: 400% 100%;
|
||||||
|
animation: goods-thumb-loading 1.2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-thumb.is-loaded::before,
|
||||||
|
.goods-thumb.is-error::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-thumb img {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes goods-thumb-loading {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,9 +8,8 @@
|
|||||||
<div class="item-detail-big-img" v-if="imgList[imgIndex]">
|
<div class="item-detail-big-img" v-if="imgList[imgIndex]">
|
||||||
<el-image
|
<el-image
|
||||||
:src="currentImg"
|
:src="currentImg"
|
||||||
:preview-src-list="previewList"
|
fit="cover"
|
||||||
fit="contain"
|
style="width: 100%; height: 100%"
|
||||||
style="width: 100%; height: 350px"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,27 +43,6 @@
|
|||||||
<div class="item-detail-title">
|
<div class="item-detail-title">
|
||||||
<div class="item-detail-title-row">
|
<div class="item-detail-title-row">
|
||||||
<span class="item-detail-name">{{ skuDetail.goodsName }}</span>
|
<span class="item-detail-name">{{ skuDetail.goodsName }}</span>
|
||||||
<el-tag
|
|
||||||
v-if="isECouponGoods"
|
|
||||||
class="goods-type-tag"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
电子卡券
|
|
||||||
</el-tag>
|
|
||||||
<el-tag
|
|
||||||
v-else-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
|
|
||||||
class="goods-type-tag"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
实物商品
|
|
||||||
</el-tag>
|
|
||||||
<el-tag
|
|
||||||
v-else-if="skuDetail.goodsType == 'VIRTUAL_GOODS'"
|
|
||||||
class="goods-type-tag"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
虚拟商品
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sell-point">
|
<div class="sell-point">
|
||||||
@@ -95,7 +73,7 @@
|
|||||||
<!-- 批发价格 -->
|
<!-- 批发价格 -->
|
||||||
<div v-if="!isECouponGoods && wholesaleNum && wholesaleNum.length">
|
<div v-if="!isECouponGoods && wholesaleNum && wholesaleNum.length">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="item-price-title">价格</div>
|
<div class="item-price-title"></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in wholesalePrice"
|
v-for="(item, index) in wholesalePrice"
|
||||||
@@ -119,7 +97,6 @@
|
|||||||
|
|
||||||
<!-- 普通价格 -->
|
<!-- 普通价格 -->
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="item-price-title">价格</span>
|
|
||||||
<span class="item-price">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
|
<span class="item-price">{{ $filters.unitPrice(skuDetail.price, "¥") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,7 +205,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 选择颜色 -->
|
<!-- 选择颜色 -->
|
||||||
<div
|
<div
|
||||||
class="item-select"
|
class="item-select item-spec"
|
||||||
v-for="(sku, index) in formatList"
|
v-for="(sku, index) in formatList"
|
||||||
:key="sku.name"
|
:key="sku.name"
|
||||||
>
|
>
|
||||||
@@ -257,7 +234,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="add-buy-car-box">
|
<div class="add-buy-car-box">
|
||||||
<div class="item-select">
|
<div class="item-select item-spec">
|
||||||
<div class="item-select-title">
|
<div class="item-select-title">
|
||||||
<p>数量</p>
|
<p>数量</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -271,25 +248,10 @@
|
|||||||
:precision="0"
|
:precision="0"
|
||||||
@blur="changeCount"
|
@blur="changeCount"
|
||||||
></el-input-number>
|
></el-input-number>
|
||||||
<span class="inventory"> 库存{{ displayStock }}</span>
|
<span class="inventory">(库存:{{ displayStock }})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="item-select"
|
|
||||||
v-if="
|
|
||||||
!isECouponGoods &&
|
|
||||||
skuDetail.goodsType !== 'VIRTUAL_GOODS' &&
|
|
||||||
skuDetail.weight !== 0
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="item-select-title">
|
|
||||||
<p>重量</p>
|
|
||||||
</div>
|
|
||||||
<div class="item-select-column">
|
|
||||||
<span class="inventory">{{ skuDetail.weight }}kg</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="item-select add-buy-car-row"
|
class="item-select add-buy-car-row"
|
||||||
v-if="$route.query.way === 'POINT' && skuDetail.authFlag === 'PASS'"
|
v-if="$route.query.way === 'POINT' && skuDetail.authFlag === 'PASS'"
|
||||||
@@ -439,9 +401,6 @@ export default {
|
|||||||
const item = this.imgList[this.imgIndex];
|
const item = this.imgList[this.imgIndex];
|
||||||
return item ? item.url || item : "";
|
return item ? item.url || item : "";
|
||||||
},
|
},
|
||||||
previewList() {
|
|
||||||
return this.imgList.map((item) => item.url || item);
|
|
||||||
},
|
|
||||||
wholesalePrice(key) {
|
wholesalePrice(key) {
|
||||||
return this.wholesaleList.length
|
return this.wholesaleList.length
|
||||||
? this.wholesaleList.map((item) => {
|
? this.wholesaleList.map((item) => {
|
||||||
@@ -624,6 +583,16 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
async collect() {
|
async collect() {
|
||||||
|
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) {
|
||||||
|
this.$router.push({
|
||||||
|
path: "/login",
|
||||||
|
query: {
|
||||||
|
rePath: this.$route.path,
|
||||||
|
query: JSON.stringify(this.$route.query || {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 收藏商品
|
// 收藏商品
|
||||||
if (this.isCollected) {
|
if (this.isCollected) {
|
||||||
let cancel = await cancelCollect("GOODS", this.skuDetail.id);
|
let cancel = await cancelCollect("GOODS", this.skuDetail.id);
|
||||||
@@ -717,16 +686,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 相册里会混进规格 JSON,只留图片地址;没有相册时用主图
|
||||||
swiperGoodsImg() {
|
swiperGoodsImg() {
|
||||||
this.skuDetail.specList.forEach((e) => {
|
const source = this.skuDetail.goodsGalleryList || [];
|
||||||
if (e.specName === "images") {
|
const gallery = Array.from(source).filter((item) => {
|
||||||
this.imgList = this.skuDetail.goodsGalleryList.filter(i => i.indexOf("\"url\":") === -1 && i.indexOf("\"status\":") === -1);
|
if (typeof item !== "string") return false;
|
||||||
}
|
return item.indexOf('"url":') === -1 && item.indexOf('"status":') === -1;
|
||||||
});
|
});
|
||||||
if (!this.imgList) {
|
if (gallery.length) {
|
||||||
this.imgList = [this.skuDetail.original];
|
this.imgList = gallery;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.imgList = this.skuDetail.original ? [this.skuDetail.original] : [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -784,42 +755,50 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-left {
|
.item-detail-left {
|
||||||
width: 350px;
|
width: 500px;
|
||||||
margin-right: 32px;
|
margin-right: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-big-img {
|
.item-detail-big-img {
|
||||||
width: 350px;
|
width: 500px;
|
||||||
height: 350px;
|
height: 500px;
|
||||||
border: 1px solid #eee;
|
border-radius: 16px;
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
margin-bottom: 16px;
|
||||||
margin-bottom: 12px;
|
background: #f5f5f5;
|
||||||
background: #fff;
|
|
||||||
|
:deep(.el-image),
|
||||||
|
:deep(.el-image__inner) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-image__inner) {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#dplayer{
|
#dplayer{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.item-detail-big-img img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-detail-img-row {
|
.item-detail-img-row {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-img-small {
|
.item-detail-img-small {
|
||||||
width: 64px;
|
width: 86px;
|
||||||
height: 64px;
|
height: 86px;
|
||||||
border: 1px solid #eee;
|
border: 2px solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
border-color: $theme_color;
|
border-color: $theme_color;
|
||||||
@@ -829,6 +808,7 @@ export default {
|
|||||||
.item-detail-img-small img {
|
.item-detail-img-small img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*商品选购详情*/
|
/*商品选购详情*/
|
||||||
@@ -848,18 +828,11 @@ export default {
|
|||||||
|
|
||||||
.item-detail-name {
|
.item-detail-name {
|
||||||
color: #171A26;
|
color: #171A26;
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-type-tag {
|
|
||||||
flex-shrink: 0;
|
|
||||||
--el-tag-bg-color: #fafafa;
|
|
||||||
--el-tag-border-color: #eee;
|
|
||||||
--el-tag-text-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-detail-express {
|
.item-detail-express {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 2px 3px;
|
padding: 2px 3px;
|
||||||
@@ -1026,6 +999,22 @@ export default {
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-spec {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.item-select-title {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-select-column {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-select-title {
|
.item-select-title {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -1215,8 +1204,9 @@ export default {
|
|||||||
|
|
||||||
.inventory {
|
.inventory {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
color: #999;
|
color: #666;
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
.view-video{
|
.view-video{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="goods-detail-extra">
|
<div class="goods-detail-extra">
|
||||||
|
<div class="goods-detail-extra__main">
|
||||||
|
<div class="detail-layout">
|
||||||
|
<div class="detail-main">
|
||||||
<div class="detail-anchor" ref="tabNav" :class="{ 'is-stuck': anchorStuck }">
|
<div class="detail-anchor" ref="tabNav" :class="{ 'is-stuck': anchorStuck }">
|
||||||
<button
|
<button
|
||||||
v-for="tab in detailTabs"
|
v-for="tab in detailTabs"
|
||||||
@@ -117,15 +120,25 @@
|
|||||||
<div class="detail-empty" v-else>暂无商品参数</div>
|
<div class="detail-empty" v-else>暂无商品参数</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<StoreSide
|
||||||
|
:store-id="skuDetail.storeId"
|
||||||
|
:goods-id="skuDetail.goodsId"
|
||||||
|
:sku-id="skuDetail.id"
|
||||||
|
:store-name-fallback="skuDetail.storeName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Refresh } from '@element-plus/icons-vue';
|
import { Refresh } from '@element-plus/icons-vue';
|
||||||
import { goodsComment, goodsCommentNum } from '@/api/member.js';
|
import { goodsComment, goodsCommentNum } from '@/api/member.js';
|
||||||
|
import StoreSide from './StoreSide.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Refresh },
|
components: { Refresh, StoreSide },
|
||||||
name: 'ShowGoodsDetail',
|
name: 'ShowGoodsDetail',
|
||||||
props: {
|
props: {
|
||||||
detail: {
|
detail: {
|
||||||
@@ -277,8 +290,25 @@ export default {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.goods-detail-extra {
|
.goods-detail-extra {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-detail-extra__main {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
margin: 16px auto 24px;
|
margin: 0 auto;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-anchor {
|
.detail-anchor {
|
||||||
@@ -334,6 +364,7 @@ export default {
|
|||||||
.detail-panel {
|
.detail-panel {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
|
padding-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section + .detail-section {
|
.detail-section + .detail-section {
|
||||||
@@ -381,7 +412,7 @@ export default {
|
|||||||
.item-param-grid {
|
.item-param-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
margin: 16px 24px 24px;
|
margin: 16px 24px 0;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
border-left: 1px solid #f0f0f0;
|
border-left: 1px solid #f0f0f0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
419
buyer/src/components/goodsDetail/StoreSide.vue
Normal file
419
buyer/src/components/goodsDetail/StoreSide.vue
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<aside class="store-side" v-if="storeId">
|
||||||
|
<div class="store-card">
|
||||||
|
<div class="store-head" @click="goStore">
|
||||||
|
<img
|
||||||
|
v-if="store.storeLogo && !logoError"
|
||||||
|
class="store-logo"
|
||||||
|
:src="store.storeLogo"
|
||||||
|
:alt="storeName"
|
||||||
|
@error="logoError = true"
|
||||||
|
/>
|
||||||
|
<span v-else class="store-logo store-logo-fallback">{{ storeInitial }}</span>
|
||||||
|
<div class="store-head-text">
|
||||||
|
<p class="store-name">
|
||||||
|
<span v-if="isSelfOperated" class="store-self">自营</span>
|
||||||
|
<span class="store-name-text">{{ storeName }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="store-scores">
|
||||||
|
<div class="score-item">
|
||||||
|
<span class="score-label">描述</span>
|
||||||
|
<span class="score-value">{{ formatScore(store.descriptionScore) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="score-item">
|
||||||
|
<span class="score-label">服务</span>
|
||||||
|
<span class="score-value">{{ formatScore(store.serviceScore) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="score-item">
|
||||||
|
<span class="score-label">物流</span>
|
||||||
|
<span class="score-value">{{ formatScore(store.deliveryScore) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="store-actions">
|
||||||
|
<button type="button" class="store-btn" @click="contact">联系客服</button>
|
||||||
|
<button type="button" class="store-btn" :class="{ 'is-on': collected }" @click="toggleCollect">
|
||||||
|
{{ collected ? "已收藏" : "收藏店铺" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="enter-store" @click="goStore">进店逛逛</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="recommend-card" v-if="recommends.length">
|
||||||
|
<p class="recommend-title">店铺推荐</p>
|
||||||
|
<div
|
||||||
|
class="recommend-item"
|
||||||
|
v-for="item in recommends"
|
||||||
|
:key="item.id"
|
||||||
|
@click="goGoods(item)"
|
||||||
|
>
|
||||||
|
<img class="recommend-img" :src="item.thumbnail || item.small" :alt="item.goodsName" />
|
||||||
|
<div class="recommend-info">
|
||||||
|
<p class="recommend-name">{{ item.goodsName }}</p>
|
||||||
|
<div class="recommend-meta">
|
||||||
|
<p class="recommend-price">{{ $filters.unitPrice(item.price, "¥") }}</p>
|
||||||
|
<span class="recommend-sales">销量 {{ item.buyCount || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Message } from "@/utils/message";
|
||||||
|
import { goodsList } from "@/api/goods";
|
||||||
|
import { getStoreById } from "@/api/shopentry";
|
||||||
|
import { cancelStoreCollect, collectStore, isStoreCollection } from "@/api/member";
|
||||||
|
import imTalk from "@/components/mixes/talkIm";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "StoreSide",
|
||||||
|
mixins: [imTalk],
|
||||||
|
props: {
|
||||||
|
storeId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
goodsId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
skuId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
storeNameFallback: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
store: {},
|
||||||
|
logoError: false,
|
||||||
|
collected: false,
|
||||||
|
recommends: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
storeName() {
|
||||||
|
return this.store.storeName || this.storeNameFallback || "店铺";
|
||||||
|
},
|
||||||
|
storeInitial() {
|
||||||
|
return this.storeName.slice(0, 1);
|
||||||
|
},
|
||||||
|
// 自营标记后端可能是 true、1 或 "1"
|
||||||
|
isSelfOperated() {
|
||||||
|
const flag = this.store.selfOperated;
|
||||||
|
return flag === true || flag === 1 || flag === "1";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
storeId: {
|
||||||
|
immediate: true,
|
||||||
|
handler(id) {
|
||||||
|
if (!id) return;
|
||||||
|
this.logoError = false;
|
||||||
|
this.loadStore(id);
|
||||||
|
this.loadRecommend(id);
|
||||||
|
this.loadCollected(id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 没有评分或小于等于 0 时按满分展示
|
||||||
|
formatScore(value) {
|
||||||
|
const score = Number(value);
|
||||||
|
if (!Number.isFinite(score) || score <= 0) return "5.00";
|
||||||
|
return score.toFixed(2);
|
||||||
|
},
|
||||||
|
loadStore(id) {
|
||||||
|
getStoreById(id).then((res) => {
|
||||||
|
if (res.success && res.result) this.store = res.result;
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
// 按销量取店内商品,去掉当前商品,最多 4 个
|
||||||
|
loadRecommend(id) {
|
||||||
|
goodsList({
|
||||||
|
storeId: id,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 8,
|
||||||
|
sort: "buyCount",
|
||||||
|
order: "desc",
|
||||||
|
}).then((res) => {
|
||||||
|
const records = (res.success && res.result && res.result.records) || [];
|
||||||
|
this.recommends = records
|
||||||
|
.filter((item) => String(item.goodsId) !== String(this.goodsId))
|
||||||
|
.slice(0, 4);
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
// 同时有 token 和用户信息才查收藏,避免残留 userInfo 触发登录跳转
|
||||||
|
loadCollected(id) {
|
||||||
|
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) return;
|
||||||
|
isStoreCollection("STORE", id).then((res) => {
|
||||||
|
if (res.success && res.result) this.collected = true;
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
goStore() {
|
||||||
|
this.$router.push({ path: "/merchant", query: { id: this.storeId } });
|
||||||
|
},
|
||||||
|
// 推荐商品新开详情页,店铺页仍在当前页打开
|
||||||
|
goGoods(item) {
|
||||||
|
const href = this.$router.resolve({
|
||||||
|
path: "/goodsDetail",
|
||||||
|
query: { skuId: item.id, goodsId: item.goodsId },
|
||||||
|
}).href;
|
||||||
|
window.open(href, "_blank");
|
||||||
|
},
|
||||||
|
contact() {
|
||||||
|
this.IMService(this.storeId, this.goodsId, this.skuId);
|
||||||
|
},
|
||||||
|
// 未登录先去登录,并带回当前商品页
|
||||||
|
async toggleCollect() {
|
||||||
|
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) {
|
||||||
|
this.$router.push({
|
||||||
|
path: "/login",
|
||||||
|
query: {
|
||||||
|
rePath: this.$route.path,
|
||||||
|
query: JSON.stringify(this.$route.query || {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.collected) {
|
||||||
|
const cancel = await cancelStoreCollect("STORE", this.storeId);
|
||||||
|
if (cancel.success) {
|
||||||
|
this.collected = false;
|
||||||
|
Message.success("已取消收藏");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const collect = await collectStore("STORE", this.storeId);
|
||||||
|
if (collect.code === 200) {
|
||||||
|
this.collected = true;
|
||||||
|
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.store-side {
|
||||||
|
width: 260px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card,
|
||||||
|
.recommend-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-head {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo-fallback {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
background: $theme_color;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-head-text {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
color: #171A26;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name-text {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-self {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: $theme_color;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-scores {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
display: block;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
color: $theme_color;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-btn,
|
||||||
|
.enter-store {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
background: #fff;
|
||||||
|
color: #171A26;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 32px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $theme_color;
|
||||||
|
border-color: $theme_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-btn.is-on {
|
||||||
|
color: $theme_color;
|
||||||
|
border-color: $theme_color;
|
||||||
|
background: #fff5f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enter-store {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-card {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px 12px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: #171A26;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-info {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-name {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-price {
|
||||||
|
margin: 0;
|
||||||
|
color: $theme_color;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-sales {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<transition name='fade'>
|
<transition name='fade'>
|
||||||
<ul class="drop-items">
|
<ul class="drop-items">
|
||||||
<li @click="goUserCenter('/home/MyOrder')">我的主页</li>
|
<li @click="goUserCenter('/home')">我的主页</li>
|
||||||
<li @click="goUserCenter('/home/Coupons')">优惠券</li>
|
<li @click="goUserCenter('/home/Coupons')">优惠券</li>
|
||||||
<li @click="goUserCenter('/home/Favorites')">我的收藏</li>
|
<li @click="goUserCenter('/home/Favorites')">我的收藏</li>
|
||||||
<li @click="signOutFun">退出登录</li>
|
<li @click="signOutFun">退出登录</li>
|
||||||
@@ -39,7 +39,12 @@
|
|||||||
</li>
|
</li>
|
||||||
<li @click="goUserCenter('/home/MyOrder')"><span class="nav-item">我的订单</span></li>
|
<li @click="goUserCenter('/home/MyOrder')"><span class="nav-item">我的订单</span></li>
|
||||||
<li @click="goUserCenter('/home/MyTracks')"><span class="nav-item">我的足迹</span></li>
|
<li @click="goUserCenter('/home/MyTracks')"><span class="nav-item">我的足迹</span></li>
|
||||||
<li @click="goUserCenter('/home/MsgList')"><span class="nav-item">我的消息</span></li>
|
<li @click="goUserCenter('/home/MsgList')">
|
||||||
|
<span class="nav-item">
|
||||||
|
我的消息
|
||||||
|
<i v-if="unreadMsg > 0" class="msg-badge">{{ unreadLabel }}</i>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
<li v-if="$route.name !== 'Cart'" class="cart-nav-item">
|
<li v-if="$route.name !== 'Cart'" class="cart-nav-item">
|
||||||
<el-dropdown
|
<el-dropdown
|
||||||
class="cart-nav-dropdown"
|
class="cart-nav-dropdown"
|
||||||
@@ -49,7 +54,7 @@
|
|||||||
>
|
>
|
||||||
<span class="nav-item" @click="goToPay">
|
<span class="nav-item" @click="goToPay">
|
||||||
<el-icon :size="18"><ShoppingCart /></el-icon>
|
<el-icon :size="18"><ShoppingCart /></el-icon>
|
||||||
购物车({{ cartNum < 100 ? cartNum : '99' }})
|
购物车({{ Number(cartNum) > 99 ? '99+' : cartNum }})
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<div class="shopping-cart-null" v-show="shoppingCart.length <= 0">
|
<div class="shopping-cart-null" v-show="shoppingCart.length <= 0">
|
||||||
@@ -90,6 +95,7 @@ import storage from '@/plugins/storage.js';
|
|||||||
import {cartGoodsAll} from '@/api/cart.js';
|
import {cartGoodsAll} from '@/api/cart.js';
|
||||||
import {logout} from '@/api/account.js';
|
import {logout} from '@/api/account.js';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { refreshUnreadMessage } from '@/utils/unreadMessage';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ShoppingCart },
|
components: { ShoppingCart },
|
||||||
@@ -97,6 +103,7 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
if (storage.getItem('userInfo')) {
|
if (storage.getItem('userInfo')) {
|
||||||
this.userInfo = JSON.parse(storage.getItem('userInfo'));
|
this.userInfo = JSON.parse(storage.getItem('userInfo'));
|
||||||
|
refreshUnreadMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -114,6 +121,12 @@ export default {
|
|||||||
// 购物车商品数量
|
// 购物车商品数量
|
||||||
cartNum() {
|
cartNum() {
|
||||||
return this.$store.state.cartNum;
|
return this.$store.state.cartNum;
|
||||||
|
},
|
||||||
|
unreadMsg() {
|
||||||
|
return Number(this.$store.state.unreadMsg) || 0;
|
||||||
|
},
|
||||||
|
unreadLabel() {
|
||||||
|
return this.unreadMsg > 99 ? '99+' : String(this.unreadMsg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -136,16 +149,10 @@ export default {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = this.$router.resolve({
|
this.$router.push('/cart');
|
||||||
path: '/cart',
|
|
||||||
});
|
|
||||||
window.open(url.href, '_blank');
|
|
||||||
},
|
},
|
||||||
myInfo() { // 跳转会员中心
|
myInfo() { // 跳转会员中心
|
||||||
let url = this.$router.resolve({
|
this.$router.push('/home');
|
||||||
path: '/home/MyOrder'
|
|
||||||
});
|
|
||||||
window.open(url.href, '_blank');
|
|
||||||
},
|
},
|
||||||
signOutFun() { // 退出登录
|
signOutFun() { // 退出登录
|
||||||
logout().then(res => {
|
logout().then(res => {
|
||||||
@@ -154,6 +161,7 @@ export default {
|
|||||||
storage.removeItem('userInfo');
|
storage.removeItem('userInfo');
|
||||||
storage.removeItem('cartNum');
|
storage.removeItem('cartNum');
|
||||||
this.$store.commit('SET_CARTNUM', 0)
|
this.$store.commit('SET_CARTNUM', 0)
|
||||||
|
this.$store.commit('SET_UNREAD_MSG', 0)
|
||||||
this.$router.push('/login');
|
this.$router.push('/login');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -181,11 +189,10 @@ export default {
|
|||||||
},
|
},
|
||||||
shopEntry() {
|
shopEntry() {
|
||||||
if (storage.getItem('accessToken')) {
|
if (storage.getItem('accessToken')) {
|
||||||
const routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/shopEntry',
|
path: '/shopEntry',
|
||||||
query: { id: 1 },
|
query: { id: 1 },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, '_blank');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
@@ -289,6 +296,19 @@ export default {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.msg-badge {
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $theme_color;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.cart-nav-item {
|
.cart-nav-item {
|
||||||
:deep(.cart-nav-dropdown),
|
:deep(.cart-nav-dropdown),
|
||||||
:deep(.el-dropdown) {
|
:deep(.el-dropdown) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<span @mouseenter="getCartList">
|
<span @mouseenter="getCartList">
|
||||||
<el-icon class="cart-icon" :size="20" @click="goCartList"><ShoppingCart /></el-icon>
|
<el-icon class="cart-icon" :size="20" @click="goCartList"><ShoppingCart /></el-icon>
|
||||||
</span>
|
</span>
|
||||||
<i class="cart-badge">{{ cartNum < 100 ? cartNum : "99" }}</i>
|
<i class="cart-badge">{{ Number(cartNum) > 99 ? "99+" : cartNum }}</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,8 +45,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goCartList() {
|
goCartList() {
|
||||||
const routerUrl = this.$router.resolve({ path: '/cart' })
|
this.$router.push('/cart')
|
||||||
window.open(routerUrl.href, '_blank')
|
|
||||||
},
|
},
|
||||||
getCartList() {
|
getCartList() {
|
||||||
if (storage.getItem('userInfo')) {
|
if (storage.getItem('userInfo')) {
|
||||||
@@ -71,7 +70,7 @@ $cart-col-width: 40px;
|
|||||||
|
|
||||||
.scroll-show {
|
.scroll-show {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -125,8 +124,9 @@ $cart-col-width: 40px;
|
|||||||
background-color: $theme_color;
|
background-color: $theme_color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
width: 17px;
|
min-width: 17px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
|
padding: 0 4px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "0",
|
value: "0",
|
||||||
label: "代付款"
|
label: "待付款"
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -184,7 +184,12 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
isMemberRoute(location) {
|
isMemberRoute(location) {
|
||||||
return location.path.startsWith("/home/") || location.path === "/cart";
|
const path = location.path && location.path.charAt(0) === "/" ? location.path : `/${location.path || ""}`;
|
||||||
|
return path === "/home" || path.startsWith("/home/") || path === "/cart";
|
||||||
|
},
|
||||||
|
normalizeLocation(location) {
|
||||||
|
if (!location.path || location.path.charAt(0) === "/") return location;
|
||||||
|
return { ...location, path: `/${location.path}` };
|
||||||
},
|
},
|
||||||
promptLoginThenNavigate(location) {
|
promptLoginThenNavigate(location) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
@@ -208,13 +213,12 @@ export default {
|
|||||||
},
|
},
|
||||||
// 快捷跳转中心
|
// 快捷跳转中心
|
||||||
entryControl(val) {
|
entryControl(val) {
|
||||||
const location = this.resolveRoute(val.path);
|
const location = this.normalizeLocation(this.resolveRoute(val.path));
|
||||||
if (this.isMemberRoute(location) && !storage.getItem("userInfo")) {
|
if (this.isMemberRoute(location) && !storage.getItem("userInfo")) {
|
||||||
this.promptLoginThenNavigate(location);
|
this.promptLoginThenNavigate(location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = this.$router.resolve(location);
|
this.$router.push(location);
|
||||||
window.open(url.href, "_blank");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -87,11 +87,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
goArticle (id) { // 跳转文章详情
|
goArticle (id) { // 跳转文章详情
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/article',
|
path: '/article',
|
||||||
query: {id}
|
query: {id}
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, '_blank');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goToSeckill() {
|
goToSeckill() {
|
||||||
const routeUrl = this.$router.resolve({ path: "/seckill" });
|
this.$router.push("/seckill");
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
countDown(currIndex) {
|
countDown(currIndex) {
|
||||||
const zeroTime = new Date(new Date().toLocaleDateString()).getTime();
|
const zeroTime = new Date(new Date().toLocaleDateString()).getTime();
|
||||||
|
|||||||
@@ -198,11 +198,10 @@ export default {
|
|||||||
if (!arr[0]) {
|
if (!arr[0]) {
|
||||||
arr.shift();
|
arr.shift();
|
||||||
}
|
}
|
||||||
let routerUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/goodsList",
|
path: "/goodsList",
|
||||||
query: { categoryId: arr.toString() },
|
query: { categoryId: arr.toString() },
|
||||||
});
|
});
|
||||||
window.open(routerUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -281,11 +281,10 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
goShopPage(id) {
|
goShopPage(id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/merchant",
|
path: "/merchant",
|
||||||
query: { id },
|
query: { id },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
// 同步购物车商品收藏状态
|
// 同步购物车商品收藏状态
|
||||||
async syncCartCollectionStatus() {
|
async syncCartCollectionStatus() {
|
||||||
@@ -959,3 +958,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,55 +2,43 @@
|
|||||||
<div class="coupon-center">
|
<div class="coupon-center">
|
||||||
<BaseHeader></BaseHeader>
|
<BaseHeader></BaseHeader>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div>
|
<h2 class="coupon-page-title">领券中心</h2>
|
||||||
<div class="coupon-title">
|
|
||||||
<router-link to="/">
|
|
||||||
<img src="../assets/images/logo.png" width="120" alt="" />
|
|
||||||
</router-link>
|
|
||||||
<p>领券中心</p>
|
|
||||||
<el-input
|
|
||||||
v-model="keyword"
|
|
||||||
style="width: 400px"
|
|
||||||
@keyup.enter="search"
|
|
||||||
placeholder="搜索优惠券"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="fontsize_18 recommend">推荐好券</div>
|
<empty v-if="!loading && list.length === 0" />
|
||||||
|
<ul class="coupon-list" v-else-if="list.length > 0">
|
||||||
<empty v-if="!loading && list.length === 0" />
|
<li v-for="item in list" class="coupon-item" :key="item.id">
|
||||||
<ul class="coupon-list" v-else-if="list.length > 0">
|
<div class="c-left">
|
||||||
<li v-for="(item, index) in list" class="coupon-item" :key="index">
|
<div class="coupon-amount">
|
||||||
<div class="c-left">
|
<span v-if="item.couponType === 'PRICE'" class="global_color">
|
||||||
<div>
|
¥<span class="price">{{ $filters.unitPrice(item.price) }}</span>
|
||||||
<span
|
</span>
|
||||||
v-if="item.couponType === 'PRICE'"
|
<span v-if="item.couponType === 'DISCOUNT'" class="global_color">
|
||||||
class="fontsize_12 global_color"
|
<span class="price">{{ item.couponDiscount || item.discount }}</span>折
|
||||||
>¥<span class="price">{{ $filters.unitPrice(item.price) }}</span></span>
|
</span>
|
||||||
<span
|
<span class="describe">{{ thresholdText(item) }}</span>
|
||||||
v-if="item.couponType === 'DISCOUNT'"
|
|
||||||
class="fontsize_12 global_color"
|
|
||||||
><span class="price">{{ item.couponDiscount || item.discount }}</span>折</span>
|
|
||||||
<span class="describe"
|
|
||||||
>满{{ item.consumeThreshold }}元可用</span>
|
|
||||||
</div>
|
|
||||||
<p>使用范围:{{ useScope(item.scopeType, item.storeName) }}</p>
|
|
||||||
<p>有效期:{{ item.endTime }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<b></b>
|
<p class="coupon-name">
|
||||||
<a class="c-right" @click="receive(item)">立即领取</a>
|
<span class="scope-tag">{{ scopeTag(item.scopeType, item.storeName) }}</span>
|
||||||
<i class="circle-top"></i>
|
<span>{{ item.couponName }}</span>
|
||||||
<i class="circle-bottom"></i>
|
</p>
|
||||||
</li>
|
<p class="coupon-time">有效期:{{ item.endTime }}</p>
|
||||||
</ul>
|
</div>
|
||||||
<el-pagination v-model:current-page="params.pageNumber"
|
<a
|
||||||
v-model:page-size="params.pageSize"
|
class="c-right"
|
||||||
:total="total"
|
:class="{ 'is-finished': isFinished(item) }"
|
||||||
@current-change="changePageNum"
|
@click="receive(item)"
|
||||||
class="pageration"
|
>{{ isFinished(item) ? "已领完" : "立即领取" }}</a>
|
||||||
@size-change="changePageSize"
|
</li>
|
||||||
layout="total, sizes, prev, pager, next"></el-pagination>
|
</ul>
|
||||||
</div>
|
<el-pagination
|
||||||
|
v-model:current-page="params.pageNumber"
|
||||||
|
v-model:page-size="params.pageSize"
|
||||||
|
:total="total"
|
||||||
|
@current-change="changePageNum"
|
||||||
|
class="pageration"
|
||||||
|
@size-change="changePageSize"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
></el-pagination>
|
||||||
</div>
|
</div>
|
||||||
<BaseFooter></BaseFooter>
|
<BaseFooter></BaseFooter>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +53,6 @@ export default {
|
|||||||
list: [], // 优惠券列表
|
list: [], // 优惠券列表
|
||||||
total: 0, // 优惠券总数
|
total: 0, // 优惠券总数
|
||||||
loading: false,
|
loading: false,
|
||||||
keyword: "",
|
|
||||||
params: {
|
params: {
|
||||||
// 请求参数
|
// 请求参数
|
||||||
getType: "FREE",
|
getType: "FREE",
|
||||||
@@ -75,13 +62,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 搜索优惠券
|
|
||||||
search() {
|
|
||||||
this.params.couponName = this.keyword.trim();
|
|
||||||
this.params.pageNumber = 1;
|
|
||||||
this.getList();
|
|
||||||
},
|
|
||||||
// 获取优惠券列表
|
|
||||||
getList() {
|
getList() {
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -116,6 +96,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 领取优惠券
|
// 领取优惠券
|
||||||
receive(item) {
|
receive(item) {
|
||||||
|
if (this.isFinished(item)) return;
|
||||||
const userInfo = storage.getItem("userInfo");
|
const userInfo = storage.getItem("userInfo");
|
||||||
const isLogin = userInfo && JSON.parse(userInfo).username;
|
const isLogin = userInfo && JSON.parse(userInfo).username;
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
@@ -158,23 +139,22 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
},
|
},
|
||||||
// 优惠券可用范围
|
// 发行量已领完
|
||||||
useScope(type, storeName) {
|
isFinished(item) {
|
||||||
let shop = "平台";
|
return Number(item.publishNum) > 0 && Number(item.receivedNum) >= Number(item.publishNum);
|
||||||
let goods = "全部商品";
|
},
|
||||||
if (storeName !== "platform") shop = storeName;
|
// 使用门槛文案
|
||||||
switch (type) {
|
thresholdText(item) {
|
||||||
case "ALL":
|
const threshold = Number(item.consumeThreshold);
|
||||||
goods = "全部商品";
|
if (!threshold) return "无使用门槛";
|
||||||
break;
|
return `满${item.consumeThreshold}元可用`;
|
||||||
case "PORTION_GOODS":
|
},
|
||||||
goods = "部分商品";
|
// 券范围。店铺名为 platform 时是平台通用券
|
||||||
break;
|
scopeTag(type, storeName) {
|
||||||
case "PORTION_GOODS_CATEGORY":
|
if (type === "PORTION_GOODS") return "商品券";
|
||||||
goods = "部分分类商品";
|
if (type === "PORTION_GOODS_CATEGORY") return "分类券";
|
||||||
break;
|
if (storeName && storeName !== "platform") return "店铺券";
|
||||||
}
|
return "通用券";
|
||||||
return `${shop}${goods}可用`;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -184,84 +164,156 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../assets/styles/coupon.scss";
|
@import "../assets/styles/coupon.scss";
|
||||||
|
|
||||||
|
.coupon-center {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
width: 100%;
|
width: 1200px;
|
||||||
background-color: #fff;
|
margin: 0 auto 48px;
|
||||||
|
background: transparent;
|
||||||
> div {
|
|
||||||
margin: 10px auto;
|
|
||||||
width: 1200px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.coupon-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
p {
|
.coupon-page-title {
|
||||||
font-size: 18px;
|
margin: 28px 0 20px;
|
||||||
margin-right: 500px;
|
color: #171A26;
|
||||||
}
|
font-size: 28px;
|
||||||
border-bottom: 2px solid $theme_color;
|
font-weight: 600;
|
||||||
}
|
|
||||||
.recommend {
|
|
||||||
margin: 20px auto;
|
|
||||||
font-weight: bold;
|
|
||||||
width: 200px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.coupon-item {
|
|
||||||
overflow: hidden;
|
|
||||||
$claim-width: 55px;
|
|
||||||
|
|
||||||
.c-left {
|
.coupon-list {
|
||||||
width: calc(100% - #{$claim-width});
|
gap: 16px;
|
||||||
box-sizing: border-box;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-item {
|
||||||
|
width: calc((100% - 32px) / 3);
|
||||||
|
height: auto;
|
||||||
|
min-height: 132px;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
b,
|
||||||
|
i.circle-top,
|
||||||
|
i.circle-bottom {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
b {
|
.c-left {
|
||||||
right: $claim-width;
|
flex: 1;
|
||||||
width: 0;
|
width: auto;
|
||||||
background: none;
|
min-width: 0;
|
||||||
border-right: 1px dashed #e0e0e0;
|
padding: 16px 16px 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.coupon-amount {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.describe {
|
||||||
|
margin-left: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: #171A26;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scope-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #fff5f4;
|
||||||
|
color: $theme_color;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-name span:last-child {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-time {
|
||||||
|
margin: 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-right {
|
.c-right {
|
||||||
width: $claim-width;
|
position: relative;
|
||||||
|
right: auto;
|
||||||
|
top: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 48px;
|
||||||
|
height: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
writing-mode: vertical-rl;
|
writing-mode: vertical-rl;
|
||||||
text-orientation: mixed;
|
|
||||||
letter-spacing: 4px;
|
letter-spacing: 4px;
|
||||||
font-size: 16px;
|
white-space: nowrap;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.9;
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-finished {
|
||||||
|
background: #c8c8c8;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.circle-top,
|
|
||||||
i.circle-bottom {
|
|
||||||
right: calc(#{$claim-width} - 9px);
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.circle-top {
|
|
||||||
top: -9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.circle-bottom {
|
|
||||||
bottom: -9px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageration {
|
.pageration {
|
||||||
text-align: right;
|
display: flex;
|
||||||
padding-bottom: 10px;
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,27 +6,10 @@
|
|||||||
<div class="shop-nav-container">
|
<div class="shop-nav-container">
|
||||||
<el-breadcrumb>
|
<el-breadcrumb>
|
||||||
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
|
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
|
||||||
<el-breadcrumb-item v-for="(item, index) in categoryBar" :to="goGoodsList(index)" target="_blank" :key="index">
|
<el-breadcrumb-item v-for="(item, index) in categoryBar" :to="goGoodsList(index)" :key="index">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
<div class="store-collect" v-if="!takeDownSale && goodsMsg.data">
|
|
||||||
<span class="mr_10" v-if="goodsMsg.data">
|
|
||||||
<router-link
|
|
||||||
class="store-name-link"
|
|
||||||
:to="{ path: '/merchant', query: { id: goodsMsg.data.storeId } }"
|
|
||||||
>{{ goodsMsg.data.storeName }}</router-link>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="store-collect-btn"
|
|
||||||
:class="{ 'is-collected': storeCollected }"
|
|
||||||
@click="collect"
|
|
||||||
>
|
|
||||||
<el-icon class="store-collect-icon"><StarFilled /></el-icon>
|
|
||||||
{{ storeCollected? "已收藏店铺": "收藏店铺" }}
|
|
||||||
</span>
|
|
||||||
<span class="ml_10 contact-service-btn" @click="IMService(goodsMsg.data.storeId,goodsMsg.data.goodsId,goodsMsg.data.id)">联系客服</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -48,21 +31,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { StarFilled } from '@element-plus/icons-vue';
|
|
||||||
import Search from "@/components/Search";
|
import Search from "@/components/Search";
|
||||||
|
|
||||||
import ShowGoods from "@/components/goodsDetail/ShowGoods";
|
import ShowGoods from "@/components/goodsDetail/ShowGoods";
|
||||||
import empty from "@/components/empty/Main";
|
import empty from "@/components/empty/Main";
|
||||||
import ShowGoodsDetail from "@/components/goodsDetail/ShowGoodsDetail";
|
import ShowGoodsDetail from "@/components/goodsDetail/ShowGoodsDetail";
|
||||||
import { goodsSkuDetail } from "@/api/goods";
|
import { goodsSkuDetail } from "@/api/goods";
|
||||||
import {
|
import { getGoodsDistribution } from "@/api/member";
|
||||||
cancelStoreCollect,
|
|
||||||
collectStore,
|
|
||||||
isStoreCollection,
|
|
||||||
getGoodsDistribution,
|
|
||||||
} from "@/api/member";
|
|
||||||
import { getDetailById } from "@/api/shopentry";
|
|
||||||
import imTalk from '@/components/mixes/talkIm'
|
|
||||||
export default {
|
export default {
|
||||||
name: "GoodsDetail",
|
name: "GoodsDetail",
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter (to, from, next) {
|
||||||
@@ -72,14 +47,11 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
this.getGoodsDetail();
|
this.getGoodsDetail();
|
||||||
},
|
},
|
||||||
mixins: [imTalk],
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
goodsMsg: {}, // 商品信息
|
goodsMsg: {}, // 商品信息
|
||||||
isLoading: false, // 加载状态
|
isLoading: false, // 加载状态
|
||||||
categoryBar: [], // 分类
|
categoryBar: [], // 分类
|
||||||
storeCollected: false, // 商品收藏
|
|
||||||
storeMsg: {}, // 店铺信息
|
|
||||||
takeDownSale:false, // 是否下架
|
takeDownSale:false, // 是否下架
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -140,24 +112,6 @@ export default {
|
|||||||
if (!this.goodsMsg.data.intro) {
|
if (!this.goodsMsg.data.intro) {
|
||||||
this.goodsMsg.data.intro = ''
|
this.goodsMsg.data.intro = ''
|
||||||
}
|
}
|
||||||
// 判断是否收藏(须同时有 token,避免未登录/ token 失效时误跳登录页)
|
|
||||||
if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
|
|
||||||
isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => {
|
|
||||||
if (res.success && res.result) {
|
|
||||||
this.storeCollected = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.storeMsg) {
|
|
||||||
// 获取店铺信息
|
|
||||||
getDetailById(this.goodsMsg.data.storeId).then((res) => {
|
|
||||||
if (res.success) {
|
|
||||||
this.storeMsg = res.result;
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.message);
|
Message.error(res.message);
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
@@ -183,37 +137,12 @@ export default {
|
|||||||
query: { categoryId: arr.toString() },
|
query: { categoryId: arr.toString() },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async collect () {
|
|
||||||
// 收藏店铺
|
|
||||||
const storeId = this.goodsMsg.data && this.goodsMsg.data.storeId;
|
|
||||||
if (!storeId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.Cookies.getItem("userInfo")) {
|
|
||||||
Message.warning("请先登录");
|
|
||||||
this.$router.push("/login");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.storeCollected) {
|
|
||||||
let cancel = await cancelStoreCollect("STORE", storeId);
|
|
||||||
if (cancel.success) {
|
|
||||||
Message.success("已取消收藏");
|
|
||||||
this.storeCollected = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let collect = await collectStore("STORE", storeId);
|
|
||||||
if (collect.code === 200) {
|
|
||||||
this.storeCollected = true;
|
|
||||||
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {},
|
watch: {},
|
||||||
components: { Search,
|
components: { Search,
|
||||||
ShowGoods,
|
ShowGoods,
|
||||||
ShowGoodsDetail,
|
ShowGoodsDetail,
|
||||||
empty, StarFilled },
|
empty },
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -248,52 +177,6 @@ export default {
|
|||||||
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
|
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
|
||||||
color: #171A26;
|
color: #171A26;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-collect {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
color: #171A26;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.store-collect-btn,
|
|
||||||
.contact-service-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #171A26;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $theme_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-collect-btn {
|
|
||||||
.store-collect-icon {
|
|
||||||
color: #999;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-collected,
|
|
||||||
&.is-collected .store-collect-icon {
|
|
||||||
color: $theme_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-name-link {
|
|
||||||
color: #438cde;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $theme_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.sale-btn{
|
.sale-btn{
|
||||||
margin:10px 0
|
margin:10px 0
|
||||||
|
|||||||
@@ -21,32 +21,34 @@
|
|||||||
>
|
>
|
||||||
<span :class="{ 'goods-list-tool-active': index === sortIndex }">
|
<span :class="{ 'goods-list-tool-active': index === sortIndex }">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
<el-icon><ArrowDown /></el-icon>
|
<el-icon v-if="index === sortIndex" class="sort-arrow"><ArrowDown /></el-icon>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li @click="orderBy('price', 5, 'up')" class="price-sort">
|
<li @click="orderBy('price', priceSortIndex)" class="price-sort">
|
||||||
<span :class="{ 'goods-list-tool-active': 5 === sortIndex }">
|
<span :class="{ 'goods-list-tool-active': priceSortIndex === sortIndex }">
|
||||||
价格
|
价格
|
||||||
<span class="price-arrows">
|
<span class="price-arrows">
|
||||||
<el-icon :class="{ 'price-color': sortPriceIndex == 'desc' }"><ArrowUp /></el-icon>
|
<el-icon :class="{ 'is-on': priceSortIndex === sortIndex && sortPriceIndex === 'asc' }"><ArrowUp /></el-icon>
|
||||||
<el-icon :class="{ 'price-color': sortPriceIndex == 'asc' }"><ArrowDown /></el-icon>
|
<el-icon :class="{ 'is-on': priceSortIndex === sortIndex && sortPriceIndex === 'desc' }"><ArrowDown /></el-icon>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<div class="goods-list">
|
<div v-if="searched && !loading && goodsList.length === 0" class="list-empty">
|
||||||
<empty v-if="goodsList.length === 0" />
|
<empty :_Title="emptyTitle" />
|
||||||
|
<div v-if="recommendList.length" class="recommend-title">为你推荐</div>
|
||||||
|
</div>
|
||||||
|
<div class="goods-list" v-if="displayGoods.length">
|
||||||
<div
|
<div
|
||||||
v-else
|
|
||||||
class="goods-show-info"
|
class="goods-show-info"
|
||||||
v-for="(item, index) in goodsList"
|
v-for="(item, index) in displayGoods"
|
||||||
:key="index"
|
:key="item.id || index"
|
||||||
@click="goGoodsDetail(item.id, item.goodsId)"
|
@click="goGoodsDetail(item.id, item.goodsId)"
|
||||||
>
|
>
|
||||||
<div class="goods-show-img">
|
<div class="goods-show-img">
|
||||||
<img :src="item.thumbnail" />
|
<GoodsThumb :src="item.thumbnail" :alt="item.goodsName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-price">
|
<div class="goods-show-price">
|
||||||
<span>
|
<span>
|
||||||
@@ -85,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-page">
|
<div class="goods-page" v-if="total > 0">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="params.pageNumber"
|
v-model:current-page="params.pageNumber"
|
||||||
v-model:page-size="params.pageSize"
|
v-model:page-size="params.pageSize"
|
||||||
@@ -105,6 +107,7 @@
|
|||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||||
import GoodsClassNav from "@/components/nav/GoodsClassNav";
|
import GoodsClassNav from "@/components/nav/GoodsClassNav";
|
||||||
|
import GoodsThumb from "@/components/goods/GoodsThumb.vue";
|
||||||
import * as apiGoods from "@/api/goods";
|
import * as apiGoods from "@/api/goods";
|
||||||
export default {
|
export default {
|
||||||
name: "GoodsList",
|
name: "GoodsList",
|
||||||
@@ -115,7 +118,10 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
sortIndex: 0, // 排序状态
|
sortIndex: 0, // 排序状态
|
||||||
|
priceSortIndex: 4,
|
||||||
sortPriceIndex: false, // 判断价格升序还是降序
|
sortPriceIndex: false, // 判断价格升序还是降序
|
||||||
|
searched: false,
|
||||||
|
recommendList: [],
|
||||||
goodsTool: [
|
goodsTool: [
|
||||||
// 排序类型
|
// 排序类型
|
||||||
{ title: "综合", en: "" },
|
{ title: "综合", en: "" },
|
||||||
@@ -154,6 +160,16 @@ export default {
|
|||||||
this.getGoodsList();
|
this.getGoodsList();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
displayGoods() {
|
||||||
|
if (this.goodsList.length) return this.goodsList;
|
||||||
|
return this.searched && !this.loading ? this.recommendList : [];
|
||||||
|
},
|
||||||
|
emptyTitle() {
|
||||||
|
const keyword = this.params.keyword;
|
||||||
|
return keyword ? `没有找到与“${keyword}”相关的商品` : "暂无商品";
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 搜索
|
// 搜索
|
||||||
handleSearch(key) {
|
handleSearch(key) {
|
||||||
@@ -204,11 +220,10 @@ export default {
|
|||||||
Message.warning("店铺信息不存在");
|
Message.warning("店铺信息不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/merchant",
|
path: "/merchant",
|
||||||
query: { id },
|
query: { id },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
// 分页 修改页码
|
// 分页 修改页码
|
||||||
changePageNum(val) {
|
changePageNum(val) {
|
||||||
@@ -230,14 +245,28 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.goodsList = res.result.records;
|
this.goodsList = res.result.records || [];
|
||||||
this.total = res.result.total;
|
this.total = res.result.total;
|
||||||
|
if (!this.goodsList.length) this.loadRecommend();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
this.goodsList = [];
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.searched = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadRecommend() {
|
||||||
|
if (this.recommendList.length) return;
|
||||||
|
apiGoods
|
||||||
|
.goodsList({ pageNumber: 1, pageSize: 10, sort: "buyCount", order: "desc" })
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) this.recommendList = res.result.records || [];
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
getParams(val) {
|
getParams(val) {
|
||||||
// 筛选条件回显
|
// 筛选条件回显
|
||||||
Object.assign(this.params, val);
|
Object.assign(this.params, val);
|
||||||
@@ -256,6 +285,7 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GoodsClassNav,
|
GoodsClassNav,
|
||||||
|
GoodsThumb,
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
},
|
},
|
||||||
@@ -395,8 +425,12 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-color {
|
.el-icon {
|
||||||
color: #b3b3b3;
|
color: #c8c8c8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon.is-on {
|
||||||
|
color: $theme_color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,9 +467,30 @@ export default {
|
|||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
.price-color {
|
.sort-arrow {
|
||||||
color: $theme_color !important;
|
color: $theme_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-arrows .el-icon {
|
||||||
|
color: #c8c8c8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-arrows .el-icon.is-on {
|
||||||
|
color: $theme_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-empty {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend-title {
|
||||||
|
padding: 4px 16px 12px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-list {
|
.goods-list {
|
||||||
|
|||||||
@@ -92,9 +92,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="foot">
|
<div class="foot">
|
||||||
<div type="flex" justify="space-around" class="help">
|
<div type="flex" justify="space-around" class="help">
|
||||||
<router-link to="/article" class="item" target="_blank">帮助</router-link>
|
<router-link to="/article" class="item">帮助</router-link>
|
||||||
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
|
<router-link to="/article?id=1371779927900160000" class="item">隐私</router-link>
|
||||||
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
|
<router-link to="/article?id=1371992704333905920" class="item">条款</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div type="flex" justify="center" class="copyright">
|
<div type="flex" justify="center" class="copyright">
|
||||||
Copyright © {{year}} - Present
|
Copyright © {{year}} - Present
|
||||||
@@ -601,3 +601,5 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
44
buyer/src/pages/NotFound.vue
Normal file
44
buyer/src/pages/NotFound.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-found-page">
|
||||||
|
<BaseHeader></BaseHeader>
|
||||||
|
<Search :show-tag="false"></Search>
|
||||||
|
<div class="not-found">
|
||||||
|
<h1>页面不存在</h1>
|
||||||
|
<p>你访问的地址没有对应页面</p>
|
||||||
|
<el-button type="primary" @click="$router.push('/')">返回首页</el-button>
|
||||||
|
</div>
|
||||||
|
<BaseFooter></BaseFooter>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "NotFound",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.not-found {
|
||||||
|
width: 1200px;
|
||||||
|
min-height: 420px;
|
||||||
|
margin: 24px auto 40px;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-found p {
|
||||||
|
margin: 12px 0 24px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button class="signup-btn" @click="handleRegist">注册</el-button>
|
<el-button class="signup-btn" @click="handleRegist">注册</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<p class="agreement">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920" target="_blank">商城用户协议</router-link>》</p>
|
<p class="agreement">点击注册,表示您同意《<router-link to="/article?id=1371992704333905920">商城用户协议</router-link>》</p>
|
||||||
</el-form>
|
</el-form>
|
||||||
<Verify
|
<Verify
|
||||||
ref="verify"
|
ref="verify"
|
||||||
@@ -78,9 +78,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="foot">
|
<div class="foot">
|
||||||
<div type="flex" justify="space-around" class="help">
|
<div type="flex" justify="space-around" class="help">
|
||||||
<router-link to="/article" class="item" target="_blank">帮助</router-link>
|
<router-link to="/article" class="item">帮助</router-link>
|
||||||
<router-link to="/article?id=1371779927900160000" class="item" target="_blank">隐私</router-link>
|
<router-link to="/article?id=1371779927900160000" class="item">隐私</router-link>
|
||||||
<router-link to="/article?id=1371992704333905920" class="item" target="_blank">条款</router-link>
|
<router-link to="/article?id=1371992704333905920" class="item">条款</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div type="flex" justify="center" class="copyright">
|
<div type="flex" justify="center" class="copyright">
|
||||||
Copyright © {{year}} - Present
|
Copyright © {{year}} - Present
|
||||||
|
|||||||
@@ -1,35 +1,271 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="member-home">
|
||||||
<div class="userBox">
|
<div class="home-head">
|
||||||
<div class="box">
|
<div>
|
||||||
<!-- 我的订单组件 -->
|
<div class="home-hello">你好,{{ displayName }}</div>
|
||||||
<myOrderPage :homePage="true" />
|
<div class="home-sub">查看订单进度、优惠券和账户余额</div>
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<!-- 近期收藏 -->
|
|
||||||
<myFavorites :homePage="true" />
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="msg-entry" @click="go('MsgList')">
|
||||||
|
<el-icon :size="18"><Bell /></el-icon>
|
||||||
|
<span>我的消息</span>
|
||||||
|
<i v-if="unreadMsg > 0" class="msg-badge">{{ unreadLabel }}</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-grid">
|
||||||
|
<button
|
||||||
|
v-for="item in orderStats"
|
||||||
|
:key="item.key"
|
||||||
|
type="button"
|
||||||
|
class="stat-card"
|
||||||
|
@click="item.action()"
|
||||||
|
>
|
||||||
|
<span class="stat-count">{{ item.count }}</span>
|
||||||
|
<span class="stat-label">{{ item.label }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="asset-grid">
|
||||||
|
<button type="button" class="asset-card" @click="go('Coupons')">
|
||||||
|
<div>
|
||||||
|
<div class="asset-label">优惠券</div>
|
||||||
|
<div class="asset-value">{{ couponCount }}<span>张可用</span></div>
|
||||||
|
</div>
|
||||||
|
<span class="asset-link">查看</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="asset-card" @click="go('MoneyManagement')">
|
||||||
|
<div>
|
||||||
|
<div class="asset-label">账户余额</div>
|
||||||
|
<div class="asset-value">¥{{ $filters.unitPrice(balance) }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="asset-link">查看</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import myOrderPage from '@/pages/home/orderCenter/MyOrder'
|
import { Bell } from "@element-plus/icons-vue";
|
||||||
import myFavorites from '@/pages/home/memberCenter/Favorites'
|
import storage from "@/plugins/storage";
|
||||||
|
import { getOrderList } from "@/api/order";
|
||||||
|
import { afterSaleList, getMembersWallet, memberCouponList } from "@/api/member";
|
||||||
|
import { refreshUnreadMessage } from "@/utils/unreadMessage";
|
||||||
|
|
||||||
|
function totalOf(res) {
|
||||||
|
return res && res.success ? Number(res.result && res.result.total) || 0 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'main',
|
name: "UserCenterHome",
|
||||||
components: {
|
components: { Bell },
|
||||||
myOrderPage,
|
data() {
|
||||||
myFavorites
|
return {
|
||||||
}
|
displayName: "会员",
|
||||||
}
|
waitPay: 0,
|
||||||
|
waitShip: 0,
|
||||||
|
waitReceive: 0,
|
||||||
|
afterSale: 0,
|
||||||
|
couponCount: 0,
|
||||||
|
balance: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
unreadMsg() {
|
||||||
|
return Number(this.$store.state.unreadMsg) || 0;
|
||||||
|
},
|
||||||
|
unreadLabel() {
|
||||||
|
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
|
||||||
|
},
|
||||||
|
orderStats() {
|
||||||
|
return [
|
||||||
|
{ key: "pay", label: "待付款", count: this.waitPay, action: () => this.goOrder("WAIT_PAY") },
|
||||||
|
{ key: "ship", label: "待发货", count: this.waitShip, action: () => this.goOrder("WAIT_SHIP") },
|
||||||
|
{ key: "receive", label: "待收货", count: this.waitReceive, action: () => this.goOrder("WAIT_USE") },
|
||||||
|
{ key: "after", label: "售后", count: this.afterSale, action: () => this.go("AfterSale") },
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.readUser();
|
||||||
|
this.loadSummary();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
readUser() {
|
||||||
|
try {
|
||||||
|
const raw = storage.getItem("userInfo");
|
||||||
|
const user = raw ? JSON.parse(raw) : {};
|
||||||
|
this.displayName = user.nickName || user.username || "会员";
|
||||||
|
} catch {
|
||||||
|
this.displayName = "会员";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadSummary() {
|
||||||
|
const page = { pageNumber: 1, pageSize: 1 };
|
||||||
|
Promise.all([
|
||||||
|
getOrderList({ ...page, tag: "WAIT_PAY" }).catch(() => null),
|
||||||
|
getOrderList({ ...page, tag: "WAIT_SHIP" }).catch(() => null),
|
||||||
|
getOrderList({ ...page, tag: "WAIT_USE" }).catch(() => null),
|
||||||
|
afterSaleList({ ...page, sort: "createTime", order: "desc" }).catch(() => null),
|
||||||
|
memberCouponList({ ...page, memberCouponStatus: "NEW" }).catch(() => null),
|
||||||
|
getMembersWallet().catch(() => null),
|
||||||
|
refreshUnreadMessage(),
|
||||||
|
]).then(([pay, ship, receive, afterSale, coupon, wallet]) => {
|
||||||
|
this.waitPay = totalOf(pay);
|
||||||
|
this.waitShip = totalOf(ship);
|
||||||
|
this.waitReceive = totalOf(receive);
|
||||||
|
this.afterSale = totalOf(afterSale);
|
||||||
|
this.couponCount = totalOf(coupon);
|
||||||
|
this.balance = wallet && wallet.success ? wallet.result.memberWallet || 0 : 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
go(name) {
|
||||||
|
this.$router.push({ name });
|
||||||
|
},
|
||||||
|
goOrder(tag) {
|
||||||
|
this.$router.push({ name: "MyOrder", query: { tag } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.userBox {
|
.member-home {
|
||||||
padding: 0 0 20px 0;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 4px 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hello {
|
||||||
|
color: #333;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-entry {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $theme_color;
|
||||||
|
border-color: $theme_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-badge {
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $theme_color;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-grid,
|
||||||
|
.asset-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card,
|
||||||
|
.asset-card {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $theme_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 18px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-count {
|
||||||
|
color: $theme_color;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-label {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-value {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 28px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-link {
|
||||||
|
color: $theme_color;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,44 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<UserCenterLayout>
|
<UserCenterLayout>
|
||||||
<div class="safeList">
|
<div class="safe-list">
|
||||||
<!-- 密码 -->
|
<div class="safe-card">
|
||||||
<div class="safeItem">
|
<div class="safe-icon">
|
||||||
<div class="safeItem-icon">
|
<el-icon :size="22"><Key /></el-icon>
|
||||||
<el-icon :size="40"><Key /></el-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="safeItem-content">
|
<div class="safe-body">
|
||||||
<div class="setDivItem">登录密码</div>
|
<p class="safe-title">登录密码</p>
|
||||||
<div class="setDivItem theme">互联网账号存在被盗风险,建议您定期更改密码以保护账户安全。</div>
|
<p class="safe-desc">互联网账号存在被盗风险,建议您定期更改密码以保护账户安全。</p>
|
||||||
</div>
|
|
||||||
<div class="safeItem-action">
|
|
||||||
<el-button @click="modifyPwd">修改密码</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<el-button @click="modifyPwd">修改密码</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="safeItem safeItem-connect">
|
<div class="safe-card safe-card-connect">
|
||||||
<div class="safeItem-main">
|
<div class="safe-card-main">
|
||||||
<div class="safeItem-icon">
|
<div class="safe-icon">
|
||||||
<el-icon :size="40"><Link /></el-icon>
|
<el-icon :size="22"><Link /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="safeItem-content">
|
<div class="safe-body">
|
||||||
<div class="setDivItem">第三方账户绑定</div>
|
<p class="safe-title">第三方账户绑定</p>
|
||||||
<div class="setDivItem theme">绑定后可使用第三方账号快捷登录。</div>
|
<p class="safe-desc">绑定后可使用第三方账号快捷登录。</p>
|
||||||
</div>
|
|
||||||
<div class="safeItem-action">
|
|
||||||
<el-button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</el-button>
|
|
||||||
<el-button v-else @click="confirmUnbind(wechatConnect)">解绑</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<el-button v-if="!isBound(wechatConnect.type)" type="primary" @click="openBindModal(wechatConnect)">绑定</el-button>
|
||||||
|
<el-button v-else @click="confirmUnbind(wechatConnect)">解绑</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="connectList">
|
<div class="connect-row">
|
||||||
<div class="connectRow">
|
<span class="connect-name">{{ wechatConnect.label }}</span>
|
||||||
<div class="connectRowLeft">
|
<span class="connect-status" :class="{ 'is-bound': isBound(wechatConnect.type) }">
|
||||||
<span class="connectName">{{ wechatConnect.label }}</span>
|
{{ isBound(wechatConnect.type) ? "已绑定" : "未绑定" }}
|
||||||
<el-tag v-if="isBound(wechatConnect.type)" color="success">已绑定</el-tag>
|
</span>
|
||||||
<el-tag v-else>未绑定</el-tag>
|
<el-skeleton v-if="connectLoading" animated class="connect-skeleton" />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-skeleton size="large" fix v-if="connectLoading"></el-skeleton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,73 +265,101 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.theme {
|
.safe-list {
|
||||||
color: $theme_color;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setDivItem {
|
.safe-card,
|
||||||
line-height: 1.75;
|
.safe-card-main {
|
||||||
}
|
|
||||||
|
|
||||||
.safeItem {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
border-bottom: 1px solid $border_color;
|
|
||||||
padding: 16px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.safeItem-connect {
|
.safe-card {
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-card-connect {
|
||||||
display: block;
|
display: block;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.safeItem-main {
|
.safe-card-main {
|
||||||
display: flex;
|
padding: 20px 20px 16px;
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.safeItem-icon {
|
.safe-icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 56px;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff5f4;
|
||||||
|
color: $theme_color;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: $theme_color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.safeItem-content {
|
.safe-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.safeItem-action {
|
.safe-title {
|
||||||
flex-shrink: 0;
|
margin: 0;
|
||||||
display: flex;
|
color: #171A26;
|
||||||
align-items: center;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connectList {
|
.safe-desc {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 8px;
|
|
||||||
margin-left: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectRow {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connectRowLeft {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin: 0 20px 16px 84px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connectName {
|
.connect-name {
|
||||||
color: $title_color;
|
color: #171A26;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-status {
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-status.is-bound {
|
||||||
|
background: #f0faf3;
|
||||||
|
color: #1f9d55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-skeleton {
|
||||||
|
width: 120px;
|
||||||
|
height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wechatQrBox {
|
.wechatQrBox {
|
||||||
|
|||||||
@@ -280,3 +280,9 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -200,11 +200,10 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
goShop(id) { // 跳转店铺页面
|
goShop(id) { // 跳转店铺页面
|
||||||
let url = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/merchant',
|
path: '/merchant',
|
||||||
query: { id }
|
query: { id }
|
||||||
})
|
})
|
||||||
window.open(url.href, '_blank')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Message, Modal } from "@/utils/message";
|
import { Message, Modal } from "@/utils/message";
|
||||||
import { delMemberMsg, getMemberMessage, readMemberMsg } from '@/api/member.js';
|
import { delMemberMsg, getMemberMessage, readMemberMsg } from '@/api/member.js';
|
||||||
|
import { refreshUnreadMessage } from '@/utils/unreadMessage';
|
||||||
|
|
||||||
const CACHE_KEY = 'buyer-member-message';
|
const CACHE_KEY = 'buyer-member-message';
|
||||||
|
|
||||||
@@ -71,7 +72,10 @@ export default {
|
|||||||
markRead () {
|
markRead () {
|
||||||
if (!this.detail.id || this.detail.status !== 'UN_READY') return
|
if (!this.detail.id || this.detail.status !== 'UN_READY') return
|
||||||
readMemberMsg(this.detail.id).then(res => {
|
readMemberMsg(this.detail.id).then(res => {
|
||||||
if (res && res.success) this.detail.status = 'ALREADY_READY'
|
if (res && res.success) {
|
||||||
|
this.detail.status = 'ALREADY_READY'
|
||||||
|
refreshUnreadMessage()
|
||||||
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
},
|
},
|
||||||
goBack () {
|
goBack () {
|
||||||
@@ -86,6 +90,7 @@ export default {
|
|||||||
delMemberMsg(this.detail.id).then((res) => {
|
delMemberMsg(this.detail.id).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
Message.success('消息已成功放入回收站')
|
Message.success('消息已成功放入回收站')
|
||||||
|
refreshUnreadMessage()
|
||||||
this.goBack()
|
this.goBack()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ export default {
|
|||||||
memberMsgList(this.params).then(res => {
|
memberMsgList(this.params).then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.messageData = res.result;
|
this.messageData = res.result;
|
||||||
|
if (this.params.status === 'UN_READY') {
|
||||||
|
this.$store.commit('SET_UNREAD_MSG', Number(res.result.total) || 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -212,11 +212,10 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
shopPage(id) {
|
shopPage(id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/merchant',
|
path: '/merchant',
|
||||||
query: {id: id}
|
query: {id: id}
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, '_blank');
|
|
||||||
},
|
},
|
||||||
change(index) {
|
change(index) {
|
||||||
this.activeTab = index
|
this.activeTab = index
|
||||||
|
|||||||
@@ -211,11 +211,10 @@ export default {
|
|||||||
},
|
},
|
||||||
shopPage (id) {
|
shopPage (id) {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
const routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/merchant',
|
path: '/merchant',
|
||||||
query: { id }
|
query: { id }
|
||||||
})
|
})
|
||||||
window.open(routeUrl.href, '_blank')
|
|
||||||
},
|
},
|
||||||
filterOrderStatus (status) { // 获取订单状态中文
|
filterOrderStatus (status) { // 获取订单状态中文
|
||||||
const ob = this.afterSaleStatusList.filter(e => { return e.status === status });
|
const ob = this.afterSaleStatusList.filter(e => { return e.status === status });
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export default {
|
|||||||
// 状态数组
|
// 状态数组
|
||||||
orderStatusList,
|
orderStatusList,
|
||||||
afterSaleStatusList,
|
afterSaleStatusList,
|
||||||
changeWay: ['全部', '待支付', '待发货', '待收货/使用'],
|
changeWay: ['全部', '待付款', '待发货', '待收货/使用'],
|
||||||
activeOrderTab: 0,
|
activeOrderTab: 0,
|
||||||
total: 0, // 数据总数
|
total: 0, // 数据总数
|
||||||
spinShow: false, // 加载状态
|
spinShow: false, // 加载状态
|
||||||
@@ -235,8 +235,18 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
if (this.homePage) this.params.pageSize = 5;
|
if (this.homePage) this.params.pageSize = 5;
|
||||||
|
if (!this.homePage) this.applyRouteTag();
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query.tag' () {
|
||||||
|
if (this.homePage || this.$route.name !== 'MyOrder') return;
|
||||||
|
const next = this.$route.query.tag || 'ALL';
|
||||||
|
if (this.params.tag === next) return;
|
||||||
|
this.applyRouteTag();
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 退款状态枚举
|
// 退款状态枚举
|
||||||
refundPriceList(status) {
|
refundPriceList(status) {
|
||||||
@@ -261,22 +271,39 @@ export default {
|
|||||||
});
|
});
|
||||||
window.open(routeUrl.href, '_blank');
|
window.open(routeUrl.href, '_blank');
|
||||||
},
|
},
|
||||||
|
orderTags () {
|
||||||
|
return ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
|
||||||
|
},
|
||||||
|
applyRouteTag () {
|
||||||
|
const tags = this.orderTags();
|
||||||
|
const tag = this.$route.query.tag;
|
||||||
|
const index = tags.indexOf(tag);
|
||||||
|
this.activeOrderTab = index > -1 ? index : 0;
|
||||||
|
this.params.tag = tags[this.activeOrderTab];
|
||||||
|
this.params.pageNumber = 1;
|
||||||
|
},
|
||||||
// 切换订单状态
|
// 切换订单状态
|
||||||
change (index) {
|
change (index) {
|
||||||
this.activeOrderTab = index;
|
this.activeOrderTab = index;
|
||||||
this.params.pageNumber = 1;
|
this.params.pageNumber = 1;
|
||||||
delete this.params.commentStatus;
|
delete this.params.commentStatus;
|
||||||
const tags = ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
|
const tags = this.orderTags();
|
||||||
this.params.tag = tags[index] || 'ALL';
|
this.params.tag = tags[index] || 'ALL';
|
||||||
|
const query = { ...this.$route.query };
|
||||||
|
if (index === 0) delete query.tag;
|
||||||
|
else query.tag = this.params.tag;
|
||||||
|
const current = this.$route.query.tag || 'ALL';
|
||||||
|
if (current !== this.params.tag) {
|
||||||
|
this.$router.replace({ name: 'MyOrder', query });
|
||||||
|
}
|
||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
shopPage (id) {
|
shopPage (id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: '/merchant',
|
path: '/merchant',
|
||||||
query: { id: id }
|
query: { id: id }
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, '_blank');
|
|
||||||
},
|
},
|
||||||
orderDetail (sn) {
|
orderDetail (sn) {
|
||||||
this.$router.push({ name: 'OrderDetail', query: {sn} });
|
this.$router.push({ name: 'OrderDetail', query: {sn} });
|
||||||
|
|||||||
@@ -589,11 +589,10 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
shopPage(id) {
|
shopPage(id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/merchant",
|
path: "/merchant",
|
||||||
query: { id: id },
|
query: { id: id },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
async getDetail() {
|
async getDetail() {
|
||||||
// 获取订单详情
|
// 获取订单详情
|
||||||
|
|||||||
@@ -72,11 +72,10 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
shopPage(id) {
|
shopPage(id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/merchant",
|
path: "/merchant",
|
||||||
query: { id: id },
|
query: { id: id },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
clearAll() {
|
clearAll() {
|
||||||
// 清除全部足迹
|
// 清除全部足迹
|
||||||
|
|||||||
@@ -836,11 +836,10 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转店铺首页
|
// 跳转店铺首页
|
||||||
goShopPage(id) {
|
goShopPage(id) {
|
||||||
let routeUrl = this.$router.resolve({
|
this.$router.push({
|
||||||
path: "/merchant",
|
path: "/merchant",
|
||||||
query: { id: id },
|
query: { id: id },
|
||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
|
||||||
},
|
},
|
||||||
useCoupon(id, used) {
|
useCoupon(id, used) {
|
||||||
// 使用优惠券
|
// 使用优惠券
|
||||||
|
|||||||
@@ -29,46 +29,32 @@
|
|||||||
<div v-else class="goods-show-info1" v-for="(item, index) in goodsList" :key="index"
|
<div v-else class="goods-show-info1" v-for="(item, index) in goodsList" :key="index"
|
||||||
@click="goGoodsDetail(item.skuId, item.goodsId)">
|
@click="goGoodsDetail(item.skuId, item.goodsId)">
|
||||||
<div class="goods-show-img">
|
<div class="goods-show-img">
|
||||||
<img width="200" height="200" :src="item.goodsImage" />
|
<img :src="item.goodsImage" :alt="item.goodsName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-price">
|
<div class="goods-show-price">
|
||||||
<span>
|
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
|
||||||
<span class="seckill-price text-danger">{{ $filters.unitPrice(item.price, "¥") }}</span>
|
<span class="origin-price">{{ $filters.unitPrice(item.originalPrice, "¥") }}</span>
|
||||||
<span style="color: #999; text-decoration: line-through">{{ $filters.unitPrice(item.originalPrice, "¥") }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-detail" style="margin-bottom: 20px">
|
<div class="goods-show-detail">
|
||||||
<span>{{ item.goodsName }}</span>
|
<span>{{ item.goodsName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-seckill-btn" :class="{
|
|
||||||
'goods-seckill-btn-gray': nowHour < list[currIndex].timeLine,
|
|
||||||
}">
|
|
||||||
{{ nowHour >= list[currIndex].timeLine ? "立即抢购" : "即将开始" }}
|
|
||||||
</div>
|
|
||||||
<div v-if="nowHour >= list[currIndex].timeLine && item.quantity <= item.salesNum"
|
|
||||||
class="goods-seckill-btn goods-seckill-btn-gray">
|
|
||||||
已售罄
|
|
||||||
</div>
|
|
||||||
<div v-if="nowHour >= list[currIndex].timeLine" class="goods-show-num1">
|
<div v-if="nowHour >= list[currIndex].timeLine" class="goods-show-num1">
|
||||||
<span>已售{{
|
<span>已售{{ salePercent(item) }}%</span>
|
||||||
(item.quantity && item.quantity > 0
|
<el-progress
|
||||||
? Math.ceil(
|
|
||||||
(item.salesNum / (item.quantity)) * 100
|
|
||||||
)
|
|
||||||
: 100) + "%"
|
|
||||||
}}</span><el-progress
|
|
||||||
:show-text="false"
|
:show-text="false"
|
||||||
color="#df0021"
|
:color="'var(--theme-color, #ff3c2a)'"
|
||||||
style="width: 110px"
|
:percentage="salePercent(item)"
|
||||||
class="ml_10"
|
|
||||||
:percentage="item.quantity && item.quantity > 0
|
|
||||||
? Math.ceil((item.salesNum / item.quantity) * 100)
|
|
||||||
: 100"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-seller">
|
<div class="goods-show-seller">
|
||||||
<span>{{ item.storeName }}</span>
|
<span>{{ item.storeName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="goods-seckill-btn"
|
||||||
|
:class="{ 'is-disabled': !canBuy(item) }"
|
||||||
|
>
|
||||||
|
{{ buyText(item) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,6 +128,25 @@ export default {
|
|||||||
});
|
});
|
||||||
window.open(routeUrl.href, "_blank");
|
window.open(routeUrl.href, "_blank");
|
||||||
},
|
},
|
||||||
|
// 已售占比。没有库存时按售罄展示
|
||||||
|
salePercent (item) {
|
||||||
|
if (!item.quantity || item.quantity <= 0) return 100;
|
||||||
|
return Math.min(100, Math.ceil((item.salesNum / item.quantity) * 100));
|
||||||
|
},
|
||||||
|
// 场次已开始,且销量达到库存
|
||||||
|
isSoldOut (item) {
|
||||||
|
return this.nowHour >= this.list[this.currIndex].timeLine && item.quantity <= item.salesNum;
|
||||||
|
},
|
||||||
|
// 场次已开始且未售罄才可抢
|
||||||
|
canBuy (item) {
|
||||||
|
return this.nowHour >= this.list[this.currIndex].timeLine && !this.isSoldOut(item);
|
||||||
|
},
|
||||||
|
// 按钮文案:未开始 / 已售罄 / 立即抢购
|
||||||
|
buyText (item) {
|
||||||
|
if (this.nowHour < this.list[this.currIndex].timeLine) return "即将开始";
|
||||||
|
if (this.isSoldOut(item)) return "已售罄";
|
||||||
|
return "立即抢购";
|
||||||
|
},
|
||||||
countDown (currIndex) {
|
countDown (currIndex) {
|
||||||
// 倒计时
|
// 倒计时
|
||||||
// 0点时间戳
|
// 0点时间戳
|
||||||
@@ -175,73 +180,96 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../assets/styles/goodsList.scss";
|
@import "../../assets/styles/goodsList.scss";
|
||||||
|
|
||||||
.goods-seckill-btn {
|
.seckill-list {
|
||||||
position: absolute;
|
background: #f5f5f5;
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 80px;
|
|
||||||
color: #fff;
|
|
||||||
height: 35px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 35px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: $theme_color;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
margin-right: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-seckill-btn-gray {
|
.promotion-decorate {
|
||||||
background-color: #666;
|
width: auto;
|
||||||
}
|
margin: 28px auto 16px;
|
||||||
|
color: #171A26;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
.promotion-decorate::before,
|
&::before,
|
||||||
.promotion-decorate::after {
|
&::after {
|
||||||
background-image: url("/src/assets/images/sprite@2x.png");
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-line {
|
.time-line {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
height: 60px;
|
height: auto;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: #fff;
|
background: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 0 30px;
|
flex: 1;
|
||||||
font-size: 16px;
|
width: auto;
|
||||||
font-weight: bold;
|
min-height: 78px;
|
||||||
width: 240px;
|
padding: 12px 20px;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #171A26;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
& + li {
|
||||||
|
box-shadow: inset 1px 0 0 #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-curr {
|
.not-curr {
|
||||||
border: 1px solid #999;
|
border: none;
|
||||||
border-radius: 20px;
|
border-radius: 0;
|
||||||
padding: 3px 10px;
|
padding: 0;
|
||||||
margin-left: 10px;
|
margin: 0;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-weight: normal;
|
font-weight: 400;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.currTimeline {
|
.currTimeline {
|
||||||
background-color: $theme_color;
|
background: $theme_color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
>div:nth-child(1) {
|
&:hover {
|
||||||
font-size: 20px;
|
background: $theme_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
>div:nth-child(2) {
|
> div:nth-child(1) {
|
||||||
font-size: 14px;
|
font-size: 22px;
|
||||||
margin-left: 10px;
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-curr {
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,55 +277,116 @@ export default {
|
|||||||
.seckill-goods-wrap {
|
.seckill-goods-wrap {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 16px auto 40px;
|
||||||
|
padding: 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 320px;
|
||||||
|
|
||||||
.goods-list {
|
.goods-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
column-gap: 10px;
|
column-gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.empty) {
|
||||||
|
margin: 48px 0;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-show-info1 {
|
.goods-show-info1 {
|
||||||
width: calc(25% - 8px);
|
width: calc(25% - 12px);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 6px;
|
padding: 12px;
|
||||||
margin: 10px 0;
|
margin: 0 0 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #fff;
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.goods-show-info1:hover {
|
&:hover {
|
||||||
border: 1px solid #ccc;
|
border-color: #eee;
|
||||||
box-shadow: 0px 0px 15px #ccc;
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.goods-show-img {
|
.goods-show-img {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fafafa;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 200px;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.goods-show-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.origin-price {
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-show-detail {
|
||||||
|
height: 40px;
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-show-num1 {
|
.goods-show-num1 {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 8px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-progress-bar__inner {
|
.goods-show-seller {
|
||||||
background-color: #df0021;
|
margin-bottom: 10px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-seckill-btn {
|
||||||
|
margin-top: auto;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 34px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: $theme_color;
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
background: #c8c8c8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,17 +24,25 @@
|
|||||||
<p>{{ userInfo.nickName }}</p>
|
<p>{{ userInfo.nickName }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-menu-item index="UserCenter">
|
||||||
|
<el-icon><House /></el-icon>
|
||||||
|
<span>我的主页</span>
|
||||||
|
</el-menu-item>
|
||||||
<template v-for="(menu, index) in menuList" :key="index">
|
<template v-for="(menu, index) in menuList" :key="index">
|
||||||
<el-sub-menu v-if="menu.display" :index="menu.title">
|
<el-sub-menu v-if="menu.display" :index="menu.title">
|
||||||
<template #title>
|
<template #title>
|
||||||
<el-icon><Location /></el-icon>
|
<el-icon><component :is="menuIcon(menu.icon)" /></el-icon>
|
||||||
<span>{{ menu.title }}</span>
|
<span>{{ menu.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
v-for="(chlidren, i) in menu.menus"
|
v-for="(chlidren, i) in menu.menus"
|
||||||
:key="i"
|
:key="i"
|
||||||
:index="chlidren.path"
|
:index="chlidren.path"
|
||||||
>{{ chlidren.title }}</el-menu-item>
|
>
|
||||||
|
<el-icon><component :is="menuIcon(chlidren.icon)" /></el-icon>
|
||||||
|
<span>{{ chlidren.title }}</span>
|
||||||
|
<span v-if="chlidren.path === 'MsgList' && unreadMsg > 0" class="menu-badge">{{ unreadLabel }}</span>
|
||||||
|
</el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
@@ -48,12 +56,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Location, User } from '@element-plus/icons-vue';
|
import {
|
||||||
|
Bell,
|
||||||
|
ChatDotRound,
|
||||||
|
Clock,
|
||||||
|
Coin,
|
||||||
|
Goods,
|
||||||
|
House,
|
||||||
|
List,
|
||||||
|
Location,
|
||||||
|
Lock,
|
||||||
|
Present,
|
||||||
|
RefreshLeft,
|
||||||
|
Star,
|
||||||
|
Ticket,
|
||||||
|
Trophy,
|
||||||
|
User,
|
||||||
|
Wallet,
|
||||||
|
Warning,
|
||||||
|
} from '@element-plus/icons-vue';
|
||||||
import menuList from "./menu";
|
import menuList from "./menu";
|
||||||
import Storage from "@/plugins/storage.js";
|
import Storage from "@/plugins/storage.js";
|
||||||
|
import { refreshUnreadMessage } from "@/utils/unreadMessage";
|
||||||
|
|
||||||
|
const MENU_ICONS = {
|
||||||
|
Bell,
|
||||||
|
ChatDotRound,
|
||||||
|
Clock,
|
||||||
|
Coin,
|
||||||
|
Goods,
|
||||||
|
House,
|
||||||
|
List,
|
||||||
|
Location,
|
||||||
|
Lock,
|
||||||
|
Present,
|
||||||
|
RefreshLeft,
|
||||||
|
Star,
|
||||||
|
Ticket,
|
||||||
|
Trophy,
|
||||||
|
User,
|
||||||
|
Wallet,
|
||||||
|
Warning,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Location, User },
|
components: { House, User },
|
||||||
name: "Home",
|
name: "Home",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -67,14 +114,24 @@ export default {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
unreadMsg() {
|
||||||
|
return Number(this.$store.state.unreadMsg) || 0;
|
||||||
|
},
|
||||||
|
unreadLabel() {
|
||||||
|
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.body.classList.add("buyer-user-center");
|
document.body.classList.add("buyer-user-center");
|
||||||
|
refreshUnreadMessage();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
document.body.classList.remove("buyer-user-center");
|
document.body.classList.remove("buyer-user-center");
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
menuIcon(name) {
|
||||||
|
return MENU_ICONS[name] || Goods;
|
||||||
|
},
|
||||||
onSelect(name) {
|
onSelect(name) {
|
||||||
this.$router.push({ name });
|
this.$router.push({ name });
|
||||||
},
|
},
|
||||||
@@ -134,6 +191,38 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-menu {
|
||||||
|
:deep(.el-menu-item),
|
||||||
|
:deep(.el-sub-menu__title) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu-item .el-icon),
|
||||||
|
:deep(.el-sub-menu__title .el-icon) {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-badge {
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $theme_color;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中项会把文字设成主题红。角标用 span,避免 i 继承成红底红字 */
|
||||||
|
.side-menu :deep(.el-menu-item.is-active) .menu-badge {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-footer-center {
|
.layout-footer-center {
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
|
|||||||
@@ -6,20 +6,20 @@
|
|||||||
|
|
||||||
// 订单中心
|
// 订单中心
|
||||||
const order = [{
|
const order = [{
|
||||||
icon: '',
|
icon: 'List',
|
||||||
title: '订单中心',
|
title: '订单中心',
|
||||||
menus: [{
|
menus: [{
|
||||||
icon: '',
|
icon: 'Goods',
|
||||||
title: '我的订单',
|
title: '我的订单',
|
||||||
path: 'MyOrder'
|
path: 'MyOrder'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Location',
|
||||||
title: '收货地址',
|
title: '收货地址',
|
||||||
path: 'MyAddress'
|
path: 'MyAddress'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'RefreshLeft',
|
||||||
title: '售后订单',
|
title: '售后订单',
|
||||||
path: 'AfterSale'
|
path: 'AfterSale'
|
||||||
}
|
}
|
||||||
@@ -29,66 +29,66 @@ const order = [{
|
|||||||
|
|
||||||
// 会员中心
|
// 会员中心
|
||||||
const member = [{
|
const member = [{
|
||||||
icon: '',
|
icon: 'User',
|
||||||
title: '会员中心',
|
title: '会员中心',
|
||||||
menus: [{
|
menus: [{
|
||||||
icon: '',
|
icon: 'User',
|
||||||
title: '用户信息',
|
title: '用户信息',
|
||||||
path: 'Profile'
|
path: 'Profile'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Lock',
|
||||||
title: '账户安全',
|
title: '账户安全',
|
||||||
path: 'AccountSafe'
|
path: 'AccountSafe'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Bell',
|
||||||
title: '我的消息',
|
title: '我的消息',
|
||||||
path: 'MsgList'
|
path: 'MsgList'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Clock',
|
||||||
title: '我的足迹',
|
title: '我的足迹',
|
||||||
path: 'MyTracks'
|
path: 'MyTracks'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Star',
|
||||||
title: '我的收藏',
|
title: '我的收藏',
|
||||||
path: 'Favorites'
|
path: 'Favorites'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'ChatDotRound',
|
||||||
title: '我的评论',
|
title: '我的评论',
|
||||||
path: 'CommentList'
|
path: 'CommentList'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Warning',
|
||||||
title: '我的投诉',
|
title: '我的投诉',
|
||||||
path: 'ComplainList'
|
path: 'ComplainList'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Coin',
|
||||||
title: '我的积分',
|
title: '我的积分',
|
||||||
path: 'Point'
|
path: 'Point'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Trophy',
|
||||||
title: '我的等级',
|
title: '我的等级',
|
||||||
path: 'MemberGrade'
|
path: 'MemberGrade'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Ticket',
|
||||||
title: '我的优惠券',
|
title: '我的优惠券',
|
||||||
path: 'Coupons'
|
path: 'Coupons'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Wallet',
|
||||||
title: '我的余额',
|
title: '我的余额',
|
||||||
path: 'MoneyManagement'
|
path: 'MoneyManagement'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '',
|
icon: 'Present',
|
||||||
title: '我的礼品卡',
|
title: '我的礼品卡',
|
||||||
path: 'MyGiftCards'
|
path: 'MyGiftCards'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ const MoneyManagement = () => import("@/pages/home/userCenter/MoneyManagement");
|
|||||||
const MyGiftCards = () => import("@/pages/home/userCenter/MyGiftCards");
|
const MyGiftCards = () => import("@/pages/home/userCenter/MyGiftCards");
|
||||||
|
|
||||||
const Home = () => import("@/pages/user/Home");
|
const Home = () => import("@/pages/user/Home");
|
||||||
|
const UserCenter = () => import("@/pages/home/Main");
|
||||||
const Merchant = () => import("@/pages/Merchant");
|
const Merchant = () => import("@/pages/Merchant");
|
||||||
|
const NotFound = () => import("@/pages/NotFound");
|
||||||
const topic = () => import("@/pages/Topic");
|
const topic = () => import("@/pages/Topic");
|
||||||
const ShopEntry = () => import("@/pages/shopEntry/ShopEntry");
|
const ShopEntry = () => import("@/pages/shopEntry/ShopEntry");
|
||||||
|
|
||||||
@@ -178,8 +180,13 @@ export const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: "/home",
|
path: "/home",
|
||||||
component: Home,
|
component: Home,
|
||||||
redirect: { name: "MyOrder" },
|
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "UserCenter",
|
||||||
|
component: UserCenter,
|
||||||
|
meta: { title: "个人中心" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "MyTracks",
|
path: "MyTracks",
|
||||||
name: "MyTracks",
|
name: "MyTracks",
|
||||||
@@ -213,19 +220,19 @@ export const router = createRouter({
|
|||||||
path: "CommentList",
|
path: "CommentList",
|
||||||
name: "CommentList",
|
name: "CommentList",
|
||||||
component: CommentList,
|
component: CommentList,
|
||||||
mate: { title: "评价列表" },
|
meta: { title: "评价列表" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "AddEval",
|
path: "AddEval",
|
||||||
name: "AddEval",
|
name: "AddEval",
|
||||||
component: AddEval,
|
component: AddEval,
|
||||||
mate: { title: "添加评价" },
|
meta: { title: "添加评价" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "EvalDetail",
|
path: "EvalDetail",
|
||||||
name: "EvalDetail",
|
name: "EvalDetail",
|
||||||
component: EvalDetail,
|
component: EvalDetail,
|
||||||
mate: { title: "评价详情" },
|
meta: { title: "评价详情" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "ComplainList",
|
path: "ComplainList",
|
||||||
@@ -331,6 +338,12 @@ export const router = createRouter({
|
|||||||
component: Merchant,
|
component: Merchant,
|
||||||
meta: { title: "店铺" },
|
meta: { title: "店铺" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/:pathMatch(.*)*",
|
||||||
|
name: "NotFound",
|
||||||
|
component: NotFound,
|
||||||
|
meta: { title: "页面不存在" },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,40 @@
|
|||||||
/**
|
/**
|
||||||
* 买家端内部链接跳转(首页装修、列表等共用)
|
* 买家端内部链接跳转(首页装修、列表等共用)
|
||||||
*
|
*
|
||||||
* @author Mike
|
* 商品详情新开标签,其余站内页面在当前页打开。外链仍新开。
|
||||||
* @date 2026-08-05
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** 内部链接新窗口打开,外链直接打开。浏览商品详情不要求登录。 */
|
const NEW_TAB_PATHS = ["/goodsDetail", "/pointGoodsDetail"];
|
||||||
|
|
||||||
|
function isExternalLink(url) {
|
||||||
|
return /^https?:\/\//i.test(url) || url.startsWith("//");
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeInternal(url) {
|
||||||
|
return url.charAt(0) === "/" ? url : `/${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathnameOf(url) {
|
||||||
|
return normalizeInternal(url).split("?")[0].split("#")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldOpenNewTab(url) {
|
||||||
|
if (!url) return false;
|
||||||
|
if (isExternalLink(url)) return true;
|
||||||
|
const path = pathnameOf(url);
|
||||||
|
return NEW_TAB_PATHS.indexOf(path) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
export function openLink(url, router) {
|
export function openLink(url, router) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
if (url.charAt(0) !== "/") {
|
if (isExternalLink(url)) {
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.open(router.resolve(url).href, "_blank");
|
const internal = normalizeInternal(url);
|
||||||
|
if (shouldOpenNewTab(internal)) {
|
||||||
|
window.open(router.resolve(internal).href, "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return router.push(internal);
|
||||||
}
|
}
|
||||||
|
|||||||
24
buyer/src/utils/unreadMessage.js
Normal file
24
buyer/src/utils/unreadMessage.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import store from "@/vuex/store";
|
||||||
|
import storage from "@/plugins/storage";
|
||||||
|
import { memberMsgList } from "@/api/member";
|
||||||
|
|
||||||
|
// 用未读消息总数刷新角标,只取 1 条记录
|
||||||
|
export function refreshUnreadMessage() {
|
||||||
|
if (!storage.getItem("userInfo")) {
|
||||||
|
store.commit("SET_UNREAD_MSG", 0);
|
||||||
|
return Promise.resolve(0);
|
||||||
|
}
|
||||||
|
return memberMsgList({
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
status: "UN_READY",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const total = res && res.success ? Number(res.result && res.result.total) || 0 : 0;
|
||||||
|
store.commit("SET_UNREAD_MSG", total);
|
||||||
|
return total;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return Number(store.state.unreadMsg) || 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ export const SET_NAVLIST = (state, data) => {
|
|||||||
export const SET_CARTNUM = (state, data) => {
|
export const SET_CARTNUM = (state, data) => {
|
||||||
state.cartNum = data
|
state.cartNum = data
|
||||||
}
|
}
|
||||||
|
export const SET_UNREAD_MSG = (state, data) => {
|
||||||
|
state.unreadMsg = Number(data) || 0
|
||||||
|
}
|
||||||
export const SET_HOTWORDS = (state, data) => {
|
export const SET_HOTWORDS = (state, data) => {
|
||||||
state.hotWordsList = data
|
state.hotWordsList = data
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default createStore({
|
|||||||
state: {
|
state: {
|
||||||
navList: [],
|
navList: [],
|
||||||
cartNum: storage.getItem("cartNum") || 0,
|
cartNum: storage.getItem("cartNum") || 0,
|
||||||
|
unreadMsg: 0,
|
||||||
logoImg: storage.getItem("logoImg") || defaultLogo,
|
logoImg: storage.getItem("logoImg") || defaultLogo,
|
||||||
siteName: storage.getItem("siteName") || "lilishop",
|
siteName: storage.getItem("siteName") || "lilishop",
|
||||||
hotWordsList: storage.getItem("hotWordsList"),
|
hotWordsList: storage.getItem("hotWordsList"),
|
||||||
|
|||||||
Reference in New Issue
Block a user