This commit is contained in:
2025-09-27 22:57:59 +08:00
parent 8a458ff0a4
commit ed26244cdb
12585 changed files with 1914308 additions and 3474 deletions

View File

@@ -1,26 +1,11 @@
<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 class="cta" @click="onConsultTap" hover-class="cta-active" hover-stay-time="80">
<text class="cta-text">{{ consultLabel }}</text>
</view>
</view>
<view class="kpi kpi-grid">
@@ -55,7 +40,33 @@
</view>
</view>
<!-- 公告栏已上移到顶部 -->
<!-- 咨询输入弹层 -->
<view v-if="consultDialogVisible" class="dialog-mask" @touchmove.stop.prevent @click.stop>
<view class="dialog">
<view class="dialog-title">咨询</view>
<textarea class="dialog-textarea" v-model="consultMessage" placeholder="请输入咨询内容..." maxlength="500"></textarea>
<view class="dialog-actions">
<view class="btn" @click="closeConsultDialog">取消</view>
<view class="btn primary" @click="submitConsult">提交</view>
</view>
</view>
</view>
<!-- 公告栏放在常用功能上方KPI 下方 -->
<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">
@@ -81,7 +92,7 @@
</template>
<script>
import { get } from '../../common/http.js'
import { get, post, put } from '../../common/http.js'
import { ROUTES } from '../../common/constants.js'
import { KPI_ICONS as KPI_ICON_MAP } from '../../common/config.js'
export default {
@@ -93,17 +104,19 @@
notices: [],
loadingNotices: false,
noticeError: '',
consultLabel: '咨询',
consultDialogVisible: false,
consultMessage: '',
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: '⋯' }
{ key: 'customer', title: '客户', img: '/static/icons/webwxgetmsgimg.png', emoji: '👥' },
{ key: 'sale', title: '销售', img: '/static/icons/webwxgetmsgimg.jpg', emoji: '💰' },
{ key: 'account', title: '账户', img: '/static/icons/icons8-profile-50.png', emoji: '💳' },
{ key: 'supplier', title: '供应商', img: '/static/icons/icons8-supplier-50.png', emoji: '🚚' },
{ key: 'purchase', title: '进货', img: '/static/icons/icons8-dollar-ethereum-exchange-50.png', emoji: '🛒' },
{ key: 'otherPay', title: '其他支出', img: '/static/icons/icons8-expenditure-64.png', emoji: '💸' },
{ key: 'vip', title: 'VIP会员', img: '/static/icons/icons8-vip-48.png', emoji: '👑' },
{ key: 'report', title: '报表', img: '/static/icons/icons8-graph-report-50.png', emoji: '📊' }
]
}
},
@@ -117,6 +130,7 @@
}
this.fetchMetrics()
this.fetchNotices()
this.fetchLatestConsult()
},
methods: {
async fetchMetrics() {
@@ -134,6 +148,47 @@
// 忽略错误,保留默认值
}
},
async fetchLatestConsult() {
try {
const d = await get('/api/consults')
if (d && d.replied) this.consultLabel = '已回复'
else this.consultLabel = '咨询'
this._latestConsult = d
} catch(e) { this.consultLabel = '咨询' }
},
onConsultTap() {
if (this.consultLabel === '已回复' && this._latestConsult && this._latestConsult.id) {
// 展示最近一次咨询与管理员最新回复
const msg = (this._latestConsult.latestReply ? (this._latestConsult.latestReply) : (this._latestConsult.message || ''))
uni.showModal({ title: '咨询回复', content: msg || '暂无内容', showCancel: false, success: async (res) => {
if (!res || res.confirm !== true) return
try {
const r = await put(`/api/consults/${this._latestConsult.id}/ack`, {})
this.consultLabel = '咨询'
this._latestConsult = null
setTimeout(() => this.fetchLatestConsult(), 200)
} catch(e) {
try { uni.showToast({ title: (e && e.message) || '已读同步失败', icon: 'none' }) } catch(_){}
}
}})
return
}
this.consultMessage = ''
this.consultDialogVisible = true
},
closeConsultDialog() { this.consultDialogVisible = false },
async submitConsult() {
const text = String(this.consultMessage || '').trim()
if (!text) { uni.showToast({ title: '请输入咨询内容', icon: 'none' }); return }
try {
await post('/api/consults', { message: text })
this.consultDialogVisible = false
uni.showToast({ title: '已提交', icon: 'success' })
setTimeout(() => this.fetchLatestConsult(), 300)
} catch (e) {
uni.showToast({ title: (e && e.message) || '提交失败', icon: 'none' })
}
},
async fetchNotices() {
this.loadingNotices = true
this.noticeError = ''
@@ -215,12 +270,21 @@
</script>
<style lang="scss">
page {
height: 100%;
overflow: hidden;
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%);
}
.home {
padding-bottom: 140rpx;
position: relative;
/* 渐变背景:顶部淡蓝过渡到白色 */
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%);
min-height: 100vh;
height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: calc(env(safe-area-inset-bottom) + 32rpx);
position: relative;
/* 渐变背景:顶部淡蓝过渡到白色 */
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 60%);
overflow: hidden;
box-sizing: border-box;
}
@@ -256,19 +320,20 @@
/* 分割标题 */
.section-title { display: flex; align-items: center; gap: 16rpx; padding: 10rpx 28rpx 0; }
.section-title { display: flex; align-items: center; gap: 16rpx; padding: 10rpx 28rpx 0; flex: 0 0 auto; }
.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: 16rpx 20rpx;
padding: 18rpx;
padding: 18rpx 18rpx 12rpx;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid $uni-border-color;
box-shadow: none;
color: $uni-text-color;
flex: 0 0 auto;
}
.hero-top {
@@ -297,17 +362,26 @@
.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; }
/* 简易弹层样式 */
.dialog-mask { position: fixed; left:0; right:0; top:0; bottom:0; background: rgba(0,0,0,0.45); display:flex; align-items:center; justify-content:center; z-index: 10000; }
.dialog { width: 82vw; background: #fff; border-radius: 16rpx; padding: 20rpx; border: 2rpx solid #eef2f6; }
.dialog-title { font-size: 32rpx; font-weight: 800; color: $uni-text-color; margin-bottom: 16rpx; }
.dialog-textarea { width: 100%; min-height: 180rpx; border: 2rpx solid #e8eef8; border-radius: 12rpx; padding: 12rpx; box-sizing: border-box; }
.dialog-actions { display:flex; justify-content:flex-end; gap: 18rpx; margin-top: 16rpx; }
.btn { padding: 10rpx 22rpx; border-radius: 999rpx; background: #f3f6fb; color: #334155; border: 2rpx solid #e2e8f0; font-weight: 700; }
.btn.primary { background: #4C8DFF; color: #fff; border-color: #4C8DFF; }
/* KPI 卡片化布局:横向铺满 */
.kpi { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12rpx; }
.kpi-item { text-align: center; background:#ffffff; border: 2rpx solid $uni-border-color; border-radius: 16rpx; padding: 16rpx 8rpx; }
/* KPI 卡片(更扁平,降低高度) */
.kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 16rpx; }
.kpi-card { display:flex; align-items:center; gap: 12rpx; text-align:left; padding: 12rpx 14rpx; border-radius: 12rpx; background:#fff; border:2rpx solid #eef2f6; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); }
.kpi-grid { gap: 12rpx; }
.kpi-card { display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap: 8rpx; text-align:left; padding: 12rpx 14rpx; border-radius: 14rpx; background:#fff; border:2rpx solid #eef2f6; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); min-height: 120rpx; }
.kpi-icon { width: 44rpx; height: 44rpx; opacity: .9; }
.kpi-content { display:flex; flex-direction:column; }
.kpi-label { color:#6b778c; font-weight:700; font-size: 24rpx; line-height: 30rpx; }
.kpi-value { color:#4C8DFF; font-size: 36rpx; line-height: 40rpx; margin-top: 0; font-weight: 800; }
.kpi-value { color:#4C8DFF; font-size: 34rpx; line-height: 38rpx; margin-top: 0; font-weight: 800; }
/* 常用功能:胶囊+阴影卡片样式的图标栅格(旧风格保留以防回退) */
@@ -318,21 +392,27 @@
/* 功能容器:更轻的留白 */
.grid-wrap {
margin: 8rpx 12rpx 24rpx;
padding: 8rpx 8rpx 0;
border-radius: 20rpx;
background: transparent;
border: 0;
flex: 1 1 auto;
display:flex;
align-items:stretch;
justify-content:center;
margin: 16rpx 20rpx 28rpx;
padding: 32rpx 30rpx;
border-radius: 26rpx;
background: rgba(255,255,255,0.96);
border: 2rpx solid #edf2f9;
box-shadow: 0 12rpx 28rpx rgba(32,75,143,0.10);
box-sizing: border-box;
}
/* 功能卡片宫格:方形竖排,图标在上文字在下(与截图一致) */
.feature-grid { display:grid; grid-template-columns: repeat(3, 1fr); gap: 14rpx; padding: 8rpx 8rpx 18rpx; }
.feature-card { height: 164rpx; background:#fff; border:2rpx solid #eef2f6; border-radius: 16rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.04); padding: 12rpx; display:flex; flex-direction: column; align-items:center; justify-content:center; }
.fc-icon { width: 86rpx; height: 86rpx; border-radius: 18rpx; background: #f7faff; border:2rpx solid #e8eef8; display:flex; align-items:center; justify-content:center; }
.fc-img { width: 56rpx; height: 56rpx; opacity: .95; }
.feature-grid { flex: 1 1 auto; width: 100%; height: 100%; display:grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 32rpx 28rpx; align-content: space-evenly; justify-items: center; }
.feature-card { width: 168rpx; height: 176rpx; background:#fff; border:2rpx solid #eef2f6; border-radius: 20rpx; box-shadow: 0 10rpx 24rpx rgba(0,0,0,0.05); padding: 18rpx 16rpx; display:flex; flex-direction: column; align-items:center; justify-content:center; }
.fc-icon { width: 78rpx; height: 78rpx; border-radius: 18rpx; background: #f7faff; display:flex; align-items:center; justify-content:center; }
.fc-img { width: 54rpx; height: 54rpx; opacity: .95; }
.fc-emoji { font-size: 48rpx; }
.fc-placeholder { width: 56rpx; height: 56rpx; border-radius: 12rpx; background: $uni-bg-color-hover; border: 2rpx solid #e8eef8; }
.fc-title { margin-top: 10rpx; font-size: 26rpx; font-weight: 700; color: $uni-text-color; }
.fc-title { margin-top: 12rpx; font-size: 28rpx; font-weight: 700; color: $uni-text-color; letter-spacing: 1rpx; }
/* 底部操作条:浅色半透明 + 金色主按钮 */
.bottom-bar {