This commit is contained in:
Teo
2025-08-22 15:12:26 +08:00
45 changed files with 3667 additions and 724 deletions

View File

@ -0,0 +1,174 @@
<template>
<div class="centerPage">
<div class="topPage">
<!-- 暂无 -->
</div>
<div class="endPage">
<Title title="AI安全巡检" :prefix="true" />
<div class="swiper">
<div class="arrow" :class="{ 'canUse': canLeft }" @click="swiperClick('left')">
<el-icon size="16" color="skyblue">
<ArrowLeft />
</el-icon>
</div>
<div class="swiper_content" ref="swiperContent">
<div class="swiper_item" v-for="(item, index) in swiperList" :key="index">
<img src="@/assets/projectLarge/swiper.png" alt="" class="swiper_img">
<div class="swiper_date">{{ item.date }}</div>
<div class="swiper_tip">{{ item.tip }}</div>
</div>
</div>
<div class="arrow" :class="{ 'canUse': canRight }" @click="swiperClick('right')">
<el-icon size="16">
<ArrowRight />
</el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import Title from './title.vue'
const swiperList = ref([
{ date: '03-18 15:00', tip: '未佩戴安全帽1' },
{ date: '03-18 15:00', tip: '未佩戴安全帽2' },
{ date: '03-18 15:00', tip: '未佩戴安全帽3' },
{ date: '03-18 15:00', tip: '未佩戴安全帽4' },
{ date: '03-18 15:00', tip: '未佩戴安全帽5' },
{ date: '03-18 15:00', tip: '未佩戴安全帽6' },
{ date: '03-18 15:00', tip: '未佩戴安全帽7' },
{ date: '03-18 15:00', tip: '未佩戴安全帽8' },
{ date: '03-18 15:00', tip: '未佩戴安全帽9' },
{ date: '03-18 15:00', tip: '未佩戴安全帽10' },
{ date: '03-18 15:00', tip: '未佩戴安全帽11' },
{ date: '03-18 15:00', tip: '未佩戴安全帽12' },
])
const swiperContent = ref<HTMLDivElement>()
const swiperItemWidth = ref(100)
const canLeft = ref(false)
const canRight = ref(true)
const swiperClick = (direction: 'left' | 'right') => {
if (direction === 'right') {
if (swiperContent.value.scrollLeft >= swiperContent.value.scrollWidth - swiperContent.value.clientWidth) {
canRight.value = false
canLeft.value = true
return
}
swiperContent.value.scrollLeft += swiperItemWidth.value
} else {
if (swiperContent.value.scrollLeft <= 0) {
canLeft.value = false
canRight.value = true
return
}
swiperContent.value.scrollLeft -= swiperItemWidth.value
}
}
onMounted(() => {
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
})
</script>
<style scoped lang="scss">
.centerPage {
display: flex;
flex-direction: column;
width: 50vw;
height: 100%;
.topPage,
.endPage {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(29, 214, 255, 0.1);
box-sizing: border-box;
}
.topPage {
flex: 1;
margin-bottom: 23px;
}
}
.swiper {
width: 100%;
display: flex;
align-items: center;
gap: 20px;
padding: 20px 20px 10px 20px;
.swiper_content {
width: 100%;
display: flex;
gap: 20px;
transition: all 0.3s ease-in-out;
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
}
.swiper_item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 133px;
height: 84px;
.swiper_img {
width: 133px;
height: 84px;
object-fit: cover;
}
.swiper_date {
position: absolute;
top: 4px;
right: 4px;
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
}
.swiper_tip {
position: absolute;
bottom: 0;
width: 100%;
padding: 5px 0;
text-align: center;
font-size: 12px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
background-color: rgba(0, 0, 0, 0.5);
}
}
}
.arrow {
display: grid;
place-items: center;
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid skyblue;
color: skyblue;
&:canUse {
color: #000 !important;
}
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<div class="header">
<div class="header_left">
<div class="header_left_img">
<img src="@/assets/large/secure.png" style="width: 100%; height: 100%" />
</div>
<div style="font-size: 12px; padding-left: 10px">安全生产天数</div>
<div class="header_left_text">
1,235
<span style="font-size: 12px"></span>
</div>
</div>
<div class="title">
<div>XXX智慧工地管理平台</div>
<div>XXX Smart Construction Stic Management Dashboard</div>
</div>
<div class="right">
<div class="top-bar">
<!-- 左侧天气图标 + 日期文字 -->
<div class="left-section">
<img src="@/assets/large/weather.png" alt="天气图标" />
<span>
<span>多云 9°/18°</span>
<span style="padding-left: 20px"> {{ week[date.week] }} ({{ date.ymd }})</span>
</span>
</div>
<!-- 分割线 -->
<div class="divider">
<div class="top-block"></div>
<div class="bottom-block"></div>
</div>
<!-- 右侧管理系统图标 + 文字 -->
<div class="right-section">
<img src="@/assets/large/setting.png" alt="设置图标" />
<span>管理系统</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const date = ref({
ymd: '',
hms: '',
week: 0
});
const setTime = () => {
let date1 = new Date();
let year: any = date1.getFullYear();
let month: any = date1.getMonth() + 1;
let day: any = date1.getDate();
let hours: any = date1.getHours();
if (hours < 10) {
hours = '0' + hours;
}
let minutes: any = date1.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let seconds: any = date1.getSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
date.value.ymd = year + '-' + month + '-' + day;
date.value.hms = hours + ':' + minutes + ':' + seconds;
date.value.week = date1.getDay();
};
// 添加定时器,每秒更新一次时间
const timer = setInterval(setTime, 1000);
// 组件卸载时清除定时器
onUnmounted(() => {
clearInterval(timer);
});
</script>
<style scoped lang="scss">
.header {
width: 100%;
height: 80px;
box-sizing: border-box;
padding: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
color: #fff;
}
.header_left {
display: flex;
align-items: center;
.header_left_img {
width: 48px;
height: 48px;
box-sizing: border-box;
// padding-right: 10px;
}
.header_left_text {
font-weight: 500;
text-shadow: 0px 1.24px 6.21px rgba(25, 179, 250, 1);
}
}
.title {
color: #fff;
font-family: 'AlimamaShuHeiTi', sans-serif;
text-align: center;
}
.title>div:first-child {
/* 第一个子元素的样式 */
font-size: 38px;
letter-spacing: 0.1em;
}
.title>div:last-child {
/* 最后一个子元素的样式 */
font-size: 14px;
}
.right {
width: 100%;
height: 100%;
display: flex;
}
/* 顶部栏容器Flex 水平布局 + 垂直居中 */
.top-bar {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
// background-color: #1e2128;
color: #fff;
padding: 8px 16px;
font-size: 14px;
}
/* 左侧区域(天气 + 日期):自身也用 Flex 水平排列,确保元素在一行 */
.left-section {
display: flex;
align-items: center;
// margin-right: auto; /* 让右侧元素(管理系统)居右 */
}
.left-section img {
width: 32px;
height: 32px;
margin-right: 8px;
/* 图标与文字间距 */
}
/* 分割线(视觉分隔,可根据需求调整样式) */
.divider {
display: grid;
grid-template-rows: 1fr 1fr;
height: 100%;
/* 根据需要调整高度 */
padding: 14px 10px;
}
.divider .top-block {
width: 2px;
height: 7px;
background: #19b5fb;
align-self: start;
}
.divider .bottom-block {
width: 2px;
height: 7px;
background: #19b5fb;
align-self: end;
}
/* 右侧区域(管理系统):图标 + 文字水平排列 */
.right-section {
display: flex;
align-items: center;
font-family: 'AlimamaShuHeiTi', sans-serif;
font-size: 20px;
cursor: pointer;
}
.right-section img {
width: 20px;
height: 20px;
margin-right: 6px;
/* 图标与文字间距 */
}
</style>

View File

@ -0,0 +1,204 @@
<template>
<div class="leftPage">
<div class="topPage">
<Title title="项目公告" />
<div class="content">
<div class="content_item" v-for="item in 6" :key="item">
<div class="round">
<div class="sub_round"></div>
</div>
<div class="ellipsis">2025年6月23日 重庆市两江新区广场前期准备与审批完毕区广场前期准备与审批完毕前期准备与审批完毕区广场前期准备与审批完毕</div>
</div>
</div>
</div>
<div class="endPage">
<Title title="人员情况" />
<div class="map">
<img src="@/assets/projectLarge/map.svg" alt="">
</div>
<div class="attendance_tag">
<div class="tag_item" v-for="(item, index) in tagList" :key="index">
<img src="@/assets/projectLarge/people.svg" alt="">
<div class="tag_title">{{ item.title }}</div>
<div class="tag_info">
{{ item.number }}
<span style="font-size: 14px;">{{ index === 2 ? '%' : '人' }}</span>
</div>
</div>
</div>
<div class="attendance_list">
<div class="attendance_item subfont">
<div class="attendance_item_title"></div>
<div class="attendance_item_title">在岗人数</div>
<div class="attendance_item_title">出勤率</div>
<div class="attendance_item_title">出勤时间</div>
</div>
<div v-for="item in list" :key="item.title" class="attendance_item">
<div class="attendance_item_title">{{ item.title }}</div>
<div class="attendance_item_number">{{ item.number }} <span class="subfont">/{{ item.number }}</span></div>
<div class="attendance_item_rate">{{ item.attendanceRate }} %</div>
<div class="attendance_item_date subfont">{{ item.date }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import Title from './title.vue'
const list = ref([
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
])
const tagList = ref([
{ title: '出勤人数', number: 259 },
{ title: '在岗人数', number: 100 },
{ title: '出勤率', number: 100 },
])
</script>
<style scoped lang="scss">
.leftPage {
display: flex;
flex-direction: column;
width: calc(25vw - 30px);
margin: 0 15px;
height: 100%;
.topPage,
.endPage {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(29, 214, 255, 0.1);
box-sizing: border-box;
}
.endPage {
flex: 1;
margin-top: 23px;
}
}
.content {
max-height: 100px;
margin: 0 15px;
padding: 0 10px;
margin-top: 15px;
box-sizing: border-box;
overflow-y: auto;
&::-webkit-scrollbar-track {
background: rgba(204, 204, 204, 0.1);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: rgba(29, 214, 255, 0.78);
border-radius: 10px;
}
.content_item {
display: flex;
align-items: flex-start;
gap: 10px;
// position: relative;
margin-bottom: 20px;
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
.ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
}
&:last-child {
margin-bottom: 0;
}
.round {
display: grid;
place-items: center;
margin-top: 3px;
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(29, 214, 255, 0.3);
.sub_round {
width: 6px;
height: 6px;
border-radius: 50%;
background: #1DD6FF;
}
}
}
}
.map {
margin-top: 15px;
}
.attendance_tag {
width: 100%;
display: flex;
justify-content: space-between;
padding: 0 30px;
margin-top: 15px;
.tag_item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
border: 1px dashed rgba(29, 214, 255, 0.3);
padding: 10px 25px;
.tag_info {
font-size: 20px;
font-weight: 700;
color: rgba(230, 247, 255, 1);
text-shadow: 0px 1.24px 6.21px rgba(0, 190, 247, 1);
}
.tag_title {
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
}
}
}
.attendance_list {
padding: 0px 30px;
font-size: 14px;
.attendance_item {
display: grid;
grid-template-columns: 3fr 2fr 2fr 3fr;
margin-top: 20px;
}
}
.subfont {
color: rgba(138, 149, 165, 1);
}
</style>

