351 lines
12 KiB
Vue
351 lines
12 KiB
Vue
<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="goDetail">
|
||
<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' })
|
||
},
|
||
goDetail() {
|
||
this.activeTab = 'detail'
|
||
try { console.log('[index] goDetail → /pages/detail/index') } catch(e){}
|
||
uni.navigateTo({ url: '/pages/detail/index' })
|
||
},
|
||
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);
|
||
z-index: 9999;
|
||
}
|
||
|
||
.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>
|