矿机租赁系统代码更新

This commit is contained in:
2025-09-26 16:40:38 +08:00
parent d02c7dccf6
commit d1b3357a8e
79 changed files with 22401 additions and 1 deletions

View File

@@ -0,0 +1,60 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<div class="content-container">
<router-view />
</div>
</template>
<script>
export default {
name: 'Content'
}
</script>
<style scoped>
.content-container {
padding: 20px;
min-height: calc(100vh - 120px);
}
</style>

View File

@@ -0,0 +1,267 @@
<template>
<div class="header-container">
<!-- 顶部导航栏 -->
<nav class="navbar">
<router-link
v-for="nav in navigation"
:key="nav.path"
:to="nav.path"
class="nav-btn"
active-class="active"
:title="nav.description"
>
<span class="nav-icon">{{ nav.icon }}</span>
<span class="nav-text">{{ nav.name }}</span>
<span v-if="nav.path === '/cart'" class="cart-count">({{ cartItemCount }})</span>
</router-link>
</nav>
<!-- 面包屑导航 -->
<!-- <div class="breadcrumb">
<router-link
v-for="(crumb, index) in breadcrumbs"
:key="index"
:to="getBreadcrumbPath(index)"
class="breadcrumb-item"
:class="{ active: index === breadcrumbs.length - 1 }"
>
{{ crumb }}
</router-link>
</div> -->
</div>
</template>
<script>
import { readCart } from '../utils/cartManager'
import { mainNavigation, getBreadcrumb } from '../utils/navigation'
import { getGoodsList } from '../api/shoppingCart'
export default {
name: 'Header',
data() {
return {
user: null,
cart: [],
// 服务端购物车数量(统计 productMachineDtoList 的总条数)
cartServerCount: 0,
navigation: mainNavigation
}
},
computed: {
cartItemCount() {
// 只使用服务端数量,避免与本地缓存数量不一致
return Number.isFinite(this.cartServerCount) ? this.cartServerCount : 0
},
breadcrumbs() {
return getBreadcrumb(this.$route.path)
}
},
watch: {},
mounted() {
this.loadCart()
// 监听购物车变化
window.addEventListener('storage', this.handleStorageChange)
// 首次加载服务端购物车数量
this.loadServerCartCount()
// 监听应用内购物车更新事件
window.addEventListener('cart-updated', this.handleCartUpdated)
},
beforeDestroy() {
window.removeEventListener('storage', this.handleStorageChange)
window.removeEventListener('cart-updated', this.handleCartUpdated)
},
methods: {
loadCart() {
this.cart = readCart()
},
async loadServerCartCount() {
try {
const res = await getGoodsList()
// 统一提取 rows/数组
const primary = Array.isArray(res && res.rows)
? res.rows
: Array.isArray(res && res.data && res.data.rows)
? res.data.rows
: Array.isArray(res && res.data)
? res.data
: (Array.isArray(res) ? res : [])
let groups = []
if (Array.isArray(primary) && primary.length) {
// 情况Ashop -> shoppingCartInfoDtoList -> productMachineDtoList
if (Array.isArray(primary[0] && primary[0].shoppingCartInfoDtoList)) {
primary.forEach(shop => {
if (Array.isArray(shop && shop.shoppingCartInfoDtoList)) {
groups.push(...shop.shoppingCartInfoDtoList)
}
})
} else {
// 情况B直接就是商品分组数组
groups = primary
}
} else if (Array.isArray(res && res.shoppingCartInfoDtoList)) {
// 情况C返回对象直接有 shoppingCartInfoDtoList
groups = res.shoppingCartInfoDtoList
}
let total = 0
if (groups.length) {
total = groups.reduce((sum, g) => sum + (Array.isArray(g && g.productMachineDtoList) ? g.productMachineDtoList.length : 0), 0)
} else if (Array.isArray(res && res.productMachineDtoList)) {
// 情况D根对象直接是机器列表
total = res.productMachineDtoList.length
}
this.cartServerCount = Number.isFinite(total) ? total : 0
} catch (e) {
// 忽略错误,保持当前显示
}
},
handleStorageChange(event) {
if (event.key === 'power_leasing_cart_v1') {
this.loadCart()
this.loadServerCartCount()
}
},
handleCartUpdated(event) {
// 支持事件携带数量 { detail: { count } }
try {
const next = event && event.detail && Number(event.detail.count)
if (Number.isFinite(next)) {
this.cartServerCount = next
return
}
} catch (e) { /* ignore malformed event detail */ }
// 无显式数量则主动刷新
this.loadServerCartCount()
},
handleLogout() {
this.user = null
this.cart = []
},
getBreadcrumbPath(index) {
const paths = ['/productList', '/cart', '/checkout']
if (index === 0) return '/productList'
if (index < paths.length) return paths[index - 1]
return '/productList'
}
}
}
</script>
<style scoped>
.header-container {
width: 100%;
}
.navbar {
display: flex;
justify-content: center;
gap: 24px;
background: #fff;
border-bottom: 1px solid #eee;
padding: 16px 0;
margin-bottom: 16px;
}
.nav-btn {
display: flex;
align-items: center;
gap: 8px;
background: none;
border: none;
font-size: 16px;
color: #2c3e50;
cursor: pointer;
padding: 12px 20px;
border-radius: 8px;
transition: all 0.3s ease;
text-decoration: none;
outline: none;
position: relative;
}
.nav-btn:hover {
background: #f8f9fa;
transform: translateY(-2px);
}
.nav-btn.active {
background: #42b983;
color: #fff;
}
.nav-icon {
font-size: 18px;
}
.nav-text {
font-weight: 600;
}
.cart-count {
background: #e74c3c;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
min-width: 20px;
text-align: center;
}
/* 面包屑导航样式 */
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
background: #f8f9fa;
border-radius: 8px;
margin: 0 20px 20px 20px;
font-size: 14px;
}
.breadcrumb-item {
color: #666;
text-decoration: none;
transition: color 0.3s ease;
}
.breadcrumb-item:hover {
color: #42b983;
}
.breadcrumb-item.active {
color: #2c3e50;
font-weight: 600;
}
.breadcrumb-item:not(:last-child)::after {
content: '>';
margin-left: 8px;
color: #ccc;
}
/* 响应式设计 */
@media (max-width: 768px) {
.navbar {
flex-direction: column;
gap: 12px;
padding: 12px 0;
}
.nav-btn {
width: 100%;
justify-content: center;
padding: 16px 20px;
}
.breadcrumb {
margin: 0 12px 16px 12px;
padding: 8px 16px;
font-size: 12px;
}
}
</style>