| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- <template>
- <div class="app-page">
- <section class="glass-card section-card">
- <el-form :model="filters" inline class="filter-form">
- <el-form-item label="SKU">
- <el-input v-model="filters.sku" placeholder="SKU编号/商品名称" clearable style="width:180px" @keyup.enter="loadData" />
- </el-form-item>
- <el-form-item label="仓库">
- <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width:140px">
- <el-option v-for="w in warehouses" :key="w" :label="w" :value="w" />
- </el-select>
- </el-form-item>
- <el-form-item label="分析维度">
- <el-select v-model="filters.dimension" placeholder="全部" clearable style="width:130px">
- <el-option label="按SKU" value="sku" />
- <el-option label="按类目" value="category" />
- <el-option label="按仓库" value="warehouse" />
- </el-select>
- </el-form-item>
- <el-form-item label="状态">
- <el-select v-model="filters.status" placeholder="全部状态" clearable style="width:120px">
- <el-option label="正常" value="正常" />
- <el-option label="滞销" value="滞销" />
- <el-option label="严重滞销" value="严重滞销" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadData">查询</el-button>
- <el-button @click="resetFilters">重置</el-button>
- </el-form-item>
- </el-form>
- </section>
- <section class="glass-card section-card" style="padding:12px 24px">
- <div class="table-toolbar" style="margin-bottom:0">
- <div class="chip-list">
- <el-button type="primary" @click="setAlert">设置预警</el-button>
- <el-button @click="doExport">导出报表</el-button>
- <el-button type="success" @click="generateReplenishment">生成补货单</el-button>
- </div>
- <el-button @click="loadData">刷新</el-button>
- </div>
- </section>
- <section class="glass-card section-card" style="padding:16px">
- <div class="stat-grid" style="grid-template-columns:repeat(5, 1fr)">
- <article class="stat-card">
- <div class="stat-card__icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)">
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12,6 12,12 16,14"/></svg>
- </div>
- <div class="stat-card__content">
- <div class="stat-card__label">库存周转天数</div>
- <div class="stat-card__value" style="font-size:28px;color:#667eea">{{ kpiData.turnoverDays }}天</div>
- <div class="stat-card__trend up"><el-icon><ArrowUp /></el-icon>12%</div>
- </div>
- </article>
- <article class="stat-card">
- <div class="stat-card__icon" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%)">
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
- </div>
- <div class="stat-card__content">
- <div class="stat-card__label">动销率</div>
- <div class="stat-card__value" style="font-size:28px;color:#11998e">{{ kpiData.salesRate }}%</div>
- <div class="stat-card__trend up"><el-icon><ArrowUp /></el-icon>5%</div>
- </div>
- </article>
- <article class="stat-card">
- <div class="stat-card__icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)">
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
- </div>
- <div class="stat-card__content">
- <div class="stat-card__label">滞销SKU</div>
- <div class="stat-card__value" style="font-size:28px;color:#f5576c">{{ kpiData.slowMoving }}</div>
- <div class="stat-card__trend down"><el-icon><ArrowDown /></el-icon>3</div>
- </div>
- </article>
- <article class="stat-card">
- <div class="stat-card__icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)">
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg>
- </div>
- <div class="stat-card__content">
- <div class="stat-card__label">库存覆盖天数</div>
- <div class="stat-card__value" style="font-size:28px;color:#00f2fe">{{ kpiData.coverageDays }}天</div>
- <div class="stat-card__trend neutral">目标: 30天</div>
- </div>
- </article>
- <article class="stat-card">
- <div class="stat-card__icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%)">
- <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 3"/></svg>
- </div>
- <div class="stat-card__content">
- <div class="stat-card__label">平均库龄</div>
- <div class="stat-card__value" style="font-size:28px;color:#fa709a">{{ kpiData.avgAge }}天</div>
- <div class="stat-card__trend down"><el-icon><ArrowDown /></el-icon>8%</div>
- </div>
- </article>
- </div>
- </section>
- <section class="glass-card section-card">
- <div class="section-header">
- <h3>库存周转分析</h3>
- <el-radio-group v-model="timeRange" size="small">
- <el-radio-button label="7d">近7天</el-radio-button>
- <el-radio-button label="30d">近30天</el-radio-button>
- <el-radio-button label="90d">近90天</el-radio-button>
- </el-radio-group>
- </div>
- <div class="chart-grid-2">
- <div class="chart-container">
- <div ref="turnoverTrendChart" style="height: 300px"></div>
- </div>
- <div class="chart-container">
- <div ref="warehouseComparisonChart" style="height: 300px"></div>
- </div>
- </div>
- </section>
- <section class="glass-card section-card">
- <div class="section-header">
- <h3>库存结构分析</h3>
- </div>
- <div class="chart-grid-3">
- <div class="chart-container">
- <div ref="categoryPieChart" style="height: 280px"></div>
- </div>
- <div class="chart-container">
- <div ref="stockDistributionChart" style="height: 280px"></div>
- </div>
- <div class="chart-container">
- <div ref="turnoverGaugeChart" style="height: 280px"></div>
- </div>
- </div>
- </section>
- <section class="glass-card section-card">
- <div class="section-header">
- <h3>滞销预警 ({{ alertItems.length }} 条)</h3>
- <div class="chip-list">
- <el-tag type="danger" effect="dark">严重滞销</el-tag>
- <el-tag type="warning" effect="dark">滞销</el-tag>
- <el-tag type="info">库存积压</el-tag>
- </div>
- </div>
- <el-table :data="alertItems" style="width:100%" v-loading="loading" :row-class-name="tableRowClass">
- <el-table-column prop="sku" label="SKU" width="150" />
- <el-table-column prop="productTitle" label="商品名称" min-width="200" show-overflow-tooltip />
- <el-table-column prop="warehouse" label="仓库" width="120" />
- <el-table-column prop="available" label="当前库存" width="90" align="center" />
- <el-table-column prop="sales30d" label="30天销量" width="90" align="center" />
- <el-table-column prop="stockDays" label="库存天数" width="100" align="center">
- <template #default="{ row }">
- <el-progress :percentage="Math.min(row.stockDays / 2, 100)" :status="stockProgressStatus(row.stockDays)" />
- </template>
- </el-table-column>
- <el-table-column prop="turnoverDays" label="周转天数" width="100" align="center">
- <template #default="{ row }">
- <span :class="turnoverClass(row.turnoverDays)">{{ row.turnoverDays }}天</span>
- </template>
- </el-table-column>
- <el-table-column prop="suggestion" label="建议" min-width="150">
- <template #default="{ row }">
- <el-tag :type="getInventorySuggestion(row.suggestion).type" size="small">
- {{ getInventorySuggestion(row.suggestion).label }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="150" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" @click="openDetail(row)">详情</el-button>
- <el-button link type="success" @click="createOrder(row)">补货</el-button>
- <el-button link type="danger" @click="handleLiquidation(row)">清仓</el-button>
- </template>
- </el-table-column>
- </el-table>
- </section>
- <section class="glass-card section-card">
- <div class="section-header">
- <h3>库存周转明细</h3>
- </div>
- <el-table :data="filteredItems" style="width:100%" v-loading="loading">
- <el-table-column prop="sku" label="SKU" width="160" />
- <el-table-column prop="productTitle" label="商品名称" min-width="200" show-overflow-tooltip />
- <el-table-column prop="category" label="类目" width="120" />
- <el-table-column prop="warehouse" label="仓库" width="120" />
- <el-table-column prop="available" label="可用库存" width="90" align="center" />
- <el-table-column prop="sales30d" label="30天销量" width="90" align="center" />
- <el-table-column prop="turnoverDays" label="周转天数" width="100" align="center">
- <template #default="{ row }">
- <span :class="turnoverClass(row.turnoverDays)">{{ row.turnoverDays }}天</span>
- </template>
- </el-table-column>
- <el-table-column prop="stockDays" label="库存覆盖天数" width="110" align="center">
- <template #default="{ row }">
- <span :class="stockDaysClass(row.stockDays)">{{ row.stockDays }}天</span>
- </template>
- </el-table-column>
- <el-table-column prop="avgAge" label="平均库龄" width="90" align="center">
- <template #default="{ row }">
- <el-tag size="small" :type="avgAgeTag(row.avgAge)">{{ row.avgAge }}天</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template #default="{ row }">
- <el-tag :type="getInventoryTurnoverStatus(row.status).type" size="small">
- {{ getInventoryTurnoverStatus(row.status).label }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="140" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" @click="openDetail(row)">详情</el-button>
- <el-button link type="success" @click="createOrder(row)">补货</el-button>
- </template>
- </el-table-column>
- </el-table>
- </section>
- </div>
- </template>
- <script setup lang="ts">
- import { onMounted, ref, computed, watch } from 'vue';
- import { ElMessage } from 'element-plus';
- import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
- import * as echarts from 'echarts';
- import { getInventoryTurnoverStatus, getInventorySuggestion } from '@/utils/enumMappings';
- interface TurnoverItem {
- sku: string;
- productTitle: string;
- category: string;
- warehouse: string;
- available: number;
- sales30d: number;
- turnoverDays: number;
- stockDays: number;
- avgAge: number;
- status: string;
- suggestion?: string;
- }
- const items = ref<TurnoverItem[]>([
- { sku: 'SKU-LUGG-20-BLK', productTitle: 'TravelFlex Carry-On 20寸 / Black', category: '行李箱', warehouse: '深圳南山仓', available: 15, sales30d: 240, turnoverDays: 15, stockDays: 18, avgAge: 20, status: '正常', suggestion: '正常' },
- { sku: 'SKU-BAG-ML-BRW', productTitle: 'Classic Leather Tote / Brown', category: '皮革包', warehouse: '义乌商贸仓', available: 45, sales30d: 150, turnoverDays: 28, stockDays: 30, avgAge: 25, status: '正常', suggestion: '正常' },
- { sku: 'SKU-SPRT-YGA-BLU', productTitle: 'Yoga Mat Pro / Blue', category: '运动瑜伽', warehouse: '深圳南山仓', available: 20, sales30d: 360, turnoverDays: 8, stockDays: 5, avgAge: 12, status: '正常', suggestion: '加仓' },
- { sku: 'SKU-LUGG-28-NVY', productTitle: 'TravelFlex Large Check-In / Navy', category: '行李箱', warehouse: '洛杉矶海外仓', available: 8, sales30d: 90, turnoverDays: 45, stockDays: 9, avgAge: 60, status: '滞销', suggestion: '促销' },
- { sku: 'SKU-BAG-BPK-OLV', productTitle: 'Urban Backpack / Olive', category: '双肩包', warehouse: '义乌商贸仓', available: 55, sales30d: 180, turnoverDays: 32, stockDays: 30, avgAge: 40, status: '正常', suggestion: '正常' },
- { sku: 'SKU-TOWEL-SET-MIX', productTitle: 'AeroDry Towel Set / 混色', category: '毛巾浴袍', warehouse: '深圳南山仓', available: 30, sales30d: 300, turnoverDays: 12, stockDays: 10, avgAge: 18, status: '正常', suggestion: '加仓' },
- { sku: 'SKU-SPRT-BTL-GRN', productTitle: 'Sports Bottle 750ml / Green', category: '运动水壶', warehouse: '洛杉矶海外仓', available: 120, sales30d: 60, turnoverDays: 60, stockDays: 200, avgAge: 90, status: '严重滞销', suggestion: '清仓' },
- { sku: 'SKU-LUGG-24-RED', productTitle: 'TravelFlex Medium / Red', category: '行李箱', warehouse: '义乌商贸仓', available: 5, sales30d: 50, turnoverDays: 75, stockDays: 10, avgAge: 85, status: '严重滞销', suggestion: '清仓' },
- { sku: 'SKU-BAG-WLT-GLD', productTitle: 'Designer Wallet / Gold', category: '钱包卡包', warehouse: '深圳南山仓', available: 80, sales30d: 40, turnoverDays: 70, stockDays: 200, avgAge: 95, status: '严重滞销', suggestion: '清仓' },
- { sku: 'SKU-SPRT-MAT-GRY', productTitle: 'Fitness Mat / Grey', category: '运动瑜伽', warehouse: '洛杉矶海外仓', available: 15, sales30d: 100, turnoverDays: 35, stockDays: 15, avgAge: 30, status: '正常', suggestion: '正常' }
- ]);
- const loading = ref(false);
- const timeRange = ref('30d');
- const warehouses = ['深圳南山仓', '义乌商贸仓', '洛杉矶海外仓'];
- const filters = ref({ sku: '', warehouse: '', dimension: '', status: '' });
- const kpiData = ref({
- turnoverDays: 28,
- salesRate: 76,
- slowMoving: 15,
- coverageDays: 42,
- avgAge: 35
- });
- let turnoverTrendChart: echarts.ECharts | null = null;
- let warehouseComparisonChart: echarts.ECharts | null = null;
- let categoryPieChart: echarts.ECharts | null = null;
- let stockDistributionChart: echarts.ECharts | null = null;
- let turnoverGaugeChart: echarts.ECharts | null = null;
- const turnoverTrendRef = ref<HTMLDivElement | null>(null);
- const warehouseComparisonRef = ref<HTMLDivElement | null>(null);
- const categoryPieRef = ref<HTMLDivElement | null>(null);
- const stockDistributionRef = ref<HTMLDivElement | null>(null);
- const turnoverGaugeRef = ref<HTMLDivElement | null>(null);
- const filteredItems = computed(() => {
- return items.value.filter(item => {
- if (filters.value.sku && !item.sku.toLowerCase().includes(filters.value.sku.toLowerCase()) && !item.productTitle.toLowerCase().includes(filters.value.sku.toLowerCase())) return false;
- if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
- if (filters.value.status && item.status !== filters.value.status) return false;
- return true;
- });
- });
- const alertItems = computed(() => {
- return items.value.filter(item => item.status === '滞销' || item.status === '严重滞销');
- });
- const initTurnoverTrendChart = () => {
- if (!turnoverTrendRef.value) return;
- turnoverTrendChart = echarts.init(turnoverTrendRef.value);
- const days = timeRange.value === '7d' ? 7 : timeRange.value === '30d' ? 30 : 90;
- const data = Array.from({ length: days }, (_, i) => ({
- day: `Day ${i + 1}`,
- turnover: Math.round(20 + Math.random() * 20),
- target: 30
- }));
-
- turnoverTrendChart.setOption({
- title: { text: '周转天数趋势', left: 'center', textStyle: { fontSize: 14, fontWeight: 500 } },
- tooltip: { trigger: 'axis' },
- legend: { data: ['实际周转', '目标'], bottom: 0 },
- grid: { left: '3%', right: '4%', bottom: '15%', top: '15%', containLabel: true },
- xAxis: { type: 'category', data: data.map(d => d.day), boundaryGap: false },
- yAxis: { type: 'value', name: '天数' },
- series: [
- { name: '实际周转', type: 'line', smooth: true, data: data.map(d => d.turnover), areaStyle: { color: 'rgba(102, 126, 234, 0.2)' }, lineStyle: { color: '#667eea' }, itemStyle: { color: '#667eea' } },
- { name: '目标', type: 'line', data: data.map(d => d.target), lineStyle: { type: 'dashed', color: '#999' }, itemStyle: { color: '#999' } }
- ]
- });
- };
- const initWarehouseComparisonChart = () => {
- if (!warehouseComparisonRef.value) return;
- warehouseComparisonChart = echarts.init(warehouseComparisonRef.value);
-
- warehouseComparisonChart.setOption({
- title: { text: '仓库周转对比', left: 'center', textStyle: { fontSize: 14, fontWeight: 500 } },
- tooltip: { trigger: 'axis' },
- legend: { data: ['周转天数', '目标'], bottom: 0 },
- grid: { left: '3%', right: '4%', bottom: '15%', top: '15%', containLabel: true },
- xAxis: { type: 'category', data: warehouses },
- yAxis: { type: 'value', name: '天数' },
- series: [
- { name: '周转天数', type: 'bar', data: [25, 32, 45], barWidth: '40%', itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#4facfe' }, { offset: 1, color: '#00f2fe' }]) } },
- { name: '目标', type: 'line', data: [30, 30, 30], lineStyle: { type: 'dashed' }, itemStyle: { color: '#f5576c' } }
- ]
- });
- };
- const initCategoryPieChart = () => {
- if (!categoryPieRef.value) return;
- categoryPieChart = echarts.init(categoryPieRef.value);
-
- categoryPieChart.setOption({
- title: { text: '类目库存占比', left: 'center', textStyle: { fontSize: 14, fontWeight: 500 } },
- tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
- legend: { orient: 'vertical', right: '5%', top: 'center' },
- series: [{
- type: 'pie',
- radius: ['40%', '70%'],
- center: ['40%', '50%'],
- data: [
- { value: 35, name: '行李箱' },
- { value: 25, name: '皮革包' },
- { value: 18, name: '运动瑜伽' },
- { value: 12, name: '双肩包' },
- { value: 10, name: '其他' }
- ],
- label: { show: false }
- }]
- });
- };
- const initStockDistributionChart = () => {
- if (!stockDistributionRef.value) return;
- stockDistributionChart = echarts.init(stockDistributionRef.value);
-
- stockDistributionChart.setOption({
- title: { text: '库存周转分布', left: 'center', textStyle: { fontSize: 14, fontWeight: 500 } },
- tooltip: { trigger: 'axis' },
- grid: { left: '3%', right: '4%', bottom: '10%', top: '15%', containLabel: true },
- xAxis: { type: 'category', data: ['<15天', '15-30天', '30-45天', '45-60天', '>60天'] },
- yAxis: { type: 'value', name: 'SKU数' },
- series: [{
- type: 'bar',
- data: [
- { value: 25, itemStyle: { color: '#11998e' } },
- { value: 40, itemStyle: { color: '#38ef7d' } },
- { value: 20, itemStyle: { color: '#fee140' } },
- { value: 10, itemStyle: { color: '#f5576c' } },
- { value: 5, itemStyle: { color: '#fa709a' } }
- ],
- barWidth: '50%'
- }]
- });
- };
- const initTurnoverGaugeChart = () => {
- if (!turnoverGaugeRef.value) return;
- turnoverGaugeChart = echarts.init(turnoverGaugeRef.value);
-
- turnoverGaugeChart.setOption({
- title: { text: '综合周转指数', left: 'center', textStyle: { fontSize: 14, fontWeight: 500 } },
- series: [{
- type: 'gauge',
- center: ['50%', '60%'],
- startAngle: 200,
- endAngle: -20,
- min: 0,
- max: 100,
- splitNumber: 10,
- itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: '#11998e' }, { offset: 0.5, color: '#fee140' }, { offset: 1, color: '#f5576c' }]) },
- progress: { show: true, width: 20 },
- pointer: { show: false },
- axisLine: { lineStyle: { width: 20 } },
- axisTick: { show: false },
- splitLine: { show: false },
- axisLabel: { show: false },
- anchor: { show: false },
- title: { show: false },
- detail: { valueAnimation: true, fontSize: 28, offsetCenter: [0, '10%'], formatter: '{value}分', color: '#333' },
- data: [{ value: 72 }]
- }, {
- type: 'pie',
- radius: ['75%', '85%'],
- center: ['50%', '60%'],
- startAngle: 200,
- endAngle: -20,
- itemStyle: { color: '#f5f5f5' },
- label: { show: false },
- data: [{ value: 100 }]
- }]
- });
- };
- const initAllCharts = () => {
- initTurnoverTrendChart();
- initWarehouseComparisonChart();
- initCategoryPieChart();
- initStockDistributionChart();
- initTurnoverGaugeChart();
- };
- const handleResize = () => {
- turnoverTrendChart?.resize();
- warehouseComparisonChart?.resize();
- categoryPieChart?.resize();
- stockDistributionChart?.resize();
- turnoverGaugeChart?.resize();
- };
- const turnoverClass = (days: number) => {
- if (days > 60) return 'text-danger';
- if (days > 30) return 'text-warning';
- return '';
- };
- const stockDaysClass = (days: number) => {
- if (days < 10) return 'text-danger';
- if (days < 20) return 'text-warning';
- return '';
- };
- const avgAgeTag = (age: number) => {
- if (age > 60) return 'danger';
- if (age > 30) return 'warning';
- return 'info';
- };
- const stockProgressStatus = (days: number) => {
- if (days > 60) return 'exception';
- if (days > 30) return 'warning';
- return 'success';
- };
- const tableRowClass = ({ row }: { row: TurnoverItem }) => {
- if (row.status === '严重滞销') return 'danger-row';
- if (row.status === '滞销') return 'warning-row';
- return '';
- };
- const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; initAllCharts(); }, 300); };
- const resetFilters = () => { filters.value = { sku: '', warehouse: '', dimension: '', status: '' }; };
- const setAlert = () => { ElMessage.info('预警设置功能开发中'); };
- const doExport = () => { ElMessage.info('导出开始'); };
- const openDetail = (row: TurnoverItem) => { ElMessage.info(`查看 ${row.sku} 详情`); };
- const createOrder = (row: TurnoverItem) => { ElMessage.success(`已为 ${row.sku} 生成补货建议`); };
- const handleLiquidation = (row: TurnoverItem) => { ElMessage.warning(`已提交 ${row.sku} 清仓申请`); };
- const generateReplenishment = () => { ElMessage.success('已生成补货单,共 3 个SKU待处理'); };
- watch(timeRange, () => { initTurnoverTrendChart(); });
- onMounted(() => {
- loadData();
- window.addEventListener('resize', handleResize);
- });
- </script>
- <style scoped>
- .filter-form :deep(.el-form-item) { margin-bottom: 0; }
- .section-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
- .section-header h3 { margin: 0; font-size: 15px; font-weight: 600; color: #333; }
- .chart-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 16px; }
- .chart-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; padding: 16px; }
- .chart-container { background: #fafafa; border-radius: 8px; padding: 8px; }
- .text-danger { color: #f5576c; font-weight: 600; }
- .text-warning { color: #fee140; font-weight: 600; }
- .stat-card { display: flex; align-items: center; gap: 12px; }
- .stat-card__icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
- .stat-card__icon svg { width: 24px; height: 24px; }
- .stat-card__content { flex: 1; min-width: 0; }
- .stat-card__label { font-size: 12px; color: #666; margin-bottom: 4px; }
- .stat-card__value { font-size: 24px; font-weight: 700; line-height: 1.2; }
- .stat-card__trend { font-size: 12px; margin-top: 4px; display: flex; align-items: center; gap: 2px; }
- .stat-card__trend.up { color: #11998e; }
- .stat-card__trend.down { color: #f5576c; }
- .stat-card__trend.neutral { color: #999; }
- .stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; }
- :deep(.danger-row) { background-color: #fff5f5 !important; }
- :deep(.warning-row) { background-color: #fffbf0 !important; }
- </style>
|