Files
PartsInquiry/frontend/pages/index/index.vue
2025-09-20 22:09:15 +08:00

346 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">
<!-- 公告栏置顶显示可点击查看详情 -->
<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="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="section-title">
<text class="section-text">常用功能</text>
</view>
<!-- 功能九宫格玻璃容器 + 圆角方形图标 -->
<view class="grid-wrap">
<view class="feature-grid">
<view class="feature-card" v-for="item in features" :key="item.key" @click="onFeatureTap(item)">
<view class="fc-icon">
<image v-if="item.img" :src="item.img" class="fc-img" mode="aspectFit" @error="onIconError(item)"></image>
<text v-else-if="item.emoji" class="fc-emoji">{{ item.emoji }}</text>
<view v-else class="fc-placeholder"></view>
</view>
<view class="fc-title">{{ item.title }}</view>
</view>
</view>
</view>
<!-- 底部操作条改为原生 tabBar移除自定义栏 -->
</view>
</template>
<script>
import { get } from '../../common/http.js'
import { ROUTES } from '../../common/constants.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.switchTab({ url: '/pages/product/list' })
return
}
if (item.key === 'sale') {
// 进入开单页并预选“销售-出货”
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'sale', type: 'out' }) } catch(e) {}
uni.switchTab({ url: '/pages/order/create' })
return
}
if (item.key === 'customer') {
uni.navigateTo({ url: '/pages/customer/select' })
return
}
if (item.key === 'account') {
// 进入账户模块(先使用账户选择页,已对接后端 /api/accounts
uni.navigateTo({ url: '/pages/account/select' })
return
}
if (item.key === 'supplier') {
uni.navigateTo({ url: '/pages/supplier/select' })
return
}
if (item.key === 'purchase') {
// 进入开单页并预选“进货-进货”
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'purchase', type: 'in' }) } catch(e) {}
uni.switchTab({ url: '/pages/order/create' })
return
}
if (item.key === 'report') {
// 报表非 tab 页,使用 navigateTo 进入
uni.navigateTo({ url: ROUTES.report })
return
}
if (item.key === 'otherPay') {
// 进入开单页并预选“其他支出”
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'expense' }) } catch(e) {}
uni.switchTab({ url: '/pages/order/create' })
return
}
uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
},
goProduct() { uni.switchTab({ url: '/pages/product/list' }) },
onCreateOrder() { uni.switchTab({ url: '/pages/order/create' }) },
goDetail() {
try { console.log('[index] goDetail → /pages/detail/index') } catch(e){}
uni.switchTab({ url: '/pages/detail/index' })
},
goMe() { uni.switchTab({ url: '/pages/my/index' }) },
onNoticeTap(n) {
uni.showModal({
title: '广告',
content: n && (n.text || n.title || n.content) || '',
showCancel: false
})
},
onIconError(item) {
item.img = ''
}
}
}
</script>
<style lang="scss">
.home {
padding-bottom: 140rpx;
position: relative;
/* 纯白背景 */
background: #ffffff;
min-height: 100vh;
}
/* 公告栏 */
.notice {
margin: 0 24rpx 24rpx;
padding: 20rpx 22rpx;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid $uni-border-color;
display: flex;
align-items: center;
gap: 16rpx;
}
.notice-left {
flex: 0 0 auto;
display: inline-flex; align-items: center; justify-content: center;
min-width: 100rpx; height: 52rpx;
padding: 0 16rpx;
border-radius: 999rpx;
background: $uni-color-primary;
color: #fff;
font-size: 28rpx;
font-weight: 800;
}
.notice-swiper { height: 72rpx; flex: 1; }
.notice-item { display: flex; align-items: center; gap: 12rpx; min-height: 72rpx; }
.notice-text { color: $uni-text-color; 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: $uni-color-primary; font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: rgba(76,141,255,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: $uni-color-primary; }
.section-text { color: $uni-text-color; font-size: 30rpx; font-weight: 700; letter-spacing: 1rpx; }
/* 顶部英雄区:浅色玻璃卡片,带金色描边与柔和阴影 */
.hero {
margin: 24rpx;
padding: 32rpx;
border-radius: 28rpx;
background: #ffffff;
border: 2rpx solid $uni-border-color;
box-shadow: none;
color: $uni-text-color;
}
.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: $uni-color-primary;
}
.cta {
padding: 10rpx 22rpx;
border-radius: 999rpx;
background: $uni-color-primary;
border: 2rpx solid $uni-color-primary;
box-shadow: none;
}
.cta-text { color: #fff; font-size: 30rpx; font-weight: 700; letter-spacing: 1rpx; }
/* KPI 卡片化布局2×2 */
.kpi { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16rpx; }
.kpi-item { text-align: center; background:#ffffff; border: 2rpx solid $uni-border-color; border-radius: 16rpx; padding: 16rpx 8rpx; }
.kpi-label {
opacity: 0.9;
font-size: 26rpx;
color: $uni-text-color-grey;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
line-height: 32rpx; /* 行高>=字体,避免上沿被裁切 */
min-height: 64rpx; /* 两行高度,防止折行挤压 */
}
.kpi-value { display: block; margin-top: 8rpx; font-size: 56rpx; font-weight: 800; color: $uni-color-primary; }
/* 常用功能:胶囊+阴影卡片样式的图标栅格(旧风格保留以防回退) */
.grid { grid-row-gap: 36rpx; grid-column-gap: 26rpx; padding: 32rpx 24rpx 28rpx; }
.grid-item { position: relative; }
.icon-squircle { width: 140rpx; height: 140rpx; border-radius: 28rpx; background: #fff; border: 2rpx solid $uni-border-color; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.06); }
.grid-chip { margin-top: 12rpx; padding: 6rpx 14rpx; border-radius: 999rpx; background: rgba(76,141,255,0.12); color: $uni-color-primary; font-size: 26rpx; font-weight: 700; }
/* 功能容器:整体玻璃面板,增强融入感 */
.grid-wrap {
margin: 0 20rpx 32rpx;
padding: 28rpx 20rpx 12rpx;
border-radius: 24rpx;
background: #ffffff;
border: 2rpx solid $uni-border-color;
box-shadow: none;
}
/* 新版功能卡片宫格(更现代卡片风) */
.feature-grid { display:grid; grid-template-columns: repeat(2, 1fr); gap: 18rpx; padding: 18rpx 18rpx 24rpx; }
.feature-card { background:#fff; border:2rpx solid $uni-border-color; border-radius: 16rpx; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.06); padding: 18rpx; display:flex; align-items:center; gap: 12rpx; }
.fc-icon { width: 96rpx; height: 96rpx; border-radius: 16rpx; background: $uni-bg-color-hover; display:flex; align-items:center; justify-content:center; }
.fc-img { width: 72rpx; height: 72rpx; }
.fc-emoji { font-size: 56rpx; }
.fc-placeholder { width: 72rpx; height: 72rpx; border-radius: 12rpx; background: $uni-bg-color-hover; border: 2rpx solid $uni-border-color; }
.fc-title { margin-left: 2rpx; font-size: 30rpx; font-weight: 700; color: $uni-text-color; }
/* 底部操作条:浅色半透明 + 金色主按钮 */
.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>