This commit is contained in:
2025-08-21 21:41:56 +08:00
24 changed files with 1951 additions and 616 deletions

View File

@ -5,7 +5,7 @@ VITE_APP_TITLE = 煤科建管平台
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = 'http://192.168.110.209:8899'
VITE_APP_BASE_API = 'http://192.168.110.180:8899'
# 无人机接口地址

View File

@ -34,6 +34,7 @@
"diagram-js": "12.3.0",
"didi": "9.0.2",
"echarts": "5.5.0",
"echarts-gl": "^2.0.9",
"element-plus": "2.8.8",
"esbuild": "^0.25.0",
"ezuikit-js": "^8.1.10",

93
src/api/tender/index.ts Normal file
View File

@ -0,0 +1,93 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//获取版本
export const obtainAllVersionNumbers = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList/obtainAllVersionNumbers',
method: 'get',
params: query
});
};
//获取sheet
export const sheetList = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList/sheetList',
method: 'get',
params: query
});
};
//获取表格数据
export const getTableList = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList/list',
method: 'get',
params: query
});
};
//修改单价数据
export const updatePrice = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList',
method: 'put',
data: query
});
};
//导入
export const importExcelFile = (query: any, data: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList/importExcelFile',
method: 'post',
params: query,
data
});
};
//招标计划列表
export const tenderPlanList = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/biddingPlan/list',
method: 'get',
params: query
});
};
//新增招标计划
export const addTenderPlan = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/biddingPlan',
method: 'post',
data: query
});
};
//删除招标计划
export const delTenderPlan = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/biddingPlan/' + query.ids,
method: 'delete'
});
};
//修改招标计划
export const updateTenderPlan = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/biddingPlan',
method: 'put',
data: query
});
};
//弹窗数据
export const treeList = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/tenderPlanLimitList/getTree',
method: 'get',
params: query
});
};
//获取招标计划详情
export const getTenderPlanDetail = (query: any): AxiosPromise<any> => {
return request({
url: '/tender/biddingPlan/getMore',
method: 'get',
params: query
});
};

BIN
src/assets/large/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

BIN
src/assets/large/top1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/large/top2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/large/top3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/assets/large/top4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
src/assets/large/up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

View File

@ -0,0 +1,169 @@
<template>
<div ref="echartBox" class="echarts"></div>
</template>
<script setup lang="ts">
import china from '@/assets/china.json';
import cq from '@/assets/cq.json';
import { ref, onMounted, watchEffect, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts/core';
import {
BarChart, // 柱状图
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
LineChart, // 折线图
LineSeriesOption,
PieChart, // 饼图
PieSeriesOption,
PictorialBarChart,
MapChart,
ScatterChart,
EffectScatterChart,
LinesChart
} from 'echarts/charts';
import {
// 组件类型的定义后缀都为 ComponentOption
// 标题
TitleComponent,
TitleComponentOption,
// 提示框
TooltipComponent,
TooltipComponentOption,
// 直角坐标系
GridComponent,
GridComponentOption,
// 图例
LegendComponent,
LegendComponentOption,
// 数据集组件
DatasetComponent,
DatasetComponentOption,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
DataZoomComponent,
DataZoomComponentOption,
// 极坐标
PolarComponent,
PolarComponentOption,
MarkLineComponentOption,
MarkLineComponent,
// MarkPoint
MarkPointComponent,
MarkPointComponentOption,
// VisualMap
VisualMapComponent,
VisualMapComponentOption,
// GeoComponent
GeoComponent,
GeoComponentOption
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import 'echarts-gl';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = echarts.ComposeOption<
| BarSeriesOption
| LineSeriesOption
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| LegendComponentOption
| DataZoomComponentOption
| PolarComponentOption
| MarkLineComponentOption
| MarkPointComponentOption
| VisualMapComponentOption
| GeoComponentOption
>;
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
DataZoomComponent,
PolarComponent,
MarkLineComponent,
MarkPointComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
BarChart,
LineChart,
PieChart,
VisualMapComponent,
PictorialBarChart,
GeoComponent,
MapChart,
ScatterChart,
EffectScatterChart,
LinesChart
]);
const props = defineProps({
option: {
type: Object,
default: () => {
return null;
}
}
});
const emit = defineEmits(['echartsEvent']);
const echartBox = ref(null);
let chart!: echarts.ECharts;
const setChart = (option: ECOption): void => {
if (!props.option || !echartBox.value) {
return;
}
chart.resize();
chart.setOption(option);
};
const resetChart = (): void => {
const option = chart.getOption();
if (!option || !echartBox.value) {
return;
}
chart.resize();
};
onMounted(() => {
(echarts as any).registerMap('china', { geoJSON: china });
(echarts as any).registerMap('cq', { geoJSON: cq });
chart = echarts.init(echartBox.value as any);
emit('echartsEvent', chart);
setChart(props.option);
// 界面拉伸后重设
window.addEventListener('resize', () => {
resetChart();
});
});
watchEffect(() => {
if (chart) {
chart.clear();
}
setChart(props.option);
});
onBeforeUnmount(() => {
if (chart) {
chart.dispose();
}
});
</script>
<style scoped lang="scss">
.echarts {
width: 100%;
height: 100%;
pointer-events: all;
}
</style>

