Files
PartsInquiry/frontend/pages/index/index.vue
2025-09-17 14:40:16 +08:00

345 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="home">
<image class="home-bg" src="/static/metal-bg.jpg" mode="aspectFill"></image>
<!-- 顶部统计卡片 -->
<view class="hero">
<view class="hero-top">
<text class="brand">五金配件管家</text>
<view class="cta">
<text class="cta-text">咨询</text>
</view>
</view>
<view class="kpi">
<view class="kpi-item">
<text class="kpi-label">今日销售额</text>
<text class="kpi-value">{{ kpi.todaySales }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">本月销售额</text>
<text class="kpi-value">{{ kpi.monthSales }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">本月利润</text>
<text class="kpi-value">{{ kpi.monthProfit }}</text>
</view>
<view class="kpi-item">
<text class="kpi-label">库存商品数量</text>
<text class="kpi-value">{{ kpi.stockCount }}</text>
</view>
</view>
</view>
<!-- 广告栏自动轮播可点击查看详情 -->
<view class="notice">
<view class="notice-left">广告</view>
<view v-if="loadingNotices" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">加载中...</view>
<view v-else-if="noticeError" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d;">{{ noticeError }}</view>
<view v-else-if="!notices.length" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a;">暂无公告</view>
<swiper v-else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical>
<swiper-item v-for="(n, idx) in notices" :key="idx">
<view class="notice-item" @click="onNoticeTap(n)">
<text class="notice-text">{{ n.text }}</text>
<text v-if="n.tag" class="notice-tag">{{ n.tag }}</text>
</view>
</swiper-item>
</swiper>
</view>
<!-- 分割标题产品与功能 -->
<view class="section-title">
<text class="section-text">常用功能</text>
</view>
<!-- 功能九宫格玻璃容器 + 圆角方形图标 -->
<view class="grid-wrap">
<view class="grid">
<view class="grid-item" v-for="item in features" :key="item.key" @click="onFeatureTap(item)">
<view class="icon icon-squircle">
<image v-if="item.img" :src="item.img" class="icon-img" mode="aspectFit" @error="onIconError(item)"></image>
<text v-else-if="item.emoji" class="icon-emoji">{{ item.emoji }}</text>
<view v-else class="icon-placeholder"></view>
</view>
<text class="grid-chip">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 底部操作条 -->
<view class="bottom-bar">
<view class="tab" :class="{ active: activeTab==='home' }" @click="activeTab='home'">
<text>首页</text>
</view>
<view class="tab" :class="{ active: activeTab==='product' }" @click="goProduct">
<text>货品</text>
</view>
<view class="tab primary" @click="onCreateOrder">
<text>开单</text>
</view>
<view class="tab" :class="{ active: activeTab==='detail' }" @click="activeTab='detail'">
<text>明细</text>
</view>
<view class="tab" :class="{ active: activeTab==='report' }" @click="activeTab='report'">
<text>报表</text>
</view>
<view class="tab" :class="{ active: activeTab==='me' }" @click="activeTab='me'">
<text>我的</text>
</view>
</view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() {
return {
kpi: { todaySales: '0.00', monthSales: '0.00', monthProfit: '0.00', stockCount: '0' },
activeTab: 'home',
notices: [],
loadingNotices: false,
noticeError: '',
features: [
{ key: 'product', title: '货品', img: '/static/icons/product.png', emoji: '📦' },
{ key: 'customer', title: '客户', img: '/static/icons/customer.png', emoji: '👥' },
{ key: 'sale', title: '销售', img: '/static/icons/sale.png', emoji: '💰' },
{ key: 'account', title: '账户', img: '/static/icons/account.png', emoji: '💳' },
{ key: 'supplier', title: '供应商', img: '/static/icons/supplier.png', emoji: '🚚' },
{ key: 'purchase', title: '进货', img: '/static/icons/purchase.png', emoji: '🛒' },
{ key: 'otherPay', title: '其他支出', img: '/static/icons/other-pay.png', emoji: '💸' },
{ key: 'vip', title: 'VIP会员', img: '/static/icons/vip.png', emoji: '👑' },
{ key: 'report', title: '报表', img: '/static/icons/report.png', emoji: '📊' },
{ key: 'more', title: '更多', img: '/static/icons/more.png', emoji: '⋯' }
]
}
},
onLoad() {
this.fetchMetrics()
this.fetchNotices()
},
methods: {
async fetchMetrics() {
try {
const d = await get('/api/dashboard/overview')
const toNum = v => (typeof v === 'number' ? v : Number(v || 0))
this.kpi = {
...this.kpi,
todaySales: toNum(d && d.todaySalesAmount).toFixed(2),
monthSales: toNum(d && d.monthSalesAmount).toFixed(2),
monthProfit: toNum(d && d.monthGrossProfit).toFixed(2),
stockCount: String((d && d.stockTotalQuantity) != null ? d.stockTotalQuantity : 0)
}
} catch (e) {
// 忽略错误,保留默认值
}
},
async fetchNotices() {
this.loadingNotices = true
this.noticeError = ''
try {
const list = await get('/api/notices')
this.notices = Array.isArray(list) ? list.map(n => ({
text: n.content || n.title || '',
tag: n.tag || ''
})) : []
} catch (e) {
this.noticeError = (e && e.message) || '公告加载失败'
} finally {
this.loadingNotices = false
}
},
onFeatureTap(item) {
if (item.key === 'product') {
uni.navigateTo({ url: '/pages/product/list' })
return
}
uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
},
goProduct() {
this.activeTab = 'product'
uni.navigateTo({ url: '/pages/product/list' })
},
onCreateOrder() {
uni.navigateTo({ url: '/pages/order/create' })
},
onNoticeTap(n) {
uni.showModal({
title: '广告',
content: n && (n.text || n.title || n.content) || '',
showCancel: false
})
},
onIconError(item) {
item.img = ''
}
}
}
</script>
<style>
.home {
padding-bottom: 140rpx;
position: relative;
/* 明亮奢华背景:金属拉丝纹理覆盖层 + 柔和浅色渐变 */
background:
repeating-linear-gradient(0deg, rgba(180,180,180,0.12) 0rpx, rgba(180,180,180,0.12) 2rpx, rgba(255,255,255,0.0) 2rpx, rgba(255,255,255,0.0) 10rpx),
linear-gradient(180deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0.55) 40%, rgba(255, 255, 255, 0.35) 100%);
min-height: 100vh;
}
.home-bg {
position: fixed;
left: 0; top: 0; right: 0; bottom: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: -1;
}
/* 公告栏 */
.notice {
margin: 0 24rpx 24rpx;
padding: 20rpx 22rpx;
border-radius: 20rpx;
background: rgba(255,255,255,0.78);
backdrop-filter: blur(12rpx);
border: 2rpx solid rgba(203, 166, 61, 0.28);
display: flex;
align-items: center;
gap: 16rpx;
}
.notice-left {
flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
min-width: 96rpx; height: 44rpx;
padding: 0 16rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #FFE69A, #F4CF62);
color: #3f320f;
font-size: 24rpx;
font-weight: 800;
}
.notice-swiper { height: 72rpx; flex: 1; }
.notice-item { display: flex; align-items: center; gap: 12rpx; min-height: 72rpx; }
.notice-text { color: #4b3e19; font-size: 28rpx; line-height: 36rpx; font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.notice-tag { color: #B4880F; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: rgba(215,167,46,0.18); }
/* 分割标题 */
.section-title { display: flex; align-items: center; gap: 16rpx; padding: 10rpx 28rpx 0; }
.section-title::before { content: ''; display: block; width: 8rpx; height: 28rpx; border-radius: 8rpx; background: linear-gradient(180deg, #FFE69A, #D7A72E); }
.section-text { color: #6b5a2a; font-size: 28rpx; font-weight: 700; letter-spacing: 1rpx; }
/* 顶部英雄区:浅色玻璃卡片,带金色描边与柔和阴影 */
.hero {
margin: 24rpx;
padding: 32rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(14rpx);
border: 2rpx solid rgba(203, 166, 61, 0.35);
box-shadow: 0 12rpx 28rpx rgba(0, 0, 0, 0.10), 0 0 0 2rpx rgba(255,255,255,0.60) inset;
color: #473c22;
}
.hero-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.brand {
font-size: 42rpx;
font-weight: 700;
letter-spacing: 2rpx;
color: #B4880F; /* 金色标题 */
}
.cta {
padding: 10rpx 22rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, rgba(255, 220, 128, 0.65), rgba(255, 240, 190, 0.65));
border: 2rpx solid rgba(203, 166, 61, 0.45);
box-shadow: 0 6rpx 14rpx rgba(203, 166, 61, 0.25);
}
.cta-text { color: #5a4712; font-size: 26rpx; }
.kpi { display: flex; }
.kpi-item { flex: 1; }
.kpi-label { opacity: 0.9; font-size: 24rpx; color: #6b5a2a; }
.kpi-value { display: block; margin-top: 12rpx; font-size: 46rpx; font-weight: 800; color: #B4880F; }
/* 功能容器:整体玻璃面板,增强融入感 */
.grid-wrap {
margin: 0 20rpx 32rpx;
padding: 28rpx 20rpx 12rpx;
border-radius: 24rpx;
background: rgba(255,255,255,0.55);
backdrop-filter: blur(10rpx);
border: 2rpx solid rgba(203,166,61,0.22);
box-shadow: 0 8rpx 18rpx rgba(0,0,0,0.06);
}
/* 功能九宫格 */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-row-gap: 64rpx;
grid-column-gap: 36rpx;
padding: 40rpx 28rpx 28rpx;
}
.grid-item { display: flex; flex-direction: column; align-items: center; text-align: center; }
.icon { display: flex; align-items: center; justify-content: center; color: #6b5a2a; position: relative; }
.icon-squircle {
width: 132rpx; height: 132rpx;
border-radius: 28rpx;
background: linear-gradient(145deg, rgba(255,255,255,0.92), rgba(255,255,255,0.70));
backdrop-filter: blur(12rpx);
border: 2rpx solid rgba(203,166,61,0.22);
box-shadow: 0 10rpx 24rpx rgba(0,0,0,0.10), 0 0 0 2rpx rgba(255,255,255,0.65) inset;
overflow: hidden;
}
.icon-squircle::before { content: ''; position: absolute; left: -30%; top: -40%; width: 160%; height: 70%; background: linear-gradient( to bottom, rgba(255,255,255,0.9), rgba(255,255,255,0.0) ); transform: rotate(12deg); }
.icon-img { width: 96rpx; height: 96rpx; }
.icon-emoji { font-size: 60rpx; line-height: 1; }
.icon-placeholder { width: 84rpx; height: 84rpx; border-radius: 18rpx; background:
linear-gradient(135deg, rgba(212,175,55,0.18), rgba(255,255,255,0.0)),
repeating-linear-gradient(90deg, rgba(180,150,60,0.35) 0, rgba(180,150,60,0.35) 8rpx, transparent 8rpx, transparent 16rpx),
repeating-linear-gradient(0deg, rgba(180,150,60,0.20) 0, rgba(180,150,60,0.20) 8rpx, transparent 8rpx, transparent 16rpx);
box-shadow: inset 0 0 0 2rpx rgba(203,166,61,0.28);
}
.icon-text { font-size: 46rpx; font-weight: 700; }
.grid-title { display: none; }
.grid-chip { margin-top: 14rpx; padding: 6rpx 14rpx; border-radius: 999rpx; background: rgba(215,167,46,0.16); color: #5a4a1f; font-size: 22rpx; }
/* 底部操作条:浅色半透明 + 金色主按钮 */
.bottom-bar {
position: fixed;
left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
justify-content: space-around;
padding: 14rpx 18rpx calc(env(safe-area-inset-bottom) + 14rpx);
background: rgba(255,255,255,0.85);
box-shadow: 0 -6rpx 18rpx rgba(0,0,0,0.08);
backdrop-filter: blur(10rpx);
}
.tab { flex: 1; text-align: center; color: #8a7535; font-size: 26rpx; }
.tab.active { color: #B4880F; }
.tab.primary {
flex: 0 0 auto;
min-width: 180rpx;
margin: 0 18rpx;
padding: 18rpx 32rpx;
background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%);
color: #493c1b;
border-radius: 999rpx;
font-size: 30rpx;
font-weight: 800;
box-shadow: 0 10rpx 22rpx rgba(215,167,46,0.25), 0 0 0 2rpx rgba(255,255,255,0.70) inset;
}
</style>