refactor(wel): 重构欢迎页为模块化组件
- 将欢迎页整体结构拆分为多个功能区组件,提高代码可维护性 - 移除原有欢迎横幅、指标卡、图表区、下方区及快捷入口的静态实现 - 引入并注册 SectionKpi、SectionStructure、SectionBusiness 等八个子组件 - 删除冗余样式,更新整体背景和布局样式 - 精简模板内容,仅保留子组件渲染及页脚版权信息 - 移除无用的响应式数据、计算属性及方法 - 包含依赖包 echarts 的新增,支持后续图表功能开发
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="wel-card rank-panel">
|
||||
<div class="panel-title">
|
||||
{{ title }}
|
||||
<span v-if="unit" class="panel-title__unit">{{ unit }}</span>
|
||||
</div>
|
||||
<wel-chart
|
||||
v-if="type === 'rank'"
|
||||
:option="rankOpt"
|
||||
:height="height"
|
||||
/>
|
||||
<wel-chart v-else :option="diffOpt" :height="height" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelChart from './wel-chart.vue';
|
||||
import { rankOption, diffOption } from '../chart-options';
|
||||
|
||||
export default {
|
||||
name: 'rank-panel',
|
||||
components: { WelChart },
|
||||
props: {
|
||||
title: { type: String, required: true },
|
||||
unit: { type: String, default: '' },
|
||||
/* rank = 普通排行;diff = 盈亏双向对比 */
|
||||
type: { type: String, default: 'rank' },
|
||||
color: { type: String, default: '#4a90e2' },
|
||||
items: { type: Array, required: true },
|
||||
height: { type: String, default: '300px' },
|
||||
nameWidth: { type: Number, default: 100 },
|
||||
},
|
||||
computed: {
|
||||
rankOpt() {
|
||||
return rankOption(this.items, { color: this.color, unit: this.unit, nameWidth: this.nameWidth });
|
||||
},
|
||||
diffOpt() {
|
||||
return diffOption(this.items);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">业务单元情况</span>
|
||||
<wel-filters condition-label="业务单元" />
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="p in panels" :key="p.title" :xs="24" :md="12">
|
||||
<rank-panel
|
||||
:title="p.title"
|
||||
:unit="p.unit"
|
||||
:type="p.type"
|
||||
:color="p.color"
|
||||
:items="p.items"
|
||||
:height="p.height"
|
||||
:name-width="p.nameWidth"
|
||||
class="business-panel"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RankPanel from './rank-panel.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
|
||||
export default {
|
||||
name: 'section-business',
|
||||
components: { RankPanel, WelFilters },
|
||||
data() {
|
||||
return {
|
||||
// Mock:业务单元排行数据,对接统计接口后替换
|
||||
panels: [
|
||||
{
|
||||
title: '营业收入排行',
|
||||
unit: '万元',
|
||||
type: 'rank',
|
||||
color: '#4a90e2',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '广西物产集团', value: 5218.7, change: 12.3 },
|
||||
{ name: '南宁港务物流', value: 4183.4, change: 8.6 },
|
||||
{ name: '柳州钢铁物流', value: 3603.9, change: -2.4 },
|
||||
{ name: '北部湾港务', value: 3131.7, change: 5.1 },
|
||||
{ name: '桂林粮油储运', value: 2750.3, change: 9.8 },
|
||||
{ name: '玉林建材集团', value: 2455.5, change: -1.2 },
|
||||
{ name: '钦州石化物流', value: 2088.3, change: 6.5 },
|
||||
{ name: '贵港航运公司', value: 1823.5, change: 3.9 },
|
||||
{ name: '百色矿产运输', value: 1588.4, change: -4.7 },
|
||||
{ name: '梧州商贸物流', value: 1214.8, change: 2.2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运输情况排行',
|
||||
unit: '单',
|
||||
type: 'rank',
|
||||
color: '#2bb3a3',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '广西物产集团', value: 238.2, change: 9.4 },
|
||||
{ name: '南宁港务物流', value: 196.3, change: 6.7 },
|
||||
{ name: '柳州钢铁物流', value: 168.1, change: 3.2 },
|
||||
{ name: '北部湾港务', value: 154.7, change: -2.8 },
|
||||
{ name: '桂林粮油储运', value: 139.2, change: 7.5 },
|
||||
{ name: '玉林建材集团', value: 128.6, change: 4.1 },
|
||||
{ name: '钦州石化物流', value: 118.3, change: -1.6 },
|
||||
{ name: '贵港航运公司', value: 106.2, change: 5.3 },
|
||||
{ name: '百色矿产运输', value: 98.8, change: 2.9 },
|
||||
{ name: '梧州商贸物流', value: 89.6, change: -3.4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '各部门毛利占比',
|
||||
unit: '%',
|
||||
type: 'rank',
|
||||
color: '#c9a227',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '公路运输一部', value: 30.7 },
|
||||
{ name: '公路运输二部', value: 28.2 },
|
||||
{ name: '铁路物流部', value: 23.2 },
|
||||
{ name: '水运事业部', value: 19.6 },
|
||||
{ name: '多式联运部', value: 15.2 },
|
||||
{ name: '仓储配送部', value: 13.1 },
|
||||
{ name: '冷链事业部', value: 10.4 },
|
||||
{ name: '大件运输部', value: 8.6 },
|
||||
{ name: '危险品运输部', value: 5.2 },
|
||||
{ name: '综合业务部', value: 2.8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '项目盈亏对比',
|
||||
unit: '万元',
|
||||
type: 'diff',
|
||||
color: '#e04b4b',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '铝锭外运项目', value: 156.5 },
|
||||
{ name: '冷链城配项目', value: 134.8 },
|
||||
{ name: '钢材南下项目', value: 118.2 },
|
||||
{ name: '粮食储备调运', value: 96.1 },
|
||||
{ name: '矿石短驳项目', value: 82.6 },
|
||||
{ name: '集装箱班列', value: 74.3 },
|
||||
{ name: '化工原料运输', value: 61.2 },
|
||||
{ name: '建材集散运输', value: 48.9 },
|
||||
{ name: '汽车零部件配送', value: 21.5 },
|
||||
{ name: '设备大件运输', value: 16.2 },
|
||||
{ name: '生鲜冷链专线', value: -8.3 },
|
||||
{ name: '危化品试点线', value: -25.4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运单量排行',
|
||||
unit: '单',
|
||||
type: 'rank',
|
||||
color: '#4a90e2',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '南宁分部', value: 320, change: 6.3 },
|
||||
{ name: '柳州分部', value: 273, change: 4.8 },
|
||||
{ name: '桂林分部', value: 241, change: -2.1 },
|
||||
{ name: '玉林分部', value: 215, change: 8.2 },
|
||||
{ name: '钦州港分部', value: 203, change: 3.6 },
|
||||
{ name: '贵港分部', value: 187, change: -1.4 },
|
||||
{ name: '百色分部', value: 158, change: 5.7 },
|
||||
{ name: '梧州分部', value: 132, change: 2.3 },
|
||||
{ name: '北海分部', value: 108, change: -3.9 },
|
||||
{ name: '河池分部', value: 86, change: 1.6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运输收入排行',
|
||||
unit: '万元',
|
||||
type: 'rank',
|
||||
color: '#8b5cf6',
|
||||
height: '320px',
|
||||
items: [
|
||||
{ name: '公路整车', value: 1863.2, change: 7.1 },
|
||||
{ name: '铁路整车', value: 1421.8, change: 4.5 },
|
||||
{ name: '水运集装箱', value: 1182.3, change: 9.2 },
|
||||
{ name: '公路零担', value: 968.5, change: 2.8 },
|
||||
{ name: '多式联运', value: 812.4, change: 11.6 },
|
||||
{ name: '航空货运', value: 655.7, change: -1.8 },
|
||||
{ name: '冷链专线', value: 542.1, change: 6.4 },
|
||||
{ name: '大件运输', value: 388.9, change: 3.1 },
|
||||
{ name: '危险品运输', value: 296.3, change: -2.5 },
|
||||
{ name: '仓储增值服务', value: 184.6, change: 8.8 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.business-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">财务情况</span>
|
||||
<wel-filters condition-label="核算口径" />
|
||||
</div>
|
||||
|
||||
<div class="wel-card finance-panel">
|
||||
<div class="panel-title">
|
||||
营业收支对比
|
||||
<span class="panel-title__unit">万元</span>
|
||||
</div>
|
||||
<wel-chart :option="barOpt" height="300px" />
|
||||
</div>
|
||||
|
||||
<div class="wel-card finance-panel">
|
||||
<div class="panel-title">
|
||||
主营业务收入/费用趋势
|
||||
<span class="panel-title__unit">万元</span>
|
||||
</div>
|
||||
<wel-chart :option="lineOpt" height="280px" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelChart from './wel-chart.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
import { groupBarOption, lineAreaOption } from '../chart-options';
|
||||
|
||||
export default {
|
||||
name: 'section-finance',
|
||||
components: { WelChart, WelFilters },
|
||||
data() {
|
||||
const months = Array.from({ length: 12 }, (_, i) => `${i + 1}月`);
|
||||
return {
|
||||
months,
|
||||
// Mock:财务收支数据,对接统计接口后替换
|
||||
incomeData: [2860, 1520, 1180, 1150, 980, 920, 760, 720, 690, 520, 380, 260],
|
||||
costData: [2410, 1330, 1010, 980, 850, 800, 660, 630, 610, 470, 340, 230],
|
||||
trendIncome: [1780, 1850, 1810, 1880, 1920, 1950, 1720, 1650, 1830, 1900, 1980, 2060],
|
||||
trendCost: [1520, 1560, 1540, 1590, 1610, 1620, 1450, 1400, 1530, 1580, 1640, 1700],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
barOpt() {
|
||||
return groupBarOption(this.months, [
|
||||
{ name: '收入', data: this.incomeData, color: '#4a90e2' },
|
||||
{ name: '费用', data: this.costData, color: '#d9dce3' },
|
||||
]);
|
||||
},
|
||||
lineOpt() {
|
||||
return lineAreaOption(this.months, [
|
||||
{ name: '主营业务收入', data: this.trendIncome, color: '#4a90e2' },
|
||||
{ name: '主营业务费用', data: this.trendCost, color: '#2bb3a3' },
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.finance-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">车队与线路</span>
|
||||
<wel-filters condition-label="运输方式" />
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :md="12">
|
||||
<rank-panel
|
||||
title="下游车队 TOP10"
|
||||
unit="单"
|
||||
color="#1a9e94"
|
||||
:items="fleetItems"
|
||||
height="320px"
|
||||
:name-width="120"
|
||||
class="fleet-panel"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12">
|
||||
<rank-panel
|
||||
title="运输线路 TOP5"
|
||||
unit="单"
|
||||
color="#c9a227"
|
||||
:items="routeItems"
|
||||
height="320px"
|
||||
:name-width="120"
|
||||
class="fleet-panel"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RankPanel from './rank-panel.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
|
||||
export default {
|
||||
name: 'section-fleet',
|
||||
components: { RankPanel, WelFilters },
|
||||
data() {
|
||||
return {
|
||||
// Mock:车队/线路排行数据,对接统计接口后替换
|
||||
fleetItems: [
|
||||
{ name: '顺达车队', value: 186.5 },
|
||||
{ name: '穗联物流车队', value: 158.2 },
|
||||
{ name: '宏运车队', value: 132.6 },
|
||||
{ name: '桂海运输车队', value: 118.4 },
|
||||
{ name: '南北快运车队', value: 96.7 },
|
||||
{ name: '恒通货运车队', value: 85.3 },
|
||||
{ name: '运达车队', value: 74.2 },
|
||||
{ name: '捷安物流车队', value: 62.8 },
|
||||
{ name: '广通运输车队', value: 54.6 },
|
||||
{ name: '长顺车队', value: 43.1 },
|
||||
],
|
||||
routeItems: [
|
||||
{ name: '南宁 → 柳州', value: 342.5 },
|
||||
{ name: '钦州港 → 南宁', value: 285.6 },
|
||||
{ name: '贵港 → 广州', value: 226.3 },
|
||||
{ name: '桂林 → 南宁', value: 168.2 },
|
||||
{ name: '玉林 → 深圳', value: 142.6 },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.fleet-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">核心经营指标</span>
|
||||
<wel-filters />
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="card in cards" :key="card.label" :xs="24" :md="8">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__top">
|
||||
<span class="kpi-card__label">{{ card.label }}</span>
|
||||
<span
|
||||
class="kpi-card__badge"
|
||||
:class="card.trend >= 0 ? 'is-up' : 'is-down'"
|
||||
>
|
||||
环比 {{ card.trend >= 0 ? '+' : '' }}{{ card.trend }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="kpi-card__value">
|
||||
{{ card.value }}<span class="kpi-card__unit">{{ card.unit }}</span>
|
||||
</div>
|
||||
<ul class="kpi-card__subs">
|
||||
<li v-for="s in card.subs" :key="s.name">
|
||||
<span class="kpi-card__sub-name">{{ s.name }}</span>
|
||||
<span class="kpi-card__sub-value">{{ s.value }}</span>
|
||||
<span
|
||||
v-if="typeof s.change === 'number'"
|
||||
class="kpi-card__sub-change"
|
||||
:class="s.change >= 0 ? 'is-up' : 'is-down'"
|
||||
>
|
||||
{{ s.change >= 0 ? '↑' : '↓' }} {{ Math.abs(s.change) }}%
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelFilters from './wel-filters.vue';
|
||||
|
||||
export default {
|
||||
name: 'section-kpi',
|
||||
components: { WelFilters },
|
||||
data() {
|
||||
return {
|
||||
// Mock:营业额/运单量/利润等核心指标,对接统计接口后替换
|
||||
cards: [
|
||||
{
|
||||
label: '营业额',
|
||||
value: '58,814.0',
|
||||
unit: '万元',
|
||||
trend: 12.6,
|
||||
subs: [
|
||||
{ name: '毛利率', value: '23.5%', change: 1.2 },
|
||||
{ name: '运输成本', value: '45,031 万元', change: -0.8 },
|
||||
{ name: '回款金额', value: '39,640 万元', change: 3.4 },
|
||||
{ name: '应收账款', value: '19,174 万元', change: -2.1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '运单量',
|
||||
value: '1,497.1',
|
||||
unit: '单',
|
||||
trend: 8.4,
|
||||
subs: [
|
||||
{ name: '已完成', value: '1,205 单', change: 6.2 },
|
||||
{ name: '在途', value: '135 单', change: 12.0 },
|
||||
{ name: '待发运', value: '92 单', change: -4.5 },
|
||||
{ name: '异常单', value: '65 单', change: 1.8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '运输利润',
|
||||
value: '2,058.2',
|
||||
unit: '万元',
|
||||
trend: 15.7,
|
||||
subs: [
|
||||
{ name: '利润率', value: '13.7%', change: 1.5 },
|
||||
{ name: '单均利润', value: '1.38 万元', change: 5.6 },
|
||||
{ name: '燃油成本', value: '8,931 万元', change: -3.2 },
|
||||
{ name: '过路费用', value: '4,208 万元', change: 0.9 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.kpi-card {
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.kpi-card__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.kpi-card__label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.kpi-card__badge {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
|
||||
&.is-up {
|
||||
color: #f04747;
|
||||
background: #fdecec;
|
||||
}
|
||||
|
||||
&.is-down {
|
||||
color: #2ba471;
|
||||
background: #e8f7ef;
|
||||
}
|
||||
}
|
||||
|
||||
.kpi-card__value {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.kpi-card__unit {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #909399;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.kpi-card__subs {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 10px 0 0;
|
||||
border-top: 1px solid #f0f2f5;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.kpi-card__sub-name {
|
||||
color: #909399;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.kpi-card__sub-value {
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.kpi-card__sub-change {
|
||||
width: 56px;
|
||||
text-align: right;
|
||||
|
||||
&.is-up {
|
||||
color: #f04747;
|
||||
}
|
||||
|
||||
&.is-down {
|
||||
color: #2ba471;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">项目情况</span>
|
||||
<wel-filters condition-label="项目状态" />
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :md="12">
|
||||
<rank-panel
|
||||
title="项目收入 TOP5"
|
||||
unit="万元"
|
||||
color="#8b5cf6"
|
||||
:items="incomeItems"
|
||||
height="280px"
|
||||
:name-width="180"
|
||||
class="project-panel"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12">
|
||||
<rank-panel
|
||||
title="项目运量 TOP5"
|
||||
unit="万吨"
|
||||
color="#2bb3a3"
|
||||
:items="volumeItems"
|
||||
height="280px"
|
||||
:name-width="180"
|
||||
class="project-panel"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RankPanel from './rank-panel.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
|
||||
export default {
|
||||
name: 'section-project',
|
||||
components: { RankPanel, WelFilters },
|
||||
data() {
|
||||
return {
|
||||
// Mock:项目排行数据,对接统计接口后替换
|
||||
incomeItems: [
|
||||
{ name: '广西华谊氯碱化工物流项目', value: 1285.6 },
|
||||
{ name: '柳钢集团钢材外运项目(二期)', value: 982.4 },
|
||||
{ name: '南宁轨道交通建材配送项目', value: 756.3 },
|
||||
{ name: '北部湾港集装箱短驳项目', value: 621.7 },
|
||||
{ name: '中粮集团粮食储备调运项目', value: 468.2 },
|
||||
],
|
||||
volumeItems: [
|
||||
{ name: '广西华谊氯碱化工物流项目', value: 32.5 },
|
||||
{ name: '柳钢集团钢材外运项目(二期)', value: 26.8 },
|
||||
{ name: '南宁轨道交通建材配送项目', value: 21.4 },
|
||||
{ name: '北部湾港集装箱短驳项目', value: 18.6 },
|
||||
{ name: '中粮集团粮食储备调运项目', value: 15.2 },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.project-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">运输结构</span>
|
||||
<wel-filters condition-label="业务类型" />
|
||||
</div>
|
||||
|
||||
<!-- 小指标卡 -->
|
||||
<el-row :gutter="16" class="structure-cards">
|
||||
<el-col v-for="c in miniCards" :key="c.label" :xs="12" :md="6">
|
||||
<div class="mini-card structure-cards__item">
|
||||
<div class="mini-card__label">{{ c.label }}</div>
|
||||
<div class="mini-card__value">
|
||||
{{ c.value }}<span class="mini-card__unit">{{ c.unit }}</span>
|
||||
</div>
|
||||
<div v-if="typeof c.change === 'number'" class="mini-card__trend" :class="c.change >= 0 ? 'is-up' : 'is-down'">
|
||||
环比 {{ c.change >= 0 ? '+' : '' }}{{ c.change }}%
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 三个占比环形图 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col v-for="d in donuts" :key="d.title" :xs="24" :md="8">
|
||||
<div class="wel-card donut-panel">
|
||||
<div class="panel-title">
|
||||
{{ d.title }}
|
||||
<span class="panel-title__unit">{{ d.unit }}</span>
|
||||
</div>
|
||||
<wel-chart :option="donutOpt(d)" height="230px" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelChart from './wel-chart.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
import { donutOption } from '../chart-options';
|
||||
|
||||
export default {
|
||||
name: 'section-structure',
|
||||
components: { WelChart, WelFilters },
|
||||
data() {
|
||||
return {
|
||||
// Mock:运输结构统计,对接统计接口后替换
|
||||
miniCards: [
|
||||
{ label: '总运单量', value: '1,391', unit: '单', change: 30 },
|
||||
{ label: '在途运单', value: '487', unit: '单', change: 12.5 },
|
||||
{ label: '待发运', value: '135', unit: '单', change: -6.3 },
|
||||
{ label: '总运量', value: '15.2', unit: '万吨', change: 8.1 },
|
||||
],
|
||||
donuts: [
|
||||
{
|
||||
title: '运输方式占比',
|
||||
unit: '按运单量',
|
||||
centerTitle: '1,391',
|
||||
centerSub: '总运单(单)',
|
||||
items: [
|
||||
{ name: '公路', value: 620 },
|
||||
{ name: '铁路', value: 335 },
|
||||
{ name: '水运', value: 215 },
|
||||
{ name: '航空', value: 121 },
|
||||
{ name: '多式联运', value: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '货物类型占比',
|
||||
unit: '按运量',
|
||||
centerTitle: '15.2',
|
||||
centerSub: '总运量(万吨)',
|
||||
items: [
|
||||
{ name: '普货', value: 6.4 },
|
||||
{ name: '冷链', value: 3.2 },
|
||||
{ name: '大件', value: 2.6 },
|
||||
{ name: '危险品', value: 1.8 },
|
||||
{ name: '其他', value: 1.2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '客户类型占比',
|
||||
unit: '按运单量',
|
||||
centerTitle: '326',
|
||||
centerSub: '合作客户(家)',
|
||||
items: [
|
||||
{ name: '合同客户', value: 810 },
|
||||
{ name: '散单客户', value: 372 },
|
||||
{ name: '平台客户', value: 209 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
donutOpt(d) {
|
||||
return donutOption(d.items, { centerTitle: d.centerTitle, centerSub: d.centerSub });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.structure-cards__item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.donut-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="wel-card summary-panel">
|
||||
<div class="panel-title">
|
||||
车辆耗时占比
|
||||
<span class="panel-title__unit">按小时</span>
|
||||
</div>
|
||||
<wel-chart :option="donutOpt" height="260px" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12">
|
||||
<div class="wel-card summary-panel">
|
||||
<div class="panel-title">
|
||||
单据完成趋势
|
||||
<span class="panel-title__unit">按周</span>
|
||||
</div>
|
||||
<wel-chart :option="lineOpt" height="260px" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelChart from './wel-chart.vue';
|
||||
import { donutOption, lineAreaOption } from '../chart-options';
|
||||
|
||||
export default {
|
||||
name: 'section-summary',
|
||||
components: { WelChart },
|
||||
data() {
|
||||
return {
|
||||
// Mock:车辆耗时与单据完成数据,对接统计接口后替换
|
||||
timeItems: [
|
||||
{ name: '在途运输', value: 86.2 },
|
||||
{ name: '装货等待', value: 32.5 },
|
||||
{ name: '卸货作业', value: 28.4 },
|
||||
{ name: '返程空驶', value: 21.6 },
|
||||
{ name: '场站等待', value: 18.3 },
|
||||
{ name: '维修保养', value: 9.8 },
|
||||
],
|
||||
weeks: ['1周', '2周', '3周', '4周', '5周', '6周', '7周'],
|
||||
doneData: [520, 545, 508, 532, 560, 548, 572],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
donutOpt() {
|
||||
return donutOption(this.timeItems, { centerTitle: '196.8', centerSub: '总耗时(小时)' });
|
||||
},
|
||||
lineOpt() {
|
||||
return lineAreaOption(this.weeks, [
|
||||
{ name: '完成单据', data: this.doneData, color: '#1a9e94' },
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.summary-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<section class="wel-sec">
|
||||
<div class="wel-sec__head">
|
||||
<span class="wel-sec__title">车务管理</span>
|
||||
<wel-filters condition-label="车辆类型" />
|
||||
</div>
|
||||
|
||||
<!-- 车务小指标卡 -->
|
||||
<el-row :gutter="16" class="vehicle-cards">
|
||||
<el-col v-for="c in miniCards" :key="c.label" :xs="12" :md="6">
|
||||
<div class="mini-card vehicle-cards__item">
|
||||
<div class="mini-card__label">{{ c.label }}</div>
|
||||
<div class="mini-card__value">
|
||||
{{ c.value }}<span class="mini-card__unit">{{ c.unit }}</span>
|
||||
</div>
|
||||
<div v-if="typeof c.change === 'number'" class="mini-card__trend" :class="c.change >= 0 ? 'is-up' : 'is-down'">
|
||||
环比 {{ c.change >= 0 ? '+' : '' }}{{ c.change }}%
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 车辆耗费构成堆叠柱状图 -->
|
||||
<div class="wel-card">
|
||||
<div class="panel-title">
|
||||
车辆耗费构成
|
||||
<span class="panel-title__unit">万元</span>
|
||||
</div>
|
||||
<wel-chart :option="stackOpt" height="320px" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelChart from './wel-chart.vue';
|
||||
import WelFilters from './wel-filters.vue';
|
||||
import { stackBarOption } from '../chart-options';
|
||||
|
||||
export default {
|
||||
name: 'section-vehicle',
|
||||
components: { WelChart, WelFilters },
|
||||
data() {
|
||||
const months = Array.from({ length: 12 }, (_, i) => `${i + 1}月`);
|
||||
return {
|
||||
months,
|
||||
// Mock:车务统计与耗费数据,对接统计接口后替换
|
||||
miniCards: [
|
||||
{ label: '可用车辆', value: 47, unit: '辆', change: 4.2 },
|
||||
{ label: '维修保养中', value: 7, unit: '辆', change: -8.6 },
|
||||
{ label: '年检临期', value: 9, unit: '辆', change: 12.5 },
|
||||
{ label: '违章未处理', value: 12, unit: '起', change: -2.1 },
|
||||
],
|
||||
fuelData: [68, 52, 41, 36, 32, 38, 24, 46, 38, 28, 12, 14],
|
||||
repairData: [22, 25, 18, 20, 16, 18, 14, 12, 15, 11, 6, 8],
|
||||
otherData: [15, 14, 12, 13, 11, 12, 9, 11, 10, 8, 5, 6],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stackOpt() {
|
||||
return stackBarOption(this.months, [
|
||||
{ name: '油费', data: this.fuelData, color: '#e0b14c' },
|
||||
{ name: '维修保养', data: this.repairData, color: '#58b98a' },
|
||||
{ name: '其他费用', data: this.otherData, color: '#4a90e2' },
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.vehicle-cards__item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div ref="chartEl" class="wel-chart" :style="{ height: height }"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'wel-chart',
|
||||
props: {
|
||||
option: { type: Object, required: true },
|
||||
height: { type: String, default: '260px' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
resizeObserver: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
option: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.render();
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.chart = echarts.init(this.$refs.chartEl);
|
||||
this.render();
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.chart && this.chart.resize();
|
||||
});
|
||||
this.resizeObserver.observe(this.$refs.chartEl);
|
||||
},
|
||||
activated() {
|
||||
/* keep-alive 恢复时重算尺寸 */
|
||||
this.chart && this.chart.resize();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.resizeObserver && this.resizeObserver.disconnect();
|
||||
this.chart && this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
render() {
|
||||
this.chart && this.chart.setOption(this.option, true);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wel-chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="wel-filter">
|
||||
<span class="wel-filter__label">统计时间</span>
|
||||
<el-select v-model="year" size="small">
|
||||
<el-option v-for="y in yearOptions" :key="y" :label="y + '年'" :value="y" />
|
||||
</el-select>
|
||||
<el-select v-model="range" size="small" class="wel-filter__range">
|
||||
<el-option v-for="r in rangeOptions" :key="r.value" :label="r.label" :value="r.value" />
|
||||
</el-select>
|
||||
<template v-if="conditionLabel">
|
||||
<span class="wel-filter__label">{{ conditionLabel }}</span>
|
||||
<el-select v-model="condition" size="small">
|
||||
<el-option label="全部" value="all" />
|
||||
</el-select>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'wel-filters',
|
||||
props: {
|
||||
conditionLabel: { type: String, default: '' },
|
||||
},
|
||||
data() {
|
||||
const now = new Date().getFullYear();
|
||||
return {
|
||||
year: now,
|
||||
yearOptions: [now, now - 1, now - 2, now - 3],
|
||||
range: 'year',
|
||||
rangeOptions: [
|
||||
{ label: '全年', value: 'year' },
|
||||
{ label: '上半年', value: 'h1' },
|
||||
{ label: '下半年', value: 'h2' },
|
||||
{ label: '自定义', value: 'custom' },
|
||||
],
|
||||
condition: 'all',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../dashboard.scss';
|
||||
|
||||
.wel-filter__range {
|
||||
width: 110px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user