View File

@ -0,0 +1,274 @@
<template>
<div class="leftPage">
<div class="topPage">
<Title title="项目概况" />
<div class="content">
<div class="content_item">项目名称智慧生态工地社区开发项目</div>
<div class="content_item">项目位置贵州省贵阳市乌当区具体地块编号01-123-11</div>
<div class="content_item">占地面积约10000亩</div>
<div class="content_item"> 土地性质城镇住宅用地兼容商业用地容积率2.5</div>
</div>
</div>
<div class="endPage">
<!-- 饼图容器 -->
<Title title="形象进度" />
<div ref="pieChartRef" class="echart" />
<!-- 折线图容器 -->
<div ref="lineChartRef" class="echart" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from "vue"
import Title from './title.vue'
import * as echarts from 'echarts';
// 饼图相关
const pieChartRef = ref<HTMLDivElement | null>(null);
let pieChart: any = null;
// 折线图相关
const lineChartRef = ref<HTMLDivElement | null>(null);
let lineChart: any = null;
// 饼图数据
const pieData = [
{ name: '桩点浇筑', value: 13 },
{ name: '水泥灌注', value: 7 },
{ name: '箱变安装', value: 40 },
{ name: '支架安装', value: 20 },
{ name: '组件安装', value: 20 },
]
// 折线图数据
const barData = {
xAxis: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'],
series: [
{
name: '计划流转面积',
data: [70, 25, 45, 115, 70, 85]
},
{
name: '已流转面积',
data: [105, 30, 150, 65, 80, 200]
}
]
}
// 饼图配置
const pieOption = {
series: {
type: 'pie',
data: pieData,
radius: [50, 80],
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{name|{b}}\n{percent|{c} %}',
minMargin: 10,
edgeDistance: 20,
lineHeight: 15,
rich: {
name: {
fontSize: 12,
color: '#fff'
},
percent: {
fontSize: 12,
color: '#fff'
}
}
},
legend: {
top: 'bottom'
},
}
};
// 柱状图配置
const barOption = {
legend: {
data: ['计划流转面积', '已流转面积'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: barData.xAxis
},
yAxis: {
name: '单位m²',
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
series: [
{
type: 'bar',
data: [], // 空数据仅用于承载markArea
markArea: {
silent: true, // 背景不响应交互
data: (() => {
const groupCount = 3; // 共3组6个月 ÷ 2
const groupWidth = 1.8; // 每组背景宽度覆盖2根柱子
const bgData = [];
for (let i = 0; i < groupCount; i++) {
const startX = i * 2 - 0.9; // 每组起始位置
const endX = startX + groupWidth; // 每组结束位置
bgData.push([
{ xAxis: startX, yAxis: 0 },
{ xAxis: endX, yAxis: 100 }
]);
}
return bgData;
})(),
itemStyle: {
color: 'rgba(255, 255, 255, 0.05)',
borderRadius: 4
}
}
},
{
name: '计划流转面积',
type: 'bar',
data: barData.series[0].data,
barWidth: 15, // 柱形宽度
itemStyle: {
color: 'rgb(29, 253, 253)'
},
},
{
name: '已流转面积',
type: 'bar',
data: barData.series[1].data,
barWidth: 15,
itemStyle: {
color: '#rgb(25, 181, 251)'
},
}
]
};
// 初始化饼图
const initPieChart = () => {
if (!pieChartRef.value) {
console.error('未找到饼图容器元素');
return;
}
pieChart = echarts.init(pieChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
pieChart.setOption(pieOption);
}
// 初始化折线图
const initLineChart = () => {
if (!lineChartRef.value) {
console.error('未找到折线图容器元素');
return;
}
lineChart = echarts.init(lineChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
lineChart.setOption(barOption);
}
// 响应窗口大小变化
const handleResize = () => {
if (pieChart) pieChart.resize();
if (lineChart) lineChart.resize();
};
// 组件挂载时初始化图表
onMounted(() => {
nextTick(() => {
initPieChart();
initLineChart();
window.addEventListener('resize', handleResize);
});
});
// 组件卸载时清理
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (pieChart) {
pieChart.dispose();
pieChart = null;
}
if (lineChart) {
lineChart.dispose();
lineChart = null;
}
});
</script>
<style scoped lang="scss">
.leftPage {
display: flex;
flex-direction: column;
width: calc(25vw - 30px);
margin: 0 15px;
height: 100%;
.topPage,
.endPage {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(29, 214, 255, 0.1);
box-sizing: border-box;
}
.endPage {
flex: 1;
margin-top: 23px;
.echart {
width: 100%;
height: 100%;
}
}
}
.content {
margin: 10px 35px;
.content_item {
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
.ellipse {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subfont {
color: rgba(138, 149, 165, 1);
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div class="title">
<div class="title_icon">
<img src="@/assets/projectLarge/section.svg" alt="">
<img src="@/assets/projectLarge/border.svg" alt="">
</div>
<div v-if="prefix">
<img src="@/assets/projectLarge/robot.svg" alt="" style="width: 20px; height: 20px;margin-right: 5px;">
</div>
<div>{{ title }}</div>
</div>
</template>
<script setup lang="ts">
defineProps({
title: {
type: String,
default: '标题'
},
prefix: {
type: Boolean,
default: false
}
})
</script>
<style scoped lang="scss">
.title {
width: 100%;
display: flex;
align-items: center;
gap: 3px;
font-family: 'AlimamaShuHeiTi', sans-serif;
.title_icon {
position: relative;
&>img:last-child {
position: absolute;
bottom: 4px;
left: 0;
}
}
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="large-screen">
<Header />
<div class="nav">
<leftPage />
<centerPage />
<rightPage />
</div>
</div>
</template>
<script setup lang="ts">
import Header from './components/header.vue';
import leftPage from './components/leftPage.vue';
import centerPage from './components/centerPage.vue';
import rightPage from './components/rightPage.vue';
</script>
<style lang="scss" scoped>
.large-screen {
width: 100vw;
height: 100vh;
background: url('@/assets/large/bg.png') no-repeat;
background-size: 100% 100%;
background-color: rgba(4, 7, 17, 1);
}
.nav {
display: flex;
gap: 15rpx;
width: 100%;
height: calc(100vh - 100px);
box-sizing: border-box;
color: #fff;
}
.nav_left,
.nav_right {
margin: 0 15px 15px 15px;
}
.nav_center {
margin-bottom: 15px;
}
</style>

View File

@ -32,7 +32,7 @@
<el-form-item>
<el-button type="primary" @click="handleExport()" v-hasPermi="['bidding:biddingLimitList:export']">导出excel</el-button>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-button
type="primary"
v-if="versionObj.status == 'draft' || versionObj.status == 'back'"
@ -51,7 +51,7 @@
v-if="versionObj.status != 'draft'"
>查看流程</el-button
>
</el-form-item>
</el-form-item> -->
</el-form>
</el-card>
</transition>

View File

@ -7,9 +7,22 @@
<div class="bg-blue-50 px-6 py-4 rounded-t-xl mb-0">
<h3 class="el-card__header-title text-lg font-semibold text-blue-800">投标项目信息填写</h3>
<span>{{ currentProject.name }}</span>
<div style="margin-top: 10px">
<el-button @click="isDisabled = false" type="primary" class="px-8 py-2.5 transition-all duration-300 font-medium" v-if="isDisabled">
点击编辑
</el-button>
</div>
</div>
</template>
<el-form ref="listOfWinningBidsFormRef" :model="form" :rules="rules" label-width="150px" class="p-6 pt-4" style="background-color: #ffffff">
<el-form
:disabled="isDisabled"
ref="listOfWinningBidsFormRef"
:model="form"
:rules="rules"
label-width="150px"
class="p-6 pt-4"
style="background-color: #ffffff"
>
<el-row :gutter="32">
<el-col :span="12">
<el-form-item label="中标价(美元)" prop="winningBidOriginal" class="rounded-lg border border-gray-100 p-1 mb-5">
@ -100,14 +113,14 @@
<el-input v-model="form.projectNumbering" placeholder="请输入项目编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-col :span="12">
<el-form-item label="项目状态" prop="projectStatus" class="rounded-lg border border-gray-100 p-1 mb-5">
<el-input v-model="form.projectStatus" placeholder="请输入项目状态(如:进行中/已完成)" />
</el-form-item>
</el-col>
</el-col> -->
</el-row>
<!-- 操作按钮区域 -->
<el-row class="mt-8">
<el-row v-if="!isDisabled" class="mt-8">
<el-col :span="24" class="text-center">
<el-button
:loading="buttonLoading"
@ -129,7 +142,7 @@
<script setup name="ListOfWinningBidsForm" lang="ts">
import { ref, reactive, toRefs, watch, onMounted, onUnmounted, getCurrentInstance, ComponentInternalInstance, computed } from 'vue';
import { addListOfWinningBids, updateListOfWinningBids, getListOfWinningBids } from '@/api/bidding/listOfWinningBids';
import { addListOfWinningBids, updateListOfWinningBids, listListOfWinningBids, getListOfWinningBids } from '@/api/bidding/listOfWinningBids';
import { ListOfWinningBidsVO, ListOfWinningBidsForm } from '@/api/bidding/listOfWinningBids/types';
import { useUserStoreHook } from '@/store/modules/user';
import { ElFormInstance, ElMessage } from 'element-plus';
@ -145,6 +158,7 @@ const currentProject = computed(() => userStore.selectedProject);
const listOfWinningBidsFormRef = ref<ElFormInstance>();
// 加载状态
const buttonLoading = ref(false);
const isDisabled = ref(false);
// 表单初始数据
const initFormData: ListOfWinningBidsForm = {
id: undefined,
@ -189,7 +203,6 @@ const data = reactive({
// 解构响应式数据
const { form, rules } = toRefs(data);
/**
* 计算人民币中标价
* 显式触发的计算函数,确保执行时机可靠
@ -216,16 +229,21 @@ const calculateWinningBid = () => {
const initData = async () => {
try {
if (currentProject.value?.id) {
const res = await getListOfWinningBids(currentProject.value.id);
if (res.data && res.data.id) {
Object.assign(form.value, res.data);
// 初始化时手动触发一次计算
setTimeout(calculateWinningBid, 0);
const res = await listListOfWinningBids({ projectId: currentProject.value.id });
if (res.code == 200) {
console.log(res.data);
resetForm();
if (!res.data) {
isDisabled.value = false;
return;
} else {
Object.assign(form.value, res.data);
}
isDisabled.value = true;
}
}
} catch (error) {
console.error('初始化数据失败:', error);
ElMessage.error('初始化数据失败');
// ElMessage.error('初始化数据失败');
}
};
@ -239,16 +257,10 @@ const submitForm = () => {
try {
// 提交前确保计算正确
calculateWinningBid();
form.value.projectId = currentProject.value?.id;
if (form.value.id) {
await updateListOfWinningBids(form.value);
} else {
await addListOfWinningBids(form.value);
}
await addListOfWinningBids(form.value);
isDisabled.value = true;
ElMessage.success('提交成功');
resetForm();
} catch (error) {
ElMessage.error('提交失败,请重试');
console.error('提交表单失败:', error);
@ -258,7 +270,6 @@ const submitForm = () => {
}
});
};
/**
* 重置表单
*/
@ -266,7 +277,6 @@ const resetForm = () => {
form.value = { ...initFormData, projectId: currentProject.value?.id };
listOfWinningBidsFormRef.value?.resetFields();
};
/**
* 监听项目ID变化 - 重新初始化数据
*/

View File

@ -64,6 +64,12 @@
<el-col :span="24">
<el-card shadow="never">
<el-form :model="treeForm" :inline="true">
<el-form-item label="版本" prop="versions">
<el-select v-model="treeForm.versions" placeholder="选择版本" @change="changeSheet">
<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="treeForm.sheet" placeholder="选择表名" @change="changeSheet">
<el-option v-for="item in sheets" :key="item" :label="item" :value="item" />
@ -146,7 +152,16 @@ import { useUserStoreHook } from '@/store/modules/user';
import { getDicts } from '@/api/system/dict/data';
import { Plus } from '@element-plus/icons-vue';
import { FormInstance } from 'element-plus';
import { treeList, sheetList, segmentedIndicatorPlanning, getPlanningList, updatePlanning, delPlanning, getDetailsList } from '@/api/contract/index';
import {
treeList,
sheetList,
segmentedIndicatorPlanning,
getPlanningList,
updatePlanning,
delPlanning,
getDetailsList,
obtainAllVersionNumbers
} from '@/api/contract/index';
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
@ -198,7 +213,8 @@ const getList = async () => {
projectId: currentProject.value?.id,
...queryParams.value,
...queryForm.value,
dictName: activeTab.value
dictName: activeTab.value,
type: '1'
};
const res = await getPlanningList(params);
if (res.code == 200) {
@ -234,7 +250,7 @@ const resetQuery = () => {
const openDialog = () => {
dialogVisible.value = true;
getSheetName();
getVersionNums();
};
const closeDialog = () => {
dialogVisible.value = false;
@ -246,19 +262,54 @@ const closeDialog = () => {
};
const treeData = ref<any[]>([]);
const treeForm = ref({
sheet: ''
sheet: '',
versions: ''
});
const sheets = ref<any[]>([]);
const treeTableRef = ref();
const isExpandAll = ref(false);
const treeLoading = ref(false);
const selectionData = ref<any>([]);
const options = ref<any[]>([]);
//获取版本号
const getVersionNums = async () => {
try {
const params = {
projectId: currentProject.value?.id,
workOrderType: '1',
pageSize: 1000,
pageNum: 1
};
const res = await obtainAllVersionNumbers(params);
if (res.code == 200) {
options.value = res.data;
if (res.data.length > 0) {
treeForm.value.versions = res.data[0].versions;
getSheetName();
} else {
treeForm.value.versions = '';
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
}
} catch (error) {
console.log(error);
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
};
//获取表名
const getSheetName = async () => {
try {
const params = {
projectId: currentProject.value?.id
// versions: queryForm.value.versions
projectId: currentProject.value?.id,
versions: treeForm.value.versions
};
const res = await sheetList(params);
if (res.code == 200) {
@ -333,7 +384,9 @@ const getTreeList = async () => {
treeLoading.value = true;
const params = {
projectId: currentProject.value?.id,
sheet: treeForm.value.sheet
sheet: treeForm.value.sheet,
versions: treeForm.value.versions,
type: '1'
};
const res = await treeList(params);
if (res.code == 200) {
@ -380,7 +433,10 @@ const submitForm = async (formEl: FormInstance | undefined) => {
projectId: currentProject.value?.id,
...form.value,
dictName: activeTab.value,
limitListBos
limitListBos,
type: '1',
versions: treeForm.value.versions,
sheet: treeForm.value.sheet
};
const res = await segmentedIndicatorPlanning(params);
if (res.code == 200) {
@ -409,7 +465,7 @@ const handleSave = (row: any) => {
});
return;
}
updatePlanning(row).then((res) => {
updatePlanning({ ...row, type: '1' }).then((res) => {
if (res.code == 200) {
ElMessage({
message: '修改成功',
@ -420,10 +476,6 @@ const handleSave = (row: any) => {
});
} catch (error) {
console.log(error);
ElMessage({
message: '修改失败',
type: 'error'
});
}
};
//删除
@ -441,10 +493,6 @@ const delHandle = (row: any) => {
});
} catch (error) {
console.log(error);
ElMessage({
message: '删除失败',
type: 'error'
});
}
};
const detailDialog = ref();
@ -457,13 +505,22 @@ const handleDetail = (row: any) => {
getDetails(row);
};
const getDetails = (row: any) => {
getDetailsList({ id: row.id }).then((res) => {
getDetailsList({ id: row.id, type: '1' }).then((res) => {
if (res.code == 200) {
detailData.value = res.data;
}
});
};
//监听项目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-option v-for="item in options" :key="item.versions" :label="item.versions" :value="item.versions" />
</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
};
@ -108,7 +109,8 @@ const getVersionNums = async () => {
if (res.code == 200) {
options.value = res.data;
if (res.data.length > 0) {
queryForm.value.versions = res.data[0];
queryForm.value.versions = res.data[0].versions;
getSheetName();
} else {
queryForm.value.versions = '';
@ -140,8 +142,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) {
@ -172,7 +174,8 @@ const getTableData = async () => {
const params = {
projectId: currentProject.value?.id,
versions: queryForm.value.versions,
sheet: queryForm.value.sheet
sheet: queryForm.value.sheet,
type: '1'
};
const res = await listBillofquantitiesLimitList(params);
if (res.code == 200) {
@ -201,7 +204,7 @@ const handleSave = (row: any) => {
return;
}
loading.value = true;
updatePrice(row).then((res) => {
updatePrice({ ...row, type: '1' }).then((res) => {
if (res.code == 200) {
ElMessage({
message: '修改成功',
@ -230,7 +233,7 @@ const importExcel = (options: any): any => {
let formData = new FormData();
formData.append('file', options.file);
loading.value = true;
importExcelFile({ projectId: currentProject.value?.id }, formData)
importExcelFile({ projectId: currentProject.value?.id, versions: queryForm.value.versions, sheet: queryForm.value.sheet, type: '1' }, formData)
.then((res) => {
const { code } = res;
if (code == 200) {
@ -259,7 +262,9 @@ const handleExport = () => {
'/tender/billofquantitiesLimitList/export',
{
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet
sheet: queryForm.value.sheet,
versions: queryForm.value.versions,
type: '1'
},
`限价一览表${queryForm.value.sheet}.xlsx`
);
@ -268,8 +273,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

View File

@ -15,13 +15,6 @@
<el-form-item label="企业名称" prop="supplierName">
<el-input v-model="queryParams.supplierName" placeholder="请输入企业名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="审核状态" prop="state">
<el-select v-model="queryParams.state" placeholder="请选择状态" clearable>
<el-option label="待审核" value="0"></el-option>
<el-option label="已通过" value="1"></el-option>
<el-option label="未通过" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
@ -38,32 +31,16 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['supplierInput:supplierInput:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['supplierInput:supplierInput:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['supplierInput:supplierInput:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<!-- <el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['supplierInput:supplierInput:export']">导出</el-button>
</el-col>
</el-col> -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="supplierInputList" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" width="100%" align="center" />
<el-table-column type="index" label="序号" align="center" width="60" />
<el-table-column label="企业登记注册类型" align="center" prop="supplierType" width="140" />
<el-table-column label="企业名称" align="center" prop="supplierName" width="180" />
<el-table-column label="法定代表人" align="center" prop="supplierPerson" width="120" />
@ -72,11 +49,9 @@
<el-table-column label="负责人电话" align="center" prop="personPhone" width="120" />
<el-table-column label="纳税规模" align="center" prop="taxScale" width="120" />
<el-table-column label="资质等级" align="center" prop="supplierLivel" width="120" />
<el-table-column label="审核状态" align="center" prop="state" width="120">
<el-table-column label="流程状态" align="center">
<template #default="scope">
<el-tag :type="scope.row.state === '1' ? 'success' : scope.row.state === '2' ? 'danger' : 'warning'">
{{ scope.row.state === '1' ? '已通过' : scope.row.state === '2' ? '未通过' : '待审核' }}
</el-tag>
<dict-tag :options="wf_business_status" :value="scope.row.state" />
</template>
</el-table-column>
<el-table-column label="入库资料" align="center" prop="inputFile" width="120">
@ -86,34 +61,30 @@
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
<el-table-column label="操作" align="center" fixed="right" width="240">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['supplierInput:supplierInput:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['supplierInput:supplierInput:remove']"
></el-button>
</el-tooltip>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['supplierInput:supplierInput:edit']"
>修改</el-button
>
<el-button link type="primary" icon="edit" @click="handleAudit(scope.row)" v-if="scope.row.state == 'draft' || scope.row.state == 'back'"
>审核</el-button
>
<el-button link type="primary" icon="View" v-if="scope.row.state != '2' && scope.row.state != 'draft'" @click="handleAuditView(scope.row)"
>查看流程</el-button
>
<!-- <el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['supplierInput:supplierInput:remove']"
></el-button> -->
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 新增/修改对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="950px" append-to-body>
<el-form ref="supplierInputFormRef" :model="form" :rules="rules" label-width="200px">
@ -249,7 +220,6 @@
</el-form-item>
</el-col>
</el-row>
<!-- 第十行安全生产许可证有效期仅劳务类型显示 -->
<el-row :gutter="20" class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12">
@ -261,7 +231,6 @@
<!-- 空列占位 -->
</el-col>
</el-row>
<!-- 第十一行注册人员数量仅劳务类型显示 -->
<el-row :gutter="20" class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12">
@ -282,7 +251,6 @@
<!-- 空列占位 -->
</el-col>
</el-row>
<!-- 第十二行职称人员数量仅劳务类型显示 -->
<el-row :gutter="20" class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12">
@ -303,7 +271,6 @@
<!-- 空列占位 -->
</el-col>
</el-row>
<!-- 第十四行入库资料上传 -->
<el-row class="mb-4">
<el-col :span="24">
@ -342,7 +309,6 @@
</el-col>
</el-row>
</el-form>
<!-- 对话框底部按钮 -->
<template #footer>
<div class="dialog-footer">
@ -362,10 +328,8 @@ import { SupplierInputVO, SupplierInputQuery, SupplierInputForm, PageData, Dialo
import Pagination from '@/components/Pagination/index.vue';
import RightToolbar from '@/components/RightToolbar/index.vue';
import FileUpload from '@/components/FileUpload/index.vue';
// 实例代理
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
// 组件引用
const fileUploadRef = ref<InstanceType<typeof FileUpload> | null>(null);
const queryFormRef = ref<ElFormInstance>();
@ -414,7 +378,7 @@ const initFormData: any = {
personnelNumber: undefined, // 后端返回的拼接字符串如“5,6,7,8”
fileId: undefined,
inputFile: undefined,
state: '0', // 新增默认待审核
// state: '0', // 新增默认待审核
// 新增:用于表单输入的单独字段
build1: undefined, // 一建建造师
build2: undefined, // 二建建造师
@ -577,6 +541,28 @@ const splitBackEndStrToForm = (resData: any) => {
form.value.personnelNumber4 = personnelArr[3] || undefined; // 其他职称人员
}
};
/** 审核过程按钮操作 */
const handleAudit = async (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/approval/supplierInput/indexEdit`,
query: {
id: row.id,
type: 'update'
}
});
};
/** 查看按钮操作 */
const handleAuditView = async (row) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/approval/supplierInput/indexEdit`,
query: {
id: row.id,
type: 'view'
}
});
};
/** 文件选择变更 */
const change = () => {
fileUrl.value = '';

View File

@ -0,0 +1,521 @@
<template>
<div class="p-4 bg-gray-50 min-h-screen">
<div class="max-w-4xl mx-auto">
<!-- 顶部按钮区域 -->
<el-card class="mb-4 rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.state"
:pageType="routeParams.type"
/>
</el-card>
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">专项方案信息</h3>
</div>
<div class="p-6">
<el-form ref="leaveFormRef" v-loading="loading" disabled :model="form" :rules="rules" label-width="150px" class="space-y-4">
<div class="">
<el-row>
<el-col :span="12">
<el-form-item label="企业登记注册类型" prop="supplierType">
<el-select v-model="form.supplierType" placeholder="请选择供应商类型" @change="handleTypeChange">
<el-option label="劳务" value="劳务"></el-option>
<el-option label="技术服务" value="技术服务"></el-option>
<el-option label="物资设备" value="物资设备"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业名称" prop="supplierName">
<el-input v-model="form.supplierName" placeholder="请输入企业名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业法定代表人" prop="supplierPerson">
<el-input v-model="form.supplierPerson" placeholder="请输入法定代表人" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="统一社会信用代码" prop="supplierCode">
<el-input v-model="form.supplierCode" placeholder="请输入统一社会信用代码" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业注册地址" prop="supplierAddres">
<el-input v-model="form.supplierAddres" placeholder="请输入注册地址" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人姓名" prop="personName">
<el-input v-model="form.personName" placeholder="请输入负责人姓名" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人联系电话" prop="personPhone">
<el-input v-model="form.personPhone" placeholder="请输入联系电话" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="纳税规模" prop="taxScale">
<el-select v-model="form.taxScale" placeholder="请选择纳税规模">
<el-option label="一般纳税人" value="一般纳税人"></el-option>
<el-option label="小规模纳税人" value="小规模纳税人"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开户行户名" prop="bankPersonName">
<el-input v-model="form.bankPersonName" placeholder="请输入开户行户名" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开户银行" prop="bankName">
<el-input v-model="form.bankName" placeholder="请输入开户银行" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="开户行账号" prop="bankAccount">
<el-input v-model="form.bankAccount" placeholder="请输入开户行账号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="经营范围" prop="scope">
<el-input v-model="form.scope" placeholder="请输入经营范围" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="企业资质等级" prop="supplierLivel">
<el-input v-model="form.supplierLivel" placeholder="请输入资质等级" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发证日期" prop="issueDate">
<el-date-picker v-model="form.issueDate" type="date" placeholder="请选择发证日期" value-format="YYYY-MM-DD" />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="证书有效期" prop="certificateValidity">
<el-date-picker v-model="form.certificateValidity" type="date" placeholder="请选择证书有效期" value-format="YYYY-MM-DD" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="近三年营业额" prop="pastThreeYears">
<el-input v-model="form.pastThreeYears" placeholder="请输入近三年营业额" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="生产许可证编号" prop="safeCode">
<el-input v-model="form.safeCode" placeholder="请输入许可证编号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生产许可证发证日期" prop="safeCodeData">
<el-date-picker v-model="form.safeCodeData" type="date" placeholder="请选择发证日期" />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="生产许可证发证日期" prop="safeCertificateValidity">
<el-date-picker v-model="form.safeCertificateValidity" type="date" placeholder="请选择发证日期" />
</el-form-item>
</el-col>
</el-row>
<el-row class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12">
<el-form-item label="一建建造师" prop="build1">
<el-input v-model="form.build1" placeholder="请输入一建建造师数量" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="二建建造师" prop="build2">
<el-input v-model="form.build2" placeholder="请输入二建建造师数量" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="其他(分别写)" prop="build4">
<el-input v-model="form.build3" placeholder="请输入其他人员数量" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="注册造价工程师" prop="build3">
<el-input v-model="form.build4" placeholder="请输入注册造价工程师数量" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24" class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12">
<el-form-item label="高级工程师人数" prop="personnelNumber1">
<el-input v-model="form.personnelNumber1" placeholder="请输高级工程师数量" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="工程师数量" prop="personnelNumber2">
<el-input v-model="form.personnelNumber2" placeholder="请输入工程师数量" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="助理工程师数量" prop="personnelNumber3">
<el-input v-model="form.personnelNumber3" placeholder="请输入助理工程师数量" clearable />
</el-form-item> </el-col
><el-col :span="12">
<el-form-item label="其他人员数量" prop="personnelNumber4">
<el-input v-model="form.personnelNumber4" placeholder="请输入其他人员数量" clearable />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<approvalRecord ref="approvalRecordRef"></approvalRecord>
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 流程选择对话框 -->
<el-dialog
draggable
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:before-close="handleClose"
width="500"
class="rounded-lg shadow-lg"
>
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
<div class="dialog-footer p-4 border-t border-gray-100 flex justify-end space-x-3">
<el-button @click="handleClose" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors"
>取消</el-button
>
<el-button type="primary" @click="submitFlow()" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>确认</el-button
>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="Leave" lang="ts">
import { LeaveForm, LeaveQuery } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { useUserStoreHook } from '@/store/modules/user';
import { getSupplierInput } from '@/api/supplierInput/supplierInput';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const buttonLoading = ref(false);
const loading = ref(true);
//路由参数
const routeParams = ref<Record<string, any>>({});
const flowCode = ref<string>('');
const status = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
const leaveFormRef = ref<ElFormInstance>();
const dialog = reactive({
visible: false,
title: '',
isEdit: false
});
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
const flowCodeOptions = [
{
value: currentProject.value?.id + '_supplierInput',
label: '供应商入库审核'
}
];
const initFormData = {
id: undefined,
supplierType: undefined,
supplierName: undefined,
supplierPerson: undefined,
supplierCode: undefined,
supplierAddres: undefined,
personName: undefined,
personPhone: undefined,
bankPersonName: undefined,
bankName: undefined,
bankAccount: undefined,
taxScale: undefined,
scope: undefined,
supplierLivel: undefined,
issueDate: undefined,
certificateValidity: undefined,
pastThreeYears: undefined,
safeCode: undefined,
safeCodeData: undefined,
safeCertificateValidity: undefined,
registeredNumber: undefined, // 后端返回的拼接字符串如“1,2,3,4”
personnelNumber: undefined, // 后端返回的拼接字符串如“5,6,7,8”
fileId: undefined,
inputFile: undefined,
state: '0', // 新增默认待审核
// 新增:用于表单输入的单独字段
build1: undefined, // 一建建造师
build2: undefined, // 二建建造师
build3: undefined, // 注册造价工程师
build4: undefined, // 其他注册人员
personnelNumber1: undefined, // 高级工程师
personnelNumber2: undefined, // 工程师
personnelNumber3: undefined, // 助理工程师
personnelNumber4: undefined // 其他职称人员
};
const data = reactive<PageData<LeaveForm, LeaveQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
fileName: undefined,
fileType: undefined,
fileSuffix: undefined,
fileStatus: undefined,
originalName: undefined,
newest: undefined,
params: {}
},
rules: {
versionNumber: [{ required: true, message: '版本号不能为空', trigger: 'blur' }],
fileName: [{ required: true, message: '文件名称不能为空', trigger: 'blur' }],
fileType: [{ required: true, message: '文件类型不能为空', trigger: 'change' }],
fileUrl: [
{
validator: (rule, value, callback) => {
// 新增时必须上传文件
if (!form.value.fileUrl) {
callback(new Error('请上传图纸文件'));
} else {
callback();
}
},
trigger: 'change'
}
]
}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
leaveFormRef.value?.resetFields();
};
/** 获取详情 */
const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
const res = await getSupplierInput(routeParams.value.id);
Object.assign(form.value, res.data);
loading.value = false;
buttonLoading.value = false;
});
};
/** 提交按钮 */
const submitForm = (status1: string) => {
status.value = status1;
buttonLoading.value = true;
dialog.visible = false;
submit(status.value, form.value);
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
//流程变量
taskVariables.value = {
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
//审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.id);
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
//审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
// 图纸上传成功之后 开始提交
const submit = async (status, data) => {
form.value = data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.state === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
console.log(data);
await handleStartWorkFlow(data);
}
};
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
getInfo();
}
});
});
</script>
<style scoped lang="scss">
/* 全局样式 */
:root {
--primary: #409eff;
--primary-light: #66b1ff;
--primary-dark: #3a8ee6;
--success: #67c23a;
--warning: #e6a23c;
--danger: #f56c6c;
--info: #909399;
}
/* 表单样式优化 */
.el-form-item {
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input__inner,
.el-select .el-input__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
.el-textarea__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
}
/* 按钮样式优化 */
.el-button {
border-radius: 4px;
transition: all 0.2s;
&.is-primary {
background-color: var(--primary);
border-color: var(--primary);
&:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
&:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
}
&.is-text {
color: var(--primary);
&:hover {
color: var(--primary-light);
background-color: rgba(64, 158, 255, 0.05);
}
}
}
/* 卡片样式优化 */
.el-card {
transition: all 0.3s ease;
&:hover {
/* transform: translateY(-2px); */
}
}
/* 对话框样式优化 */
.el-dialog {
.el-dialog__header {
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
padding: 15px 20px;
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-dialog__footer {
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
}
</style>