|
|
@@ -9,87 +9,182 @@
|
|
|
<!-- 预警提示 -->
|
|
|
<div v-if="warningCount > 0" class="alert-bar" @click="goToWarning">
|
|
|
<el-icon class="alert-icon"><WarningFilled /></el-icon>
|
|
|
- <span class="alert-text">您有 <strong>{{ warningCount }}</strong> 个配送单即将超时,请及时处理!</span>
|
|
|
+ <span class="alert-text">
|
|
|
+ 您有 <strong>{{ warningCount }}</strong> 条配送时效预警未处理,请及时跟进!
|
|
|
+ </span>
|
|
|
<span class="alert-btn">立即处理</span>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 数据统计卡片 -->
|
|
|
- <el-row :gutter="16" class="stats-row">
|
|
|
- <el-col :xs="12" :sm="8" :md="5" v-for="stat in statsData" :key="stat.key">
|
|
|
- <div class="stats-card" @click="handleStatClick(stat.key)">
|
|
|
- <div class="stats-icon" :class="stat.colorClass">
|
|
|
- <el-icon :size="24"><component :is="stat.icon" /></el-icon>
|
|
|
- </div>
|
|
|
- <div class="stats-info">
|
|
|
- <div class="stats-num">{{ stat.value }}</div>
|
|
|
- <div class="stats-label">{{ stat.label }}</div>
|
|
|
+ <!-- 指标卡:7 项两行(4+3),左右分栏展示 -->
|
|
|
+ <section class="panel overview-panel" aria-label="运营概览">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2 class="title-bar-heading">数据概览</h2>
|
|
|
+ </div>
|
|
|
+ <div class="overview-panel__body">
|
|
|
+ <div class="stats stats--overview">
|
|
|
+ <div
|
|
|
+ v-for="card in overviewCards"
|
|
|
+ :key="card.key"
|
|
|
+ class="stat stat--overview"
|
|
|
+ :class="{ warn: card.warn, 'stat--risk': card.risk }"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ @click="handleOverviewClick(card.key)"
|
|
|
+ @keydown.enter="handleOverviewClick(card.key)"
|
|
|
+ >
|
|
|
+ <div class="stat-overview-left">
|
|
|
+ <span class="stat-ic stat-ic--compact" aria-hidden="true">
|
|
|
+ <el-icon :size="22"><component :is="card.icon" /></el-icon>
|
|
|
+ </span>
|
|
|
+ <div class="stat-overview-titles">
|
|
|
+ <div class="k">{{ card.title }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-overview-right">
|
|
|
+ <div class="v">{{ card.value }}</div>
|
|
|
+ <div class="hint todo-hint-block stat-overview-hint">
|
|
|
+ <div v-for="(line, idx) in card.hintLines" :key="idx" class="todo-hint-line">{{ line }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 待办事项 -->
|
|
|
- <el-row :gutter="16" class="todo-row">
|
|
|
- <el-col :xs="24" :sm="12" :md="6" v-for="todo in todoList" :key="todo.type">
|
|
|
- <div class="todo-card" :class="todo.className" @click="handleTodoClick(todo.path)">
|
|
|
- <div class="todo-header">
|
|
|
- <span class="todo-title">{{ todo.title }}</span>
|
|
|
- <span class="todo-count" :class="todo.countClass">{{ todo.count }}</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 近30日:每日营业额、每日订单数(数据来自 statistics/orders/trend) -->
|
|
|
+ <section class="panel trend-charts-panel" aria-label="近30日趋势">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2 class="title-bar-heading">近30日趋势</h2>
|
|
|
+ </div>
|
|
|
+ <div class="trend-charts-panel__body">
|
|
|
+ <div class="trend-charts-grid">
|
|
|
+ <div class="trend-chart-card">
|
|
|
+ <div class="trend-chart-card__head">
|
|
|
+ <h3 class="trend-chart-card__title">每日营业额</h3>
|
|
|
+ <span class="trend-chart-card__unit">单位:元</span>
|
|
|
+ </div>
|
|
|
+ <ArtLineChart
|
|
|
+ class="trend-chart-card__chart"
|
|
|
+ height="240px"
|
|
|
+ :loading="trendLoading"
|
|
|
+ :data="revenueSeries"
|
|
|
+ :xAxisData="trendXAxis"
|
|
|
+ :colors="consoleChartColors.trendLineRevenue"
|
|
|
+ :showAreaColor="true"
|
|
|
+ :showAxisLine="false"
|
|
|
+ />
|
|
|
</div>
|
|
|
- <div class="todo-list">
|
|
|
- <div v-for="(item, index) in todo.items.slice(0, 2)" :key="index" class="todo-item">
|
|
|
- <span class="todo-item-text">{{ item }}</span>
|
|
|
- <span class="todo-item-action" @click.stop="handleTodoAction(todo.path)">
|
|
|
- {{ todo.actionText }}
|
|
|
- </span>
|
|
|
+ <div class="trend-chart-card">
|
|
|
+ <div class="trend-chart-card__head">
|
|
|
+ <h3 class="trend-chart-card__title">每日订单总数</h3>
|
|
|
+ <span class="trend-chart-card__unit">单位:笔</span>
|
|
|
</div>
|
|
|
+ <ArtLineChart
|
|
|
+ class="trend-chart-card__chart"
|
|
|
+ height="240px"
|
|
|
+ :loading="trendLoading"
|
|
|
+ :data="orderCountSeries"
|
|
|
+ :xAxisData="trendXAxis"
|
|
|
+ :colors="consoleChartColors.trendLineOrders"
|
|
|
+ :showAreaColor="true"
|
|
|
+ :showAxisLine="false"
|
|
|
+ />
|
|
|
</div>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 快捷操作 -->
|
|
|
- <el-row :gutter="16" class="quick-row">
|
|
|
- <el-col :xs="12" :sm="6" v-for="action in quickActions" :key="action.key">
|
|
|
- <div class="quick-btn" @click="handleQuickAction(action.path)">
|
|
|
- <div class="quick-icon" :class="action.colorClass">
|
|
|
- <el-icon :size="20"><component :is="action.icon" /></el-icon>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 今日各区域 + 今日下单客户:分组双柱(双 Y 轴) -->
|
|
|
+ <section class="panel region-today-panel" aria-label="今日各区域与客户统计">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2 class="title-bar-heading">今日各区域</h2>
|
|
|
+ <span class="region-today-panel__hint">
|
|
|
+ 按当日创建订单统计;左侧为今日各区域营业额(元)与订单数(笔);右侧为今日下单客户的下单金额(元)与商品数量(件);类目较多时可拖动底部滑块或按住图表左右平移查看
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="region-today-panel__body">
|
|
|
+ <div class="region-today-split">
|
|
|
+ <div class="region-today-split__col">
|
|
|
+ <h3 class="region-today-subtitle">今日各区域</h3>
|
|
|
+ <RegionTodayDualBarChart
|
|
|
+ height="340px"
|
|
|
+ :loading="regionTodayLoading"
|
|
|
+ :categories="regionTodayCategories"
|
|
|
+ :amounts="regionTodayAmounts"
|
|
|
+ :counts="regionTodayCounts"
|
|
|
+ :amount-color="consoleChartColors.regionAmount"
|
|
|
+ :count-color="consoleChartColors.regionCount"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="region-today-split__col">
|
|
|
+ <h3 class="region-today-subtitle">今日下单客户</h3>
|
|
|
+ <RegionTodayDualBarChart
|
|
|
+ height="340px"
|
|
|
+ :loading="clientTodayLoading"
|
|
|
+ :categories="clientTodayCategories"
|
|
|
+ :amounts="clientTodayAmounts"
|
|
|
+ :counts="clientTodayQuantities"
|
|
|
+ empty-text="暂无今日下单客户"
|
|
|
+ amount-series-name="下单金额(元)"
|
|
|
+ count-series-name="商品数量"
|
|
|
+ left-axis-name="元"
|
|
|
+ right-axis-name="数量"
|
|
|
+ count-value-suffix="件"
|
|
|
+ :amount-color="consoleChartColors.clientAmount"
|
|
|
+ :count-color="consoleChartColors.clientCount"
|
|
|
+ />
|
|
|
</div>
|
|
|
- <span class="quick-text">{{ action.text }}</span>
|
|
|
- <el-icon class="quick-arrow"><ArrowRight /></el-icon>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
|
|
|
- <!-- 最新订单 -->
|
|
|
+ <!-- 最新订单:与订单列表页一致 list-body + list-table + stripe,无 border -->
|
|
|
<section class="panel orders-card">
|
|
|
<div class="panel-top">
|
|
|
- <h2>最新订单</h2>
|
|
|
- <el-button type="primary" link @click="goToOrderList">查看更多</el-button>
|
|
|
+ <h2 class="title-bar-heading">最新订单</h2>
|
|
|
+ <el-button type="primary" link class="console-more-btn" @click="goToOrderList">
|
|
|
+ <span class="console-more-btn__inner">
|
|
|
+ 查看更多
|
|
|
+ <el-icon class="console-more-btn__arrow" :size="15" aria-hidden="true"><ArrowRight /></el-icon>
|
|
|
+ </span>
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
<div class="list-body list-wrap">
|
|
|
- <el-table class="list-table" :data="orderList" v-loading="loading" stripe>
|
|
|
- <el-table-column prop="orderNo" label="订单编号" min-width="180" />
|
|
|
- <el-table-column prop="clientName" label="客户名称" min-width="150" />
|
|
|
- <el-table-column prop="region" label="区域" min-width="100" />
|
|
|
- <el-table-column prop="totalAmount" label="订单金额(元)" min-width="120">
|
|
|
- <template #default="{ row }">
|
|
|
- <span class="amount-text">¥{{ row.totalAmount }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="status" label="订单状态" min-width="130">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag :type="getStatusType(row.status)" size="small">
|
|
|
- {{ getStatusText(row.status) }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="createTime" label="下单时间" min-width="180" />
|
|
|
- <el-table-column label="操作" min-width="100">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-button type="primary" link @click="goToOrderDetail(row.id)">查看</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
+ <el-table
|
|
|
+ class="order-list-table list-table"
|
|
|
+ :data="orderList"
|
|
|
+ v-loading="loading"
|
|
|
+ stripe
|
|
|
+ row-key="id"
|
|
|
+ >
|
|
|
+ <el-table-column prop="orderNo" label="订单编号" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="clientName" label="客户名称" min-width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="regionName" label="区域" min-width="100" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="totalAmount" label="订单金额(元)" min-width="120" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="amount-text">¥{{ formatMoney(row.totalAmount) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="订单状态" min-width="130">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getOrderStatusType(resolveOrderStatus(row))" size="small">
|
|
|
+ {{ getOrderStatusText(resolveOrderStatus(row)) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createAt" label="下单时间" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatDateTime(row.createAt) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" min-width="100" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button class="btn-op-item" @click.stop="goToOrderDetail(row)">
|
|
|
+ <el-icon><View /></el-icon>
|
|
|
+ 查看
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
</section>
|
|
|
@@ -97,474 +192,619 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
- import { ref, onMounted } from 'vue'
|
|
|
+ import { ref, computed, onMounted } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import {
|
|
|
WarningFilled,
|
|
|
UserFilled,
|
|
|
- User,
|
|
|
Shop,
|
|
|
ShoppingCart,
|
|
|
Van,
|
|
|
Money,
|
|
|
- Plus,
|
|
|
- Wallet,
|
|
|
+ Document,
|
|
|
+ View,
|
|
|
ArrowRight
|
|
|
} from '@element-plus/icons-vue'
|
|
|
- import { ElMessage } from 'element-plus'
|
|
|
+ import {
|
|
|
+ fetchWorkbenchDashboard,
|
|
|
+ fetchOrderTrend,
|
|
|
+ fetchTodayOrdersByRegion,
|
|
|
+ fetchTodayOrdersByClient,
|
|
|
+ type WorkbenchDashboardData,
|
|
|
+ type OrderTrendDayItem,
|
|
|
+ type TodayRegionOrderStatItem,
|
|
|
+ type TodayClientOrderStatItem
|
|
|
+ } from '@/api/business'
|
|
|
+ import RegionTodayDualBarChart from './RegionTodayDualBarChart.vue'
|
|
|
|
|
|
defineOptions({ name: 'Console' })
|
|
|
|
|
|
+ /** 工作台四图配色:两条折线 + 两组双柱共 6 色互不重复,便于区分 */
|
|
|
+ const consoleChartColors = {
|
|
|
+ trendLineRevenue: ['#10b981'],
|
|
|
+ /** 每日订单总数折线:金色 */
|
|
|
+ trendLineOrders: ['#d4a017'],
|
|
|
+ regionAmount: '#0d9488',
|
|
|
+ regionCount: '#d97706',
|
|
|
+ /** 今日下单客户:绿柱 + 金柱 */
|
|
|
+ clientAmount: '#10b981',
|
|
|
+ clientCount: '#d97706'
|
|
|
+ } as const
|
|
|
+
|
|
|
const router = useRouter()
|
|
|
const loading = ref(false)
|
|
|
- const warningCount = ref(3)
|
|
|
-
|
|
|
- // 统计数据
|
|
|
- const statsData = ref([
|
|
|
- { key: 'client', label: '客户总数', value: '1,286', icon: UserFilled, colorClass: 'blue', path: '/admin/user' },
|
|
|
- { key: 'supplier', label: '供应商数量', value: '86', icon: Shop, colorClass: 'green', path: '/admin/user' },
|
|
|
- { key: 'todayOrder', label: '今日订单', value: '328', icon: ShoppingCart, colorClass: 'orange', path: '/admin/order' },
|
|
|
- { key: 'pendingDelivery', label: '待配送', value: '45', icon: Van, colorClass: 'red', path: '/admin/delivery' },
|
|
|
- { key: 'todayAmount', label: '今日交易额', value: '¥58,620', icon: Money, colorClass: 'purple', path: '/admin/statistics' }
|
|
|
- ])
|
|
|
-
|
|
|
- // 待办事项
|
|
|
- const todoList = ref([
|
|
|
- {
|
|
|
- type: 'quotation',
|
|
|
- title: '待审核报价',
|
|
|
- count: 5,
|
|
|
- className: 'warning',
|
|
|
- countClass: 'orange',
|
|
|
- actionText: '处理',
|
|
|
- path: '/inquiry/quotation-list',
|
|
|
- items: ['2026年3月第3周询价...', '城东区域供应商报价...']
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'delivery',
|
|
|
- title: '待分配配送',
|
|
|
- count: 12,
|
|
|
- className: 'danger',
|
|
|
- countClass: '',
|
|
|
- actionText: '分配',
|
|
|
- path: '/admin/delivery',
|
|
|
- items: ['ORD202603190003', 'ORD202603190004']
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'cancel',
|
|
|
- title: '取消待审核',
|
|
|
- count: 3,
|
|
|
- className: 'primary',
|
|
|
- countClass: 'blue',
|
|
|
- actionText: '审核',
|
|
|
- path: '/admin/order',
|
|
|
- items: ['客户BB超市申请取消...', '客户CC商行申请取消...']
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'settlement',
|
|
|
- title: '待结算确认',
|
|
|
- count: 8,
|
|
|
- className: 'success',
|
|
|
- countClass: 'green',
|
|
|
- actionText: '确认',
|
|
|
- path: '/admin/settlement',
|
|
|
- items: ['AA餐厅线下结算...', 'BB超市线下结算...']
|
|
|
- }
|
|
|
- ])
|
|
|
-
|
|
|
- // 快捷操作
|
|
|
- const quickActions = [
|
|
|
- { key: 'inquiry', text: '发布询价', icon: Plus, colorClass: 'blue', path: '/inquiry/inquiry-list' },
|
|
|
- { key: 'delivery', text: '分配配送', icon: Van, colorClass: 'orange', path: '/admin/delivery' },
|
|
|
- { key: 'settlement', text: '确认结算', icon: Wallet, colorClass: 'green', path: '/admin/settlement' },
|
|
|
- { key: 'user', text: '新增用户', icon: User, colorClass: 'red', path: '/admin/user' }
|
|
|
- ]
|
|
|
-
|
|
|
- // 订单列表
|
|
|
- const orderList = ref([
|
|
|
- {
|
|
|
- id: '1',
|
|
|
- orderNo: 'ORD202603190001',
|
|
|
- clientName: 'XX餐厅',
|
|
|
- region: '城东',
|
|
|
- totalAmount: '1,286.00',
|
|
|
- status: 'pending_payment',
|
|
|
- createTime: '2026-03-19 10:23:45'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '2',
|
|
|
- orderNo: 'ORD202603190002',
|
|
|
- clientName: 'YY超市',
|
|
|
- region: '城西',
|
|
|
- totalAmount: '2,560.00',
|
|
|
- status: 'pending_delivery',
|
|
|
- createTime: '2026-03-19 09:15:22'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '3',
|
|
|
- orderNo: 'ORD202603180056',
|
|
|
- clientName: 'ZZ食堂',
|
|
|
- region: '城南',
|
|
|
- totalAmount: '856.00',
|
|
|
- status: 'delivering',
|
|
|
- createTime: '2026-03-18 16:45:30'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '4',
|
|
|
- orderNo: 'ORD202603180055',
|
|
|
- clientName: 'AA饭店',
|
|
|
- region: '城东',
|
|
|
- totalAmount: '3,200.00',
|
|
|
- status: 'completed',
|
|
|
- createTime: '2026-03-18 14:20:18'
|
|
|
- }
|
|
|
- ])
|
|
|
+ const dashboard = ref<WorkbenchDashboardData | null>(null)
|
|
|
+
|
|
|
+ const TREND_DAYS = 30
|
|
|
+ const trendLoading = ref(false)
|
|
|
+ const orderTrend = ref<OrderTrendDayItem[]>([])
|
|
|
+
|
|
|
+ const trendXAxis = computed(() =>
|
|
|
+ orderTrend.value.map((row) => {
|
|
|
+ const d = row.date || ''
|
|
|
+ return d.length >= 10 ? `${d.slice(5, 7)}-${d.slice(8, 10)}` : d
|
|
|
+ })
|
|
|
+ )
|
|
|
+
|
|
|
+ const revenueSeries = computed(() => orderTrend.value.map((r) => Number(r.orderAmount ?? 0)))
|
|
|
+ const orderCountSeries = computed(() => orderTrend.value.map((r) => Number(r.orderCount ?? 0)))
|
|
|
+
|
|
|
+ const regionTodayLoading = ref(false)
|
|
|
+ const regionTodayList = ref<TodayRegionOrderStatItem[]>([])
|
|
|
+
|
|
|
+ const regionTodayCategories = computed(() => regionTodayList.value.map((r) => r.regionName || r.regionId))
|
|
|
+ const regionTodayAmounts = computed(() => regionTodayList.value.map((r) => Number(r.orderAmount ?? 0)))
|
|
|
+ const regionTodayCounts = computed(() => regionTodayList.value.map((r) => Number(r.orderCount ?? 0)))
|
|
|
+
|
|
|
+ const clientTodayLoading = ref(false)
|
|
|
+ const clientTodayList = ref<TodayClientOrderStatItem[]>([])
|
|
|
+
|
|
|
+ const clientTodayCategories = computed(() =>
|
|
|
+ clientTodayList.value.map((r) => {
|
|
|
+ const n = String(r.clientName || r.clientId || '')
|
|
|
+ return n.length > 10 ? `${n.slice(0, 10)}…` : n
|
|
|
+ })
|
|
|
+ )
|
|
|
+ const clientTodayAmounts = computed(() => clientTodayList.value.map((r) => Number(r.orderAmount ?? 0)))
|
|
|
+ const clientTodayQuantities = computed(() => clientTodayList.value.map((r) => Number(r.productQuantity ?? 0)))
|
|
|
+
|
|
|
+ const warningCount = computed(() => Math.max(0, Number(dashboard.value?.deliveryWarningCount ?? 0)))
|
|
|
+
|
|
|
+ const formatMoney = (n: number | string | undefined) => {
|
|
|
+ const num = typeof n === 'string' ? parseFloat(n) : Number(n ?? 0)
|
|
|
+ if (Number.isNaN(num)) return '0.00'
|
|
|
+ return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
+ }
|
|
|
+
|
|
|
+ const formatDateTime = (t: string | undefined | null) => {
|
|
|
+ if (t == null || t === '') return '—'
|
|
|
+ return String(t).replace('T', ' ').slice(0, 19)
|
|
|
+ }
|
|
|
+
|
|
|
+ const overviewCards = computed(() => {
|
|
|
+ const d = dashboard.value
|
|
|
+ const c = d?.customerCount ?? 0
|
|
|
+ const s = d?.supplierCount ?? 0
|
|
|
+ const t = d?.todayOrderCount ?? 0
|
|
|
+ const p = d?.pendingDeliveryCount ?? 0
|
|
|
+ const a = d?.todayOrderAmount ?? 0
|
|
|
+ const snaps = d?.todoSnapshots || []
|
|
|
+ const quotSnap = snaps.find((x) => x.type === 'quotation')
|
|
|
+ const qCount = quotSnap?.count ?? 0
|
|
|
+ const quotLines =
|
|
|
+ loading.value && !d
|
|
|
+ ? ['加载中…']
|
|
|
+ : quotSnap?.items?.length
|
|
|
+ ? quotSnap.items.slice(0, 2)
|
|
|
+ : ['暂无新报价待处理']
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ key: 'client',
|
|
|
+ title: '客户总数',
|
|
|
+ value: c.toLocaleString('zh-CN'),
|
|
|
+ hintLines: ['有效客户账号'],
|
|
|
+ icon: UserFilled,
|
|
|
+ warn: false,
|
|
|
+ risk: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'supplier',
|
|
|
+ title: '供应商数量',
|
|
|
+ value: s.toLocaleString('zh-CN'),
|
|
|
+ hintLines: ['有效供应商账号'],
|
|
|
+ icon: Shop,
|
|
|
+ warn: false,
|
|
|
+ risk: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'todayOrder',
|
|
|
+ title: '今日订单',
|
|
|
+ value: t.toLocaleString('zh-CN'),
|
|
|
+ hintLines: [d?.todayOrderCountHint || '—'],
|
|
|
+ icon: ShoppingCart,
|
|
|
+ warn: false,
|
|
|
+ risk: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'pendingDelivery',
|
|
|
+ title: '待配送',
|
|
|
+ value: p.toLocaleString('zh-CN'),
|
|
|
+ hintLines: [p > 0 ? '含待分配配送的订单' : '暂无待配送'],
|
|
|
+ icon: Van,
|
|
|
+ warn: p > 0,
|
|
|
+ risk: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'todayAmount',
|
|
|
+ title: '今日交易额',
|
|
|
+ value: `¥${formatMoney(a)}`,
|
|
|
+ hintLines: [d?.todayOrderAmountHint || '—'],
|
|
|
+ icon: Money,
|
|
|
+ warn: false,
|
|
|
+ risk: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'newQuotation',
|
|
|
+ title: '新的报价',
|
|
|
+ value: qCount.toLocaleString('zh-CN'),
|
|
|
+ hintLines: quotLines,
|
|
|
+ icon: Document,
|
|
|
+ warn: qCount > 0,
|
|
|
+ risk: false
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+
|
|
|
+ const orderList = computed(() => dashboard.value?.recentOrders ?? [])
|
|
|
+
|
|
|
+ const resolveOrderStatus = (row: { cancelStatus?: string; orderStatus?: string; status?: string }) => {
|
|
|
+ if (row?.cancelStatus === 'pending_review') return 'pending_review_cancel'
|
|
|
+ if (row?.orderStatus) return row.orderStatus
|
|
|
+ if (row?.status === 'pending_payment') return 'pending_payment'
|
|
|
+ if (['completed'].includes(String(row?.status))) return 'completed'
|
|
|
+ if (['cancelled'].includes(String(row?.status))) return 'cancelled'
|
|
|
+ return 'pending_confirm'
|
|
|
+ }
|
|
|
|
|
|
- // 获取状态类型
|
|
|
- const getStatusType = (status: string) => {
|
|
|
- const typeMap: Record<string, string> = {
|
|
|
+ const getOrderStatusType = (status: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
pending_payment: 'warning',
|
|
|
- pending_delivery: 'primary',
|
|
|
- delivering: 'info',
|
|
|
- partial_delivered: 'warning',
|
|
|
pending_confirm: 'primary',
|
|
|
+ pending: 'primary',
|
|
|
+ pending_review_cancel: 'warning',
|
|
|
completed: 'success',
|
|
|
cancelled: 'info'
|
|
|
}
|
|
|
- return typeMap[status] || 'info'
|
|
|
+ return map[status] || 'info'
|
|
|
}
|
|
|
|
|
|
- // 获取状态文本
|
|
|
- const getStatusText = (status: string) => {
|
|
|
- const textMap: Record<string, string> = {
|
|
|
+ const getOrderStatusText = (status: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
pending_payment: '待支付',
|
|
|
- pending_delivery: '待分配配送',
|
|
|
- delivering: '配送中',
|
|
|
- partial_delivered: '部分送达',
|
|
|
- pending_confirm: '待确认收货',
|
|
|
+ pending_confirm: '待收货',
|
|
|
+ pending: '待收货',
|
|
|
+ pending_review_cancel: '待审核取消',
|
|
|
completed: '已完成',
|
|
|
cancelled: '已取消'
|
|
|
}
|
|
|
- return textMap[status] || status
|
|
|
+ return map[status] || status
|
|
|
}
|
|
|
|
|
|
- // 跳转预警页面
|
|
|
const goToWarning = () => {
|
|
|
- router.push('/admin/delivery-warning')
|
|
|
+ router.push('/delivery/delivery-warning')
|
|
|
}
|
|
|
|
|
|
- // 统计卡片点击
|
|
|
- const handleStatClick = (key: string) => {
|
|
|
- const stat = statsData.value.find((s) => s.key === key)
|
|
|
- if (stat?.path) {
|
|
|
- router.push(stat.path)
|
|
|
+ const handleOverviewClick = (key: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ client: '/base/user',
|
|
|
+ supplier: '/base/user',
|
|
|
+ todayOrder: '/order/order',
|
|
|
+ pendingDelivery: '/delivery/delivery',
|
|
|
+ todayAmount: '/order/order',
|
|
|
+ newQuotation: '/inquiry/manage'
|
|
|
}
|
|
|
+ const path = map[key]
|
|
|
+ if (path) router.push(path)
|
|
|
}
|
|
|
|
|
|
- // 待办点击
|
|
|
- const handleTodoClick = (path: string) => {
|
|
|
- router.push(path)
|
|
|
+ const goToOrderList = () => {
|
|
|
+ router.push('/order/order')
|
|
|
}
|
|
|
|
|
|
- // 待办操作
|
|
|
- const handleTodoAction = (path: string) => {
|
|
|
- router.push(path)
|
|
|
+ const goToOrderDetail = (row: { id: string }) => {
|
|
|
+ router.push({ path: '/order/order', query: { viewOrderId: row.id } })
|
|
|
}
|
|
|
|
|
|
- // 快捷操作
|
|
|
- const handleQuickAction = (path: string) => {
|
|
|
- router.push(path)
|
|
|
+ const loadDashboard = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ dashboard.value = await fetchWorkbenchDashboard()
|
|
|
+ } catch {
|
|
|
+ dashboard.value = null
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 跳转订单列表
|
|
|
- const goToOrderList = () => {
|
|
|
- router.push('/admin/order')
|
|
|
+ const loadOrderTrend = async () => {
|
|
|
+ trendLoading.value = true
|
|
|
+ try {
|
|
|
+ const list = await fetchOrderTrend({ days: TREND_DAYS })
|
|
|
+ orderTrend.value = Array.isArray(list) ? list : []
|
|
|
+ } catch {
|
|
|
+ orderTrend.value = []
|
|
|
+ } finally {
|
|
|
+ trendLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const loadTodayRegionStats = async () => {
|
|
|
+ regionTodayLoading.value = true
|
|
|
+ try {
|
|
|
+ const list = await fetchTodayOrdersByRegion()
|
|
|
+ regionTodayList.value = Array.isArray(list) ? list : []
|
|
|
+ } catch {
|
|
|
+ regionTodayList.value = []
|
|
|
+ } finally {
|
|
|
+ regionTodayLoading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 跳转订单详情
|
|
|
- const goToOrderDetail = (id: string) => {
|
|
|
- router.push(`/admin/order/${id}`)
|
|
|
+ const loadTodayClientStats = async () => {
|
|
|
+ clientTodayLoading.value = true
|
|
|
+ try {
|
|
|
+ const list = await fetchTodayOrdersByClient({ limit: 30 })
|
|
|
+ clientTodayList.value = Array.isArray(list) ? list : []
|
|
|
+ } catch {
|
|
|
+ clientTodayList.value = []
|
|
|
+ } finally {
|
|
|
+ clientTodayLoading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
- // TODO: 从后端获取数据
|
|
|
+ void loadDashboard()
|
|
|
+ void loadOrderTrend()
|
|
|
+ void loadTodayRegionStats()
|
|
|
+ void loadTodayClientStats()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.console-container {
|
|
|
- .alert-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12px;
|
|
|
- padding: 12px 16px;
|
|
|
- margin-bottom: 16px;
|
|
|
- background: #fffbeb;
|
|
|
- border: 1px solid #fde68a;
|
|
|
- border-radius: var(--mint-radius);
|
|
|
- cursor: pointer;
|
|
|
- transition: 0.2s ease;
|
|
|
+ .console-container {
|
|
|
+ .alert-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: #fffbeb;
|
|
|
+ border: 1px solid #fde68a;
|
|
|
+ border-radius: var(--mint-radius);
|
|
|
+ cursor: pointer;
|
|
|
+ transition: 0.2s ease;
|
|
|
|
|
|
- &:hover {
|
|
|
- background: #fef3c7;
|
|
|
- }
|
|
|
+ &:hover {
|
|
|
+ background: #fef3c7;
|
|
|
+ }
|
|
|
|
|
|
- .alert-icon {
|
|
|
- color: #d97706;
|
|
|
- font-size: 18px;
|
|
|
- }
|
|
|
+ .alert-icon {
|
|
|
+ color: #d97706;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
|
|
|
- .alert-text {
|
|
|
- flex: 1;
|
|
|
- color: #92400e;
|
|
|
- font-size: 13px;
|
|
|
- }
|
|
|
+ .alert-text {
|
|
|
+ flex: 1;
|
|
|
+ color: #92400e;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
|
|
|
- .alert-btn {
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- font-size: 13px;
|
|
|
- font-weight: 600;
|
|
|
+ .alert-btn {
|
|
|
+ color: var(--mint-primary-dark);
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-.stats-row,
|
|
|
-.todo-row,
|
|
|
-.quick-row {
|
|
|
- margin-bottom: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.stats-card,
|
|
|
-.todo-card,
|
|
|
-.quick-btn {
|
|
|
- background: var(--mint-surface);
|
|
|
- border: 1px solid var(--mint-border);
|
|
|
- border-radius: var(--mint-radius);
|
|
|
- box-shadow: var(--mint-shadow);
|
|
|
- margin-bottom: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.stats-card {
|
|
|
- min-height: 112px;
|
|
|
- padding: 12px 14px;
|
|
|
- gap: 12px;
|
|
|
- transition: border-color 0.2s ease;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- border-color: #b9dbcb;
|
|
|
- transform: none;
|
|
|
- box-shadow: var(--mint-shadow);
|
|
|
- }
|
|
|
-
|
|
|
- .stats-icon {
|
|
|
- width: 44px;
|
|
|
- height: 44px;
|
|
|
- border-radius: 10px;
|
|
|
- border: 1px solid rgba(16, 185, 129, 0.12);
|
|
|
- background: var(--mint-primary-bg);
|
|
|
- color: var(--mint-primary-dark);
|
|
|
|
|
|
- &.orange,
|
|
|
- &.red {
|
|
|
- background: #fffbeb;
|
|
|
- border-color: rgba(217, 119, 6, 0.2);
|
|
|
- color: #d97706;
|
|
|
- }
|
|
|
+ .overview-panel,
|
|
|
+ .trend-charts-panel,
|
|
|
+ .region-today-panel {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
|
|
|
- &.blue,
|
|
|
- &.green,
|
|
|
- &.purple {
|
|
|
- background: var(--mint-primary-bg);
|
|
|
- border-color: rgba(16, 185, 129, 0.12);
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- }
|
|
|
+ .overview-panel__body {
|
|
|
+ padding: 16px;
|
|
|
}
|
|
|
|
|
|
- .stats-info {
|
|
|
- .stats-num {
|
|
|
- font-size: 22px;
|
|
|
- font-weight: 700;
|
|
|
- color: var(--mint-text);
|
|
|
- line-height: 1.2;
|
|
|
- letter-spacing: -0.02em;
|
|
|
- font-variant-numeric: tabular-nums;
|
|
|
- }
|
|
|
+ .trend-charts-panel__body {
|
|
|
+ padding: 16px;
|
|
|
+ border-top: 1px solid var(--mint-border, #d9e8e2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .trend-charts-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
|
|
|
- .stats-label {
|
|
|
- font-size: 12px;
|
|
|
- color: var(--mint-text-muted);
|
|
|
- margin-top: 4px;
|
|
|
+ @media (max-width: 960px) {
|
|
|
+ .trend-charts-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.todo-card {
|
|
|
- padding: 14px;
|
|
|
- border-left: 3px solid var(--mint-border);
|
|
|
- cursor: pointer;
|
|
|
- transition: 0.2s ease;
|
|
|
+ .trend-chart-card {
|
|
|
+ min-width: 0;
|
|
|
+ padding: 14px 14px 8px;
|
|
|
+ background: var(--mint-surface, #fff);
|
|
|
+ border: 1px solid var(--mint-border, #d9e8e2);
|
|
|
+ border-radius: var(--mint-radius, 10px);
|
|
|
+ box-shadow: var(--mint-shadow, 0 1px 3px rgba(0, 0, 0, 0.05));
|
|
|
+ }
|
|
|
|
|
|
- &:hover {
|
|
|
- border-color: var(--mint-primary);
|
|
|
- transform: none;
|
|
|
- box-shadow: var(--mint-shadow);
|
|
|
+ .trend-chart-card__head {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
- &.warning {
|
|
|
- border-left-color: #d97706;
|
|
|
+ .trend-chart-card__title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--mint-text, #1e293b);
|
|
|
}
|
|
|
|
|
|
- &.danger {
|
|
|
- border-left-color: #dc2626;
|
|
|
+ .trend-chart-card__unit {
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--mint-text-muted, #64748b);
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
- &.primary {
|
|
|
- border-left-color: var(--mint-primary-dark);
|
|
|
+ .trend-chart-card__chart {
|
|
|
+ margin-left: -6px;
|
|
|
}
|
|
|
|
|
|
- &.success {
|
|
|
- border-left-color: var(--mint-primary);
|
|
|
+ .region-today-panel .panel-top {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
|
|
|
- .todo-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 12px;
|
|
|
+ .region-today-panel__hint {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--mint-text-muted, #64748b);
|
|
|
+ line-height: 1.4;
|
|
|
+ flex: 1 1 220px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
|
|
|
- .todo-title {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 600;
|
|
|
- color: var(--mint-text);
|
|
|
- }
|
|
|
+ .region-today-panel__body {
|
|
|
+ padding: 16px;
|
|
|
+ border-top: 1px solid var(--mint-border, #d9e8e2);
|
|
|
+ }
|
|
|
|
|
|
- .todo-count {
|
|
|
- background: var(--mint-primary-bg);
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- padding: 2px 10px;
|
|
|
- border-radius: 6px;
|
|
|
- font-size: 12px;
|
|
|
- font-weight: 600;
|
|
|
+ .region-today-split {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
|
|
|
- &.blue {
|
|
|
- background: var(--mint-primary-bg);
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- }
|
|
|
+ @media (max-width: 1100px) {
|
|
|
+ .region-today-split {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &.orange {
|
|
|
- background: #fffbeb;
|
|
|
- color: #d97706;
|
|
|
- }
|
|
|
+ .region-today-split__col {
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
|
|
|
- &.green {
|
|
|
- background: #d1fae5;
|
|
|
- color: #047857;
|
|
|
- }
|
|
|
+ .region-today-subtitle {
|
|
|
+ margin: 0 0 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--mint-text, #1e293b);
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ width: 3px;
|
|
|
+ height: 14px;
|
|
|
+ border-radius: 2px;
|
|
|
+ background: var(--mint-primary, #10b981);
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .todo-list {
|
|
|
- .todo-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 8px 0;
|
|
|
- border-bottom: 1px solid #edf4f1;
|
|
|
- font-size: 13px;
|
|
|
+ /* 与 delivery-demo `.stats` / `.stat` 对齐(色值与 demo 一致) */
|
|
|
+ .stats {
|
|
|
+ display: grid;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
|
|
|
- &:last-child {
|
|
|
- border-bottom: none;
|
|
|
- }
|
|
|
+ .stats--overview {
|
|
|
+ grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
+ }
|
|
|
|
|
|
- .todo-item-text {
|
|
|
- color: var(--mint-text-muted);
|
|
|
- flex: 1;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
- }
|
|
|
+ @media (max-width: 1200px) {
|
|
|
+ .stats--overview {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- .todo-item-action {
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- cursor: pointer;
|
|
|
- margin-left: 8px;
|
|
|
+ @media (max-width: 640px) {
|
|
|
+ .stats--overview {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &:hover {
|
|
|
- text-decoration: underline;
|
|
|
- }
|
|
|
- }
|
|
|
+ .stat--overview {
|
|
|
+ background: var(--mint-surface, #fff);
|
|
|
+ border: 1px solid var(--mint-border, #d9e8e2);
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 10px 14px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 12px;
|
|
|
+ min-height: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: border-color 0.2s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #b9dbcb;
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.quick-btn {
|
|
|
- border-radius: var(--mint-radius);
|
|
|
- padding: 16px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12px;
|
|
|
- cursor: pointer;
|
|
|
- transition: border-color 0.2s ease;
|
|
|
+ .stat-overview-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
|
|
|
- &:hover {
|
|
|
- border-color: #b9dbcb;
|
|
|
- transform: none;
|
|
|
- box-shadow: var(--mint-shadow);
|
|
|
+ .stat-overview-titles .k {
|
|
|
+ margin: 0;
|
|
|
}
|
|
|
|
|
|
- .quick-icon {
|
|
|
- width: 40px;
|
|
|
- height: 40px;
|
|
|
+ .stat-overview-right {
|
|
|
+ flex-shrink: 0;
|
|
|
+ text-align: right;
|
|
|
+ max-width: 52%;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-overview-hint {
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-overview-hint .todo-hint-line {
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-ic {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
border-radius: 10px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+ background: #ecfdf5;
|
|
|
+ color: #059669;
|
|
|
+ border: 1px solid rgba(16, 185, 129, 0.12);
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
|
|
|
- &.blue {
|
|
|
- background: var(--mint-primary-bg);
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- }
|
|
|
+ .stat-ic--compact {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
|
|
|
- &.orange {
|
|
|
- background: #fffbeb;
|
|
|
- color: #d97706;
|
|
|
- }
|
|
|
+ .stat .k {
|
|
|
+ font-size: 13px;
|
|
|
+ color: var(--mint-text, #1e293b);
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.35;
|
|
|
+ }
|
|
|
|
|
|
- &.green {
|
|
|
- background: #d1fae5;
|
|
|
- color: #047857;
|
|
|
- }
|
|
|
+ .stat .v {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.15;
|
|
|
+ color: var(--mint-text, #1e293b);
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ }
|
|
|
|
|
|
- &.red {
|
|
|
- background: #fef2f2;
|
|
|
- color: #dc2626;
|
|
|
- }
|
|
|
+ .stat .hint {
|
|
|
+ margin-top: 2px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #475569;
|
|
|
}
|
|
|
|
|
|
- .quick-text {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
- color: var(--mint-text);
|
|
|
- flex: 1;
|
|
|
+ .todo-hint-block {
|
|
|
+ white-space: normal;
|
|
|
}
|
|
|
|
|
|
- .quick-arrow {
|
|
|
- color: var(--mint-text-muted);
|
|
|
+ .todo-hint-line {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #64748b;
|
|
|
+ line-height: 1.45;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.orders-card {
|
|
|
- .list-body {
|
|
|
- border-top: 1px solid var(--mint-border);
|
|
|
+ .stat.warn .stat-ic {
|
|
|
+ background: #fffbeb;
|
|
|
+ color: #d97706;
|
|
|
+ border-color: rgba(217, 119, 6, 0.2);
|
|
|
}
|
|
|
|
|
|
- :deep(.el-table__cell .cell) {
|
|
|
- white-space: nowrap;
|
|
|
+ .stat.warn .v,
|
|
|
+ .stat.warn .hint {
|
|
|
+ color: #d97706;
|
|
|
}
|
|
|
|
|
|
- .amount-text {
|
|
|
- color: var(--mint-primary-dark);
|
|
|
- font-weight: 600;
|
|
|
+ .stat.warn .todo-hint-line {
|
|
|
+ color: #b45309;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat--risk .stat-ic {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #dc2626;
|
|
|
+ border-color: rgba(220, 38, 38, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat--risk .v {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .console-more-btn__inner {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .console-more-btn__arrow {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .orders-card {
|
|
|
+ .list-body {
|
|
|
+ border-top: 1px solid var(--mint-border);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-table__cell .cell) {
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .amount-text {
|
|
|
+ color: var(--mint-primary-dark);
|
|
|
+ font-weight: 600;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 与订单列表页一致:单元格 flex 垂直居中,避免状态列错位 */
|
|
|
+ .order-list-table :deep(th.el-table__cell > .cell),
|
|
|
+ .order-list-table :deep(td.el-table__cell > .cell) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
-}
|
|
|
</style>
|