View File

@ -463,7 +463,16 @@ const getDetails = (row: any) => {
}
});
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value?.id,
(nid, oid) => {
getTabsList();
}
);
onUnmounted(() => {
listeningProject();
});
onMounted(() => {
getTabsList();
});

View File

@ -3,11 +3,11 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<el-card shadow="always">
<el-form :model="queryForm" :inline="true">
<!-- <el-form-item label="版本号" prop="versions">
<el-form-item label="版本号" prop="versions">
<el-select v-model="queryForm.versions" placeholder="选择版本号" @change="changeVersions">
<el-option v-for="item in options" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item> -->
</el-form-item>
<el-form-item label="表名" prop="sheet">
<el-select v-model="queryForm.sheet" placeholder="选择表名" @change="changeSheet">
<el-option v-for="item in sheets" :key="item" :label="item" :value="item" />
@ -100,6 +100,7 @@ const getVersionNums = async () => {
try {
const params = {
projectId: currentProject.value?.id,
workOrderType: '1',
pageSize: 1000,
pageNum: 1
};
@ -140,8 +141,8 @@ const changeSheet = () => {
const getSheetName = async () => {
try {
const params = {
projectId: currentProject.value?.id
// versions: queryForm.value.versions
projectId: currentProject.value?.id,
versions: queryForm.value.versions
};
const res = await sheetList(params);
if (res.code == 200) {
@ -268,8 +269,8 @@ onUnmounted(() => {
listeningProject();
});
onMounted(() => {
// getVersionNums();
getSheetName();
getVersionNums();
// getSheetName();
});
</script>

View File

@ -0,0 +1,121 @@
<template>
<div class="progress_component">
<div class="title">
<span class="progress_title">{{ title }}</span>
<span :class="percentageClass" class="roboto">{{ percentageChange }}</span>
</div>
<div class="roboto" v-if="isShowPrice">
<span>{{ value }}</span>
<span>{{ unit }}</span>
</div>
<div class="my_el_progress">
<el-progress :percentage="progressPercentage" :color="progressColor" :show-text="false" />
</div>
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
// 定义组件属性
const props = defineProps({
// 标题文本
title: {
type: String,
required: true,
default: '指标名称'
},
// 数值
value: {
type: String,
required: true,
default: '0.00'
},
// 单位
unit: {
type: String,
default: '万元'
},
// 百分比变化值(如:-327.55%
percentageChange: {
type: String,
required: true,
default: '0.00%'
},
// 进度条百分比
progressPercentage: {
type: Number,
required: true,
default: 0
},
// 进度条颜色,默认红色
progressColor: {
type: String,
default: 'rgba(255, 77, 79, 1)'
},
// 是否显示价格
isShowPrice: {
type: Boolean,
default: true
}
});
// 计算百分比变化的样式类(红色或绿色)
const percentageClass = computed(() => {
// 检查变化值是否为正数
const isPositive = props.percentageChange.startsWith('+') ||
(!props.percentageChange.startsWith('-') && props.percentageChange !== '0.00%');
return isPositive ? 'green' : 'red';
});
</script>
<style lang="scss" scoped>
.progress_component {
width: 100%;
height: 100%;
margin-bottom: 10px;
:deep(.el-progress-bar__outer) {
background-color: transparent;
}
:deep(.el-progress-bar__inner),
:deep(.el-progress-bar__outer) {
border-radius: unset;
}
.my_el_progress {
margin-top: 15px;
padding: 10px;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.title {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 12px;
}
.progress_title {
color: rgba(143, 171, 191, 1);
font-size: 12px;
font-family: SourceHanSansCN-Regular;
font-weight: 400;
}
.roboto {
font-family: Roboto-Regular;
font-weight: 400;
}
.red {
color: rgba(255, 77, 79, 1);
}
.green {
color: rgba(0, 227, 150, 1);
}
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<div class="stat-card" :style="customStyles">
<!-- 标题区域 -->
<div class="stat-card__title">{{ title }}</div>
<!-- 数值区域 -->
<div class="stat-card__value-container">
<span class="stat-card__value">{{ formattedValue }}</span>
<span class="stat-card__unit">{{ unit }}</span>
</div>
<!-- 底部信息区域 -->
<div class="stat-card__footer">
<div class="stat-card__trend">
<img
class="stat-card__trend-icon"
:src="'/src/assets/large/' + trendIcon+'.png'"
:alt="trendDirection === 'up' ? '上升' : '下降'"
>
<span class="stat-card__trend-text">{{ trendText }}</span>
</div>
<img
class="stat-card__badge"
:src="'/src/assets/large/'+badgeIcon+'.png'"
alt="徽章图标"
>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
// 定义组件属性
const props = defineProps({
// 卡片标题
title: {
type: String,
default: '收入合同'
},
// 数值
value: {
type: Number,
default: 205805.17
},
// 单位
unit: {
type: String,
default: '万元'
},
// 增长率
growthRate: {
type: Number,
default: 3.2
},
// 增长对比周期
period: {
type: String,
default: '较上月'
},
// 趋势方向 (up/down)
trendDirection: {
type: String,
default: 'up',
validator: (value) => ['up', 'down'].includes(value)
},
// 趋势图标
trendIcon: {
type: String,
default: 'up'
},
// 徽章图标
badgeIcon: {
type: String,
default: 'top1'
},
// 卡片自定义样式
customStyles: {
type: Object,
default: () => ({})
}
});
// 格式化数值为带千分位的字符串
const formattedValue = computed(() => {
return props.value.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
});
// 生成趋势文本
const trendText = computed(() => {
return `${props.growthRate}% ${props.period}`;
});
</script>
<style lang="scss" scoped>
.stat-card {
width: 225px;
height: 147px;
background-color: rgba(29, 214, 255, 0.1);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
box-sizing: border-box;
border: 1px solid rgba(29, 214, 255, 0.1);
border-radius: 4px; // 增加轻微圆角,提升视觉效果
&__title {
font-size: 14px;
color: #8FABBF;
line-height: 20px;
}
&__value-container {
display: flex;
align-items: baseline;
}
&__value {
font-size: 24px;
color: #fff;
line-height: 30px;
margin-right: 5px;
font-weight: bold;
}
&__unit {
color: #8FABBF;
font-size: 14px;
}
&__footer {
display: flex;
justify-content: space-between;
align-items: center;
}
&__trend {
display: flex;
align-items: center;
}
&__trend-icon {
width: 12px;
height: 12px;
margin-right: 4px;
}
&__trend-text {
font-size: 14px;
color: #8FABBF;
}
&__badge {
width: 40px;
height: 40px;
}
}
// 为下降趋势添加不同颜色
:deep(.stat-card__trend-text) {
color: v-bind('trendDirection === "up" ? "#8FABBF" : "#ff4d4f"');
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div class="large_title">
<div class="title">
<img class="title_icon" src="@/assets/large/title_icon.png" alt=""></img>
<div class="title_text">{{ title }}</div>
</div>
<img class="title_bottom" src="@/assets/large/title_bottom.png" alt="">
</div>
</template>
<script setup>
import { defineProps } from 'vue';
// 定义组件属性,使组件可配置
const props = defineProps({
// 标题文本
title: {
type: String,
required: true,
default: '标题'
},
})
</script>
<style lang="scss">
.large_title {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
.title {
display: flex;
margin-bottom: 15px;
}
.title_icon {
width: 10px;
height: 24px;
margin-right: 15px;
}
.title_text {
font-size: 24px;
font-family: Rang_men_zheng_title;
font-weight: 400;
color: rgba(226, 235, 241, 1);
}
.title_bottom {
width: 100%;
height: 5px;
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="bottom_box">
<div class="bottom_box_title">收入合同</div>
<div>
<span class="bottom_box_number">205,805.17</span>
<span>万元</span>
</div>
<div class="bottom_box_bottom">
<el-progress :percentage="50" color="rgba(255, 147, 42, 1)" />
</div>
<div class="bottom_box_text">
成本率
</div>
</div>
</template>
<script setup>
</script>
<style lang="scss">
.bottom_box {
width: 225px;
height: 147px;
height: 100%;
padding: 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-around;
.bottom_box_title,
.bottom_box_text {
color: rgba(143, 171, 191, 1);
font-size: 14px;
line-height: 20px;
}
.bottom_box_number {
font-size: 24px;
color: #fff;
line-height: 30px;
}
}
</style>

View File

@ -1,8 +1,55 @@
<template>
<div class="centerPage">
<div>
<div style="height: 147px;width: 100%;display: flex;justify-content: space-between;">
<!-- <div class="top_box">
<div class="top_box_title">收入合同</div>
<div>
<span class="top_box_number">205,805.17</span>
<span>万元</span>
</div>
<div class="top_box_bottom">
<div>
<img class="up_img" src="@/assets/large/up.png" alt=""></img>
<span class="top_box_title"> 3.2% 较上月</span>
</div>
<img class="top_img" src="@/assets/large/top1.png" alt=""></img>
</div>
</div> -->
<RevenueContractCard title="收入合同" :value="156234.89" :growthRate="-1.5" trendDirection="up" trendIcon="up"
badgeIcon="top1" period="较上月" />
<RevenueContractCard title="支出合同" :value="156234.89" :growthRate="-1.5" trendDirection="up" trendIcon="up"
badgeIcon="top2" period="较上月" />
<RevenueContractCard title="合同利润" :value="156234.89" :growthRate="-1.5" trendDirection="up" trendIcon="down"
badgeIcon="top3" period="较上月" />
<RevenueContractCard title="工程变更" :value="156234.89" :growthRate="-1.5" trendDirection="up" trendIcon="up"
badgeIcon="top4" period="较上月" />
</div>
</div>
<div class="centerPage_map">
<div ref="mapRef" class="map-container" style="width: 100%; height: 100%" />
</div>
<div>
<div style="height: 147px;width: 100%;display: flex;justify-content: space-between;">
<!-- <div class="bottom_box">
<div class="bottom_box_title">收入合同</div>
<div>
<span class="bottom_box_number">205,805.17</span>
<span>万元</span>
</div>
<div class="bottom_box_bottom">
<el-progress :percentage="50" color="rgba(255, 147, 42, 1)" />
</div>
<div class="bottom_box_text">
成本率
</div>
</div> -->
<bottomboxconpoent> </bottomboxconpoent>
<bottomboxconpoent> </bottomboxconpoent>
<bottomboxconpoent> </bottomboxconpoent>
<bottomboxconpoent> </bottomboxconpoent>
</div>
</div>
</div>
</template>
@ -10,6 +57,8 @@
// import { getPowerStationOverview } from '@/api/large';
import * as echarts from 'echarts';
import china from '@/assets/china.json';
import RevenueContractCard from './RevenueContractCard.vue';
import bottomboxconpoent from './bottomboxconpoent.vue';
const data = ref<any>({});
// 地图容器引用
@ -168,9 +217,12 @@ onUnmounted(() => {
padding: 0 10px 10px 10px;
box-sizing: border-box;
.centerPage_map {
width: 100%;
height: 100%;
height: 60%;
}
}
</style>

View File

@ -1,13 +1,73 @@
<template>
<div class="leftPage">左边</div>
<div class="leftPage">
<!-- -->
<div class="kpi_box">
<TitleComponent :title="'支付KPI'" style="margin-bottom: 20px;"/>
<ProgressComponent
title="应收账款"
value="123,456.78"
percentageChange="+25.30%"
progressPercentage="75"
progressColor="rgba(255, 77, 79, 1)"
/>
<ProgressComponent
title="应付账款"
value="123,456.78"
percentageChange="+25.30%"
progressPercentage="25"
progressColor="rgba(29, 214, 255, 1)"
/>
<ProgressComponent
title="本月付款"
value="123,456.78"
percentageChange="+25.30%"
progressPercentage="45"
progressColor="rgba(0, 227, 150, 1)"
/>
<ProgressComponent
title="本月收款"
value="123,456.78"
percentageChange="+25.30%"
progressPercentage="10"
progressColor="rgba(255, 147, 42, 1)"
/>
</div>
<div class="contract_box">
<EchartBox :option="barOption" />
</div>
</div>
</template>
<script setup lang="ts"></script>
<script setup>
import { ref, reactive, onMounted, computed, toRefs, getCurrentInstance, nextTick } from 'vue';
// import echarts from 'echarts';
import TitleComponent from './TitleComponent.vue';
import ProgressComponent from './ProgressComponent.vue';
import EchartBox from '@/components/EchartBox/index.vue';
import { getBarOptions2 } from './optionList';
const barOption = ref();
const getCapitalData = (data) => {
barOption.value = getBarOptions2();
};
onMounted(() => {
getCapitalData();
});
</script>
<style scoped lang="scss">
<style lang="scss">
.leftPage {
width: 100%;
height: 100%;
background: #0c1e35;
.kpi_box{
margin-bottom: 10px;
}
.contract_box{
height: 35vh;
}
.kpi_box,.contract_box {
padding: 10px;
box-sizing: border-box;
border: 1px solid rgba(29, 214, 255, 0.3);
}
}
</style>

View File

@ -1,4 +1,5 @@
import * as echarts from 'echarts/core';
import { text } from 'stream/consumers';
// import { PictorialBarChart } from 'echarts/charts'
// 客流量图
export const getOption = (xData: any, yData: any) => {
@ -249,9 +250,10 @@ export const getOption2 = (data: any) => {
};
return option;
};
//食堂周报图
//z折线
export const getLineOption = (lineData: any) => {
const maxData = Math.ceil(Math.max(...lineData.line1));
const maxData = Math.max(...lineData.line1.flat());
const option = {
backgroundColor: '',
tooltip: {
@ -263,42 +265,49 @@ export const getLineOption = (lineData: any) => {
},
borderColor: '#7ec7ff'
},
// legend: {
// align: 'left',
// right: '5%',
// top: '1%',
// type: 'plain',
// textStyle: {
// color: '#fff',
// fontSize: 12
// },
// // icon:'rect',
// itemGap: 15,
// itemWidth: 18,
// data: [
// {
// name: '上周销售量'
// },
// {
// name: '本周销售量'
// }
// ]
// },
legend: {
align: 'left',
right: '5%',
top: '1%',
type: 'plain',
textStyle: {
color: '#fff',
fontSize: 12
},
// icon:'rect',
itemGap: 15,
itemWidth: 18,
data: [
{
name: '收款金额'
},
{
name: '付款金额'
},
{
name: '净现金流'
}
]
},
grid: {
top: '12%',
left: '1%',
right: '3%',
bottom: '12%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: lineData.xLabel,
boundaryGap: false,
axisLine: {
show: false
},
axisTick: {
show: true
show: false
},
splitLine: {
show: false
},
axisLabel: {
textStyle: {
@ -311,28 +320,28 @@ export const getLineOption = (lineData: any) => {
type: 'value',
max: maxData,
splitLine: {
show: true,
show: false,
lineStyle: {
type: 'solid',
color: 'rgba(73, 169, 191, 0.2)'
}
}
},
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
// dataZoom: [
// {
// // show: true,
// start: 0,
// end: 30,
// bottom: 2, // 下滑块距离x轴底部的距离
// height: 23
// },
// {
// type: 'inside'
// }
// ],
series: [
{
name: '逆变器功率',
name: '收款金额',
type: 'line',
symbol: 'circle', // 默认是空心圆(中间是白色的),改成实心圆
showAllSymbol: false,
@ -373,7 +382,95 @@ export const getLineOption = (lineData: any) => {
shadowColor: 'rgba(25,163,223, 0.5)', //阴影颜色
shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。
},
data: lineData.line1
data: lineData.line1[0]
},
{
name: '付款金额',
type: 'line',
symbol: 'none', // 默认是空心圆(中间是白色的),改成实心圆
showAllSymbol: false,
symbolSize: 0,
smooth: true,
lineStyle: {
width: 1,
color: 'rgba(255, 224, 179, 1)', // 线条颜色
borderColor: 'rgba(0,0,0,.4)'
},
itemStyle: {
color: 'rgba(255, 224, 179, 1)',
borderWidth: 2,
show: true
},
tooltip: {
show: true
},
areaStyle: {
//线性渐变前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是true则该四个值是绝对像素位置。
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(255, 224, 179, 0.4)'
},
{
offset: 1,
color: 'rgba(255, 224, 179, 0)'
}
],
false
),
shadowColor: 'rgba(255, 224, 179, 0.6)', //阴影颜色
shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。
},
data: lineData.line1[1]
},
{
name: '净现金流',
type: 'line',
symbol: 'none', // 默认是空心圆(中间是白色的),改成实心圆
showAllSymbol: false,
symbolSize: 0,
smooth: true,
lineStyle: {
width: 1,
color: 'rgba(39, 255, 252, 1)', // 线条颜色
borderColor: 'rgba(0,0,0,.4)'
},
itemStyle: {
color: 'rgba(39, 255, 252, 1)',
borderWidth: 2,
show: false
},
tooltip: {
show: true
},
areaStyle: {
//线性渐变前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是true则该四个值是绝对像素位置。
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(39, 255, 252, 0.4)'
},
{
offset: 1,
color: 'rgba(39, 255, 252, 0)'
}
],
false
),
shadowColor: 'rgba(39, 255, 252, 0.5)', //阴影颜色
shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。
},
data: lineData.line1[2]
}
]
};
@ -481,10 +578,10 @@ export const getDishesOption = (data?: any) => {
// 菜品库存图
export const getInventoryOption = () => {
const res = {
data: [2800, 300, 3900, 3000, 2450, 2670, 3320],
name: ['麻辣牛肉', '水煮肉片', '酸菜鱼', '辣子鸡丁', '烧白', '冬瓜排骨汤', '清炒油麦菜'],
ratio: [4000, 4000, 4000, 4000, 4000, 4000, 4000]
},
data: [2800, 300, 3900, 3000, 2450, 2670, 3320],
name: ['麻辣牛肉', '水煮肉片', '酸菜鱼', '辣子鸡丁', '烧白', '冬瓜排骨汤', '清炒油麦菜'],
ratio: [4000, 4000, 4000, 4000, 4000, 4000, 4000]
},
dataIndex = 1;
const option = {
xAxis: {
@ -600,16 +697,16 @@ export const getBarOptions = (data: any) => {
const option = {
backgroundColor: '',
grid: {
left: '7%',
left: '8%',
top: '4%',
bottom: '25%',
bottom: '8%',
right: '2%'
},
tooltip: {
show: true,
backgroundColor: '',
trigger: 'axis',
formatter: '{b0}{c0}元',
formatter: '{b0}{c0}元',
textStyle: {
color: '#fff'
}
@ -634,7 +731,7 @@ export const getBarOptions = (data: any) => {
// show: true,
},
splitLine: {
show: true,
show: false,
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)',
type: 'dashed'
@ -646,9 +743,7 @@ export const getBarOptions = (data: any) => {
{
axisLabel: {
formatter: function (value) {
if (value >= 1000) {
value = (value / 1000).toFixed(1) + 'k'; // 大于等于1000的数字显示为1k、2.5k等
}
value = value + '万';
return value;
},
color: 'rgba(255, 255, 255, 0.8)'
@ -662,7 +757,7 @@ export const getBarOptions = (data: any) => {
}
},
splitLine: {
show: true,
show: false,
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)',
type: 'dashed'
@ -670,24 +765,25 @@ export const getBarOptions = (data: any) => {
}
}
],
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
// dataZoom: [
// {
// // show: true,
// start: 0,
// end: 30,
// bottom: 2, // 下滑块距离x轴底部的距离
// height: 23
// },
// {
// type: 'inside'
// }
// ],
series: [
{
type: 'bar',
data: data.value,
stack: '合并',
barWidth: '15',
data: data.value[0],
// stack: '合并',
barWidth: '5',
barGap: '100%',
itemStyle: {
color: new echarts.graphic.LinearGradient(
0,
@ -697,22 +793,60 @@ export const getBarOptions = (data: any) => {
[
{
offset: 0,
color: 'rgba(0, 111, 255, 0)' // 0% 处的颜色
color: ' rgba(29, 214, 255, 1)' // 0% 处的颜色
},
{
offset: 0.7,
color: 'rgba(0, 111, 255, 0.5)' // 0% 处的颜色
color: ' rgba(29, 214, 255, 0.5)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(0, 111, 255, 1)' // 100% 处的颜色
color: ' rgba(29, 214, 255, 0.1)' // 100% 处的颜色
}
],
false
)
},
label: {
show: true,
show: false,
formatter: '{c}',
position: 'top',
color: '#fff',
fontSize: 10
// padding: 5
}
},
{
type: 'bar',
data: data.value[1],
// stack: '合并',
barWidth: '5',
barGap: '100%',
itemStyle: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: ' rgba(255, 77, 79, 1)' // 0% 处的颜色
},
{
offset: 0.7,
color: ' rgba(255, 77, 79, 0.5)' // 0% 处的颜色
},
{
offset: 1,
color: ' rgba(255, 77, 79, 0.1)' // 100% 处的颜色
}
],
false
)
},
label: {
show: false,
formatter: '{c}',
position: 'top',
color: '#fff',
@ -720,16 +854,66 @@ export const getBarOptions = (data: any) => {
// padding: 5
}
}
// {
// type: 'bar',
// stack: '合并',
// data: topData,
// barWidth: '15',
// itemStyle: {
// color: 'rgba(252, 217, 18, 1)'
// }
// }
]
};
return option;
};
// 收支合同分析
export const getBarOptions2 = (data: any) => {
const option = {
color:['#FF932A', '#678FE6', '#1DD6FF', '#00E396'],
title: {
text: '数量(个)',
subtext: '16',
bottom: 'center',
left: 'center',
textStyle: {
color: '#9DADB7',
fontSize: 16
},
subtextStyle:{
color: '#707070',
fontSize: 32,
fontWeight: 'bold'
}
},
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['50%', '60%'],
avoidLabelOverlap: false,
padAngle: 5,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: false,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 3, name: '100万一下' },
{ value: 4, name: '100-500万' },
{ value: 5, name: '500-1000万' },
{ value: 4, name: '1000万以上' },
]
}
]
};
return option;
}

View File

@ -1,13 +1,183 @@
<template>
<div class="rightPage">右边</div>
<div class="rightPage">
<div class="funds">
<TitleComponent :title="'资金KPI'" />
<div class="funds_echarts">
<EchartBox :option="lineOption" />
</div>
</div>
<div class="cashFlow">
<TitleComponent :title="'现金流概述'" />
<div class="inflowData">
<div class="inflow">
<div class="title">现金流入</div>
<div class="number">1000000</div>
<div class="unit">万元</div>
</div>
<div class="inflow">
<div class="title">现金流出</div>
<div class="number">1000000</div>
<div class="unit">万元</div>
</div>
<div class="inflow">
<div class="title">净现金流</div>
<div class="number">1000000</div>
<div class="unit">万元</div>
</div>
</div>
<div class="inflow_echarts">
<EchartBox :option="barOption" />
</div>
<div class="progress">
<!-- <div class="progress_item">
<div class="title">项目进度</div>
<div class="number">100%</div>
</div> -->
<ProgressComponent title="现金比率" value="123,456.78" percentageChange="3479.61%" :progressPercentage="100"
progressColor="rgba(29, 214, 255, 1)" :isShowPrice="false" class="progress_text" />
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import TitleComponent from './TitleComponent.vue';
import EchartBox from '@/components/EchartBox/index.vue';
import { getLineOption, getBarOptions } from './optionList';
import ProgressComponent from './ProgressComponent.vue';
const lineOption = ref();
const barOption = ref();
const getCapitalData = (data?: any) => {
// const xData = data.map((item) => item.time);
// const yData = data.map((item) => item.content);
const lineData = {
xLabel: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
line1: [
[100, 200, 150, 300, 250, 350, 400, 350, 450, 500, 400, 550],
[220, 250, 230, 280, 270, 300, 350, 320, 380, 400, 450, 500],
[300, 350, 320, 380, 400, 450, 500, 480, 520, 550, 600, 650]
]
// line2: ['20', '50', '12', '65', '30', '60']
};
lineOption.value = getLineOption(lineData);
};
const getTurnoverList = (data?: any) => {
// const xData = data.map((item) => item.time);
// const yData = data.map((item) => {
// // 先将content转换为数字再调用toFixed
// const num = Number(item.content);
// return isNaN(num) ? 0 : Number(num.toFixed(2));
// });
const barData = {
name: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
value: [
[2, 5, 15, 30, 25, 35, 40, 35, 45, 50, 40, 55],
[4, 3, 6, 11, 15, 22, 30, 14, 48, 22, 25, 60]
]
};
barOption.value = getBarOptions(barData);
};
onMounted(() => {
getCapitalData();
getTurnoverList();
});
//资金KPI
</script>
<style scoped lang="scss">
.rightPage {
width: 100%;
height: 100%;
background: #0c1e35;
box-sizing: border-box;
// padding: 5px;
}
.funds {
width: 100%;
// height: 40%;
border: 1px solid rgba(29, 214, 255, 0.3);
box-sizing: border-box;
padding: 10px 5px;
}
.funds_echarts {
width: 100%;
height: 25vh;
padding: 10px 0 0 0;
}
.cashFlow {
width: 100%;
// height: 50%;
border: 1px solid rgba(29, 214, 255, 0.3);
box-sizing: border-box;
padding: 10px 5px;
margin-top: 10px;
}
.inflowData {
width: 100%;
height: 12vh;
// background: #fff;
padding-top: 20px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
.inflow {
width: 100%;
height: 100%;
// background: #f5f5f5;
padding: 10px;
box-sizing: border-box;
background: rgba(29, 214, 255, 0.1);
border-left: 1px solid rgba(29, 214, 255, 1);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.title {
font-size: 14px;
// font-weight: 500;
color: #fff;
padding-bottom: 10px;
}
.number {
font-size: 24px;
// font-weight: 500;
color: #fff;
padding-bottom: 10px;
}
.unit {
font-size: 12px;
// font-weight: 500;
color: rgba(255, 255, 255, 0.5);
}
}
}
.inflow_echarts {
width: 100%;
height: 25vh;
margin-top: 20px;
}
.progress {
width: 100%;
margin-top: 10px;
}
:deep(.progress_text) {
.roboto {
color: #fff;
}
}
</style>

View File

@ -0,0 +1,279 @@
<template>
<div class="p-2">
<el-tabs type="border-card" @tab-change="handleTabChange" v-model="activeTab">
<el-tab-pane v-for="(item, index) in tabList" :key="index" :label="item.label" :name="item.value">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<el-card shadow="always">
<el-form :model="queryForm" :inline="true">
<el-form-item label="版本号" prop="versions">
<el-select v-model="queryForm.versions" placeholder="选择版本号" @change="changeVersions">
<el-option v-for="item in options" :key="item.versions" :label="item.versions" :value="item.versions" />
</el-select>
</el-form-item>
<el-form-item label="表名" prop="sheet">
<el-select v-model="queryForm.sheet" placeholder="选择表名" @change="changeSheet">
<el-option v-for="item in sheets" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="toggleExpandAll(true)">一键展开</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="toggleExpandAll(false)">一键收起</el-button>
</el-form-item>
<el-form-item>
<el-upload
ref="uploadRef"
class="upload-demo"
:http-request="importExcel"
:show-file-list="false"
v-hasPermi="['tender:billofquantitiesLimitList:importExcelFile']"
>
<template #trigger>
<el-button type="primary">导入excel</el-button>
</template>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleExport()" v-hasPermi="['tender:billofquantitiesLimitList:export']">导出excel</el-button>
</el-form-item>
</el-form>
</el-card>
</transition>
<el-card shadow="never" class="mb8">
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="remark" label="单价" align="center">
<template #default="scope">
<el-input-number
:model-value="scope.row.unitPrice"
@change="(val) => (scope.row.unitPrice = val)"
:precision="2"
:step="0.1"
:controls="false"
v-if="scope.row.quantity && scope.row.quantity != 0"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="总价" align="center">
<template #default="scope">
{{ scope.row.price }}
</template>
</el-table-column>
<el-table-column prop="price" label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
size="small"
@click="handleSave(scope.row)"
v-if="scope.row.quantity && scope.row.quantity != 0"
v-hasPermi="['tender:billofquantitiesLimitList:edit']"
>确定</el-button
>
</template>
</el-table-column>
</el-table>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { useUserStoreHook } from '@/store/modules/user';
import { obtainAllVersionNumbers, sheetList, getTableList, updatePrice, importExcelFile } from '@/api/tender/index';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const tabList = [
{
label: '招采工程量清单',
value: '2'
},
{
label: '物资设备清单',
value: '3'
}
];
const queryForm = ref({
versions: '',
sheet: ''
});
const activeTab = ref('2');
const sheets = ref([]);
const options = ref([]);
const tableData = ref([]);
const tableRef = ref();
const isExpandAll = ref(false);
const loading = ref(false);
// 切换tab
const handleTabChange = (tab: string) => {
activeTab.value = tab;
getVersionNums();
};
//切换版本
const changeVersions = () => {
getSheetName();
};
//切换表格
const changeSheet = (val: any) => {
getTableData();
};
//展开树
const toggleExpandAll = (isExpand: boolean) => {
tableData.value.forEach((row) => {
tableRef.value.toggleRowExpansion(row, isExpand);
});
isExpandAll.value = isExpand;
};
//获取版本号
const getVersionNums = async () => {
try {
const params = {
projectId: currentProject.value?.id,
workOrderType: activeTab.value,
pageSize: 1000,
pageNum: 1
};
const res = await obtainAllVersionNumbers(params);
if (res.code == 200) {
options.value = res.data;
if (res.data.length > 0) {
queryForm.value.versions = res.data[0].versions;
getSheetName();
} else {
queryForm.value.versions = '';
getSheetName();
}
}
} catch (error) {
console.log(error);
}
};
//获取表名
const getSheetName = async () => {
try {
const params = {
projectId: currentProject.value?.id,
versions: queryForm.value.versions
};
const res = await sheetList(params);
if (res.code == 200) {
sheets.value = res.data;
if (res.data.length > 0) {
queryForm.value.sheet = res.data[0];
} else {
queryForm.value.sheet = '';
}
getTableData();
}
} catch (error) {
console.log(error);
}
};
//获取表格数据
const getTableData = async () => {
try {
const params = {
projectId: currentProject.value?.id,
versions: queryForm.value.versions,
sheet: queryForm.value.sheet,
type: activeTab.value
};
const res = await getTableList(params);
if (res.code == 200) {
tableData.value = res.data;
}
} catch (error) {
console.log(error);
}
};
//导入
const importExcel = (options: any): any => {
let formData = new FormData();
formData.append('file', options.file);
loading.value = true;
importExcelFile(
{ projectId: currentProject.value?.id, sheet: queryForm.value.sheet, versions: queryForm.value.versions, type: activeTab.value },
formData
)
.then((res) => {
const { code } = res;
if (code == 200) {
proxy.$modal.msgSuccess(res.msg || '导入成功');
getTableData();
} else {
proxy.$modal.msgError(res.msg || '导入失败');
}
})
.catch((err) => {
proxy.$modal.msgError(err.msg || '导入失败');
})
.finally(() => {
loading.value = false;
});
};
//导出
const handleExport = () => {
proxy?.download(
'/tender/tenderPlanLimitList/export',
{
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions,
type: activeTab.value
},
`招标一览表${queryForm.value.sheet}.xlsx`
);
};
//确认修改
const handleSave = (row: any) => {
try {
if (!row.unitPrice) {
ElMessage({
message: '请输入单价',
type: 'warning'
});
return;
}
loading.value = true;
updatePrice(row).then((res) => {
if (res.code == 200) {
ElMessage({
message: '修改成功',
type: 'success'
});
getTableData();
}
});
} catch (error) {
console.log(error);
loading.value = false;
} finally {
loading.value = false;
}
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value?.id,
(nid, oid) => {
getVersionNums();
}
);
onUnmounted(() => {
listeningProject();
});
onMounted(() => {
getVersionNums();
});
</script>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff