修改
This commit is contained in:
		
							
								
								
									
										76
									
								
								src/api/largeScreen/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/api/largeScreen/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| import request from '@/utils/request'; | ||||
| import { AxiosPromise } from 'axios'; | ||||
| import { MasterVO, MasterForm, MasterQuery } from '@/api/patch/types'; | ||||
| /** | ||||
|  * 合同金额 | ||||
|  * | ||||
|  */ | ||||
| export const totalAmount = () => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/totalAmount', | ||||
|     method: 'get' | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 查询项目位置列表 | ||||
|  * | ||||
|  */ export const projectGis = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/project/gis', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 应收实收 | ||||
|  * | ||||
|  */ export const incomePay = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/income/pay', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 收入合同分析 | ||||
|  * | ||||
|  */ export const incomeAnalyze = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/income/analyze', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
| /** | ||||
|  * 应付实付 | ||||
|  * | ||||
|  */ export const expensesPay = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/expenses/pay', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
| /** | ||||
|  * 支出合同分析 | ||||
|  * | ||||
|  */ export const expensesAnalyze = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/expenses/analyze', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
| /** | ||||
|  * 成本 | ||||
|  * | ||||
|  */ export const cost = (clientid) => { | ||||
|   return request({ | ||||
|     url: '/money/big/screen/cost', | ||||
|     method: 'get', | ||||
|     params: clientid | ||||
|   }); | ||||
| }; | ||||
| @ -1,17 +1,17 @@ | ||||
| <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 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> | ||||
| @ -19,103 +19,102 @@ 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 | ||||
|     } | ||||
|   // 标题文本 | ||||
|   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'; | ||||
|   // 检查变化值是否为正数 | ||||
|   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%; | ||||
|   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; | ||||
|   } | ||||
|  | ||||
|     :deep(.el-progress-bar__outer) { | ||||
|         background-color: transparent; | ||||
|     } | ||||
|   .progress_title { | ||||
|     color: rgba(143, 171, 191, 1); | ||||
|     font-size: 12px; | ||||
|     font-family: SourceHanSansCN-Regular; | ||||
|     font-weight: 400; | ||||
|   } | ||||
|  | ||||
|     :deep(.el-progress-bar__inner), | ||||
|     :deep(.el-progress-bar__outer) { | ||||
|         border-radius: unset; | ||||
|     } | ||||
|   .roboto { | ||||
|     font-family: Roboto-Regular; | ||||
|     font-weight: 400; | ||||
|   } | ||||
|  | ||||
|     .my_el_progress { | ||||
|         margin-top: 15px; | ||||
|         padding: 10px; | ||||
|         box-sizing: border-box; | ||||
|         border: 1px solid rgba(255, 255, 255, 0.2); | ||||
|     } | ||||
|   .red { | ||||
|     color: rgba(255, 77, 79, 1); | ||||
|   } | ||||
|  | ||||
|     .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); | ||||
|     } | ||||
|   .green { | ||||
|     color: rgba(0, 227, 150, 1); | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| </style> | ||||
|  | ||||
| @ -2,29 +2,12 @@ | ||||
|   <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__value">{{ props.value }}</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> | ||||
|  | ||||
| @ -107,18 +90,18 @@ const trendText = computed(() => { | ||||
|   box-sizing: border-box; | ||||
|   border: 1px solid rgba(29, 214, 255, 0.1); | ||||
|   border-radius: 4px; // 增加轻微圆角,提升视觉效果 | ||||
|    | ||||
|  | ||||
|   &__title { | ||||
|     font-size: 14px; | ||||
|     color: #8FABBF; | ||||
|     color: #8fabbf; | ||||
|     line-height: 20px; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   &__value-container { | ||||
|     display: flex; | ||||
|     align-items: baseline; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   &__value { | ||||
|     font-size: 24px; | ||||
|     color: #fff; | ||||
| @ -126,34 +109,34 @@ const trendText = computed(() => { | ||||
|     margin-right: 5px; | ||||
|     font-weight: bold; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   &__unit { | ||||
|     color: #8FABBF; | ||||
|     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; | ||||
|     color: #8fabbf; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   &__badge { | ||||
|     width: 40px; | ||||
|     height: 40px; | ||||
|  | ||||
| @ -1,45 +1,158 @@ | ||||
| <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 class="bottom_box"> | ||||
|     <div class="bottom_box_title">材料成本</div> | ||||
|     <div class="bottom_box_bottom"> | ||||
|       <el-progress :percentage="materialProgressPercent" color="rgba(255, 147, 42, 1)" :format="(percent) => `${percent.toFixed(1)}%`" /> | ||||
|     </div> | ||||
|     <div class="bottom_box_text">成本使用率</div> | ||||
|     <div class="cost-item"> | ||||
|       <span class="cost-label">材料成本:</span> | ||||
|       <span class="cost-value">{{ materialCost.toFixed(2) }} 万元</span> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <!-- 分包成本展示框 --> | ||||
|   <div class="bottom_box"> | ||||
|     <div class="bottom_box_title">分包成本</div> | ||||
|     <div class="bottom_box_bottom"> | ||||
|       <el-progress :percentage="subcontractProgressPercent" color="rgba(0, 227, 150, 1)" :format="(percent) => `${percent.toFixed(1)}%`" /> | ||||
|     </div> | ||||
|     <div class="bottom_box_text">成本使用率</div> | ||||
|     <div class="cost-item"> | ||||
|       <span class="cost-label">分包成本:</span> | ||||
|       <span class="cost-value">{{ subcontractCost.toFixed(2) }} 万元</span> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <!-- 劳务成本展示框 --> | ||||
|   <div class="bottom_box"> | ||||
|     <div class="bottom_box_title">劳务成本</div> | ||||
|     <div class="bottom_box_bottom"> | ||||
|       <el-progress :percentage="laborProgressPercent" color="rgba(255, 77, 79, 1)" :format="(percent) => `${percent.toFixed(1)}%`" /> | ||||
|     </div> | ||||
|     <div class="bottom_box_text">成本使用率</div> | ||||
|     <div class="cost-item"> | ||||
|       <span class="cost-label">劳务成本:</span> | ||||
|       <span class="cost-value">{{ laborCost.toFixed(2) }} 万元</span> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <!-- 管理成本展示框 --> | ||||
|   <div class="bottom_box"> | ||||
|     <div class="bottom_box_title">管理成本</div> | ||||
|     <div class="bottom_box_bottom"> | ||||
|       <el-progress :percentage="manageProgressPercent" color="rgba(29, 214, 255, 1)" :format="(percent) => `${percent.toFixed(1)}%`" /> | ||||
|     </div> | ||||
|     <div class="bottom_box_text">成本使用率</div> | ||||
|     <div class="cost-item"> | ||||
|       <span class="cost-label">管理成本:</span> | ||||
|       <span class="cost-value">{{ manageCost.toFixed(2) }} 万元</span> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted, computed } from 'vue'; | ||||
| import { ElProgress } from 'element-plus'; | ||||
| import { cost } from '@/api/largeScreen/index'; | ||||
|  | ||||
| // 定义各类成本的响应式数据 | ||||
| const materialCost = ref(0); // 材料成本 | ||||
| const subcontractCost = ref(0); // 分包成本 | ||||
| const laborCost = ref(0); // 劳务成本 | ||||
| const manageCost = ref(0); // 管理成本 | ||||
|  | ||||
| // 进度条相关配置(每个成本单独设置预算,这里假设每个成本预算相同,可根据实际调整) | ||||
| const budgetCost = 10000; // 单个成本项的预算总成本(用于计算使用率) | ||||
|  | ||||
| // 计算每个成本项的进度百分比 | ||||
| const materialProgressPercent = computed(() => { | ||||
|   const percent = (materialCost.value / budgetCost) * 100; | ||||
|   return Math.min(percent, 100); | ||||
| }); | ||||
| const subcontractProgressPercent = computed(() => { | ||||
|   const percent = (subcontractCost.value / budgetCost) * 100; | ||||
|   return Math.min(percent, 100); | ||||
| }); | ||||
| const laborProgressPercent = computed(() => { | ||||
|   const percent = (laborCost.value / budgetCost) * 100; | ||||
|   return Math.min(percent, 100); | ||||
| }); | ||||
| const manageProgressPercent = computed(() => { | ||||
|   const percent = (manageCost.value / budgetCost) * 100; | ||||
|   return Math.min(percent, 100); | ||||
| }); | ||||
|  | ||||
| // 请求成本数据 | ||||
| const getCostData = async () => { | ||||
|   try { | ||||
|     const res = await cost(); | ||||
|     if (res.code === 200) { | ||||
|       // 为各类成本赋值(转换为数字类型) | ||||
|       materialCost.value = Number(res.data.materialCost); | ||||
|       subcontractCost.value = Number(res.data.subcontractCost); | ||||
|       laborCost.value = Number(res.data.laborCost); | ||||
|       manageCost.value = Number(res.data.manageCost); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('获取成本数据失败:', error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 页面挂载时请求数据 | ||||
| onMounted(() => { | ||||
|   getCostData(); | ||||
| }); | ||||
| </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; | ||||
|   width: 225px; | ||||
|   height: auto; | ||||
|   padding: 10px; | ||||
|   box-sizing: border-box; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 8px; | ||||
|   background: rgba(12, 24, 46, 0.8); | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
|     .bottom_box_title, | ||||
|     .bottom_box_text { | ||||
|       color: rgba(143, 171, 191, 1); | ||||
|       font-size: 14px; | ||||
|       line-height: 20px; | ||||
|     } | ||||
| .bottom_box_title { | ||||
|   color: rgba(143, 171, 191, 1); | ||||
|   font-size: 14px; | ||||
|   line-height: 20px; | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
|     .bottom_box_number { | ||||
|       font-size: 24px; | ||||
|       color: #fff; | ||||
|       line-height: 30px; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| .bottom_box_text { | ||||
|   color: rgba(143, 171, 191, 1); | ||||
|   font-size: 14px; | ||||
|   line-height: 20px; | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| .bottom_box_bottom { | ||||
|   width: 100%; | ||||
|   height: 8px; | ||||
|   margin: 5px 0; | ||||
| } | ||||
|  | ||||
| .cost-item { | ||||
|   color: #fff; | ||||
|   font-size: 14px; | ||||
|   line-height: 26px; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
| } | ||||
|  | ||||
| .cost-label { | ||||
|   color: rgba(143, 171, 191, 1); | ||||
| } | ||||
|  | ||||
| .cost-value { | ||||
|   font-weight: 500; | ||||
|   color: #fff; | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -1,146 +1,160 @@ | ||||
| <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 style="height: 147px; width: 100%; display: flex; justify-content: space-between"> | ||||
|         <!-- 收入合同:保持万元显示 --> | ||||
|         <RevenueContractCard title="收入合同" :value="formatToTenThousand(data.incomeTotalAmount, 'tenThousand')" /> | ||||
|         <!-- 支出合同:保持万元显示 --> | ||||
|         <RevenueContractCard title="支出合同" :value="formatToTenThousand(data.expensesTotalAmount, 'tenThousand')" /> | ||||
|         <!-- 合同利润:保持万元显示 --> | ||||
|         <RevenueContractCard title="合同利润" :value="formatToTenThousand(data.profitAmount, 'tenThousand')" /> | ||||
|         <!-- 工程变更:改为百分比显示 --> | ||||
|         <RevenueContractCard title="工程变更" :value="formatToTenThousand(data.changeAmount, 'percentage')" unit="" /> | ||||
|       </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 style="height: 147px; width: 100%; display: flex; justify-content: space-between"> | ||||
|         <bottomboxconpoent /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| // 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>({}); | ||||
| import { totalAmount, projectGis } from '@/api/largeScreen/index'; // 导入projectGis接口 | ||||
| import { ref, onMounted, onUnmounted, nextTick } from 'vue'; | ||||
|  | ||||
| // 地图容器引用 | ||||
| // 响应式数据(所有字段均为string,匹配接口返回) | ||||
| const data = ref({ | ||||
|   incomeTotalAmount: '0.00', | ||||
|   expensesTotalAmount: '0.00', | ||||
|   profitAmount: '0.00', | ||||
|   changeAmount: '0.00' // 接口返回的原始数值(如"0.05"代表5%) | ||||
| }); | ||||
|  | ||||
| // 地图相关变量 | ||||
| const mapRef = ref<HTMLDivElement | null>(null); | ||||
| // ECharts实例 | ||||
| let myChart: any = null; | ||||
| const projectData = ref<any[]>([]); // 存储项目地理信息数据 | ||||
|  | ||||
| // 响应窗口大小变化 | ||||
| const handleResize = () => { | ||||
|   if (myChart) { | ||||
|     myChart.resize(); | ||||
| // 接口请求:获取合同数据 | ||||
| const getContractData = async () => { | ||||
|   try { | ||||
|     const response = await totalAmount(); | ||||
|     if (response.code === 200) { | ||||
|       data.value = response.data; | ||||
|     } else { | ||||
|       console.error('合同数据请求失败:', response.msg); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('合同数据调用异常:', error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 初始化地图 | ||||
| // 新增:获取项目地理信息数据 | ||||
| const getProjectGisData = async () => { | ||||
|   try { | ||||
|     const response = await projectGis(); | ||||
|     if (response.code === 200) { | ||||
|       // 过滤掉没有经纬度的项目 | ||||
|       projectData.value = response.data.filter((item: any) => item.lng !== null && item.lat !== null && item.lng !== '' && item.lat !== ''); | ||||
|       console.log('项目地理数据:', projectData.value); | ||||
|     } else { | ||||
|       console.error('项目地理数据请求失败:', response.msg); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('项目地理数据调用异常:', error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 工具函数:根据类型处理数据显示 | ||||
|  * @param value 接口返回的原始string数值 | ||||
|  * @param type 处理类型:tenThousand(万元)、percentage(百分比) | ||||
|  * @returns 直接显示的字符串 | ||||
|  */ | ||||
| const formatToTenThousand = (value: string, type: 'tenThousand' | 'percentage') => { | ||||
|   const num = parseFloat(value); | ||||
|   if (isNaN(num)) return type === 'percentage' ? '0.00%' : '0.00'; | ||||
|  | ||||
|   if (type === 'percentage') { | ||||
|     return (num * 100).toFixed(2) + '%'; | ||||
|   } | ||||
|  | ||||
|   return (num / 10000).toFixed(2); | ||||
| }; | ||||
|  | ||||
| // 地图resize处理 | ||||
| const handleResize = () => { | ||||
|   if (myChart) myChart.resize(); | ||||
| }; | ||||
|  | ||||
| // 初始化地图(修改为使用接口数据) | ||||
| const initEcharts = () => { | ||||
|   if (!mapRef.value) { | ||||
|     console.error('未找到地图容器元素'); | ||||
|   if (!mapRef.value || projectData.value.length === 0) { | ||||
|     console.error('未找到地图容器或项目数据'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // 注册地图 | ||||
|   echarts.registerMap('china', china as any); | ||||
|  | ||||
|   // 地图数据 | ||||
|   const mapData: any = [{ name: '田东县', value: 1, itemStyle: { color: '#fff' } }]; | ||||
|  | ||||
|   // 散点数据 | ||||
|   // 散点数据 - 使用图片标记并调整名称位置 | ||||
|   const scatterData: any[] = [ | ||||
|     { | ||||
|       name: '田东光伏智慧生态工地开发项目', | ||||
|       value: [107.15, 23.63], | ||||
|       // 使用图片作为标记(注意:需替换为你的图片实际路径) | ||||
|       symbol: 'diamond', | ||||
|       // 标记颜色 | ||||
|       itemStyle: { | ||||
|         color: '#0166d6' | ||||
|       }, | ||||
|       // 图片标记大小(宽, 高) | ||||
|       symbolSize: [20, 20], | ||||
|       // 名称样式设置 | ||||
|       label: { | ||||
|         show: true, | ||||
|         formatter: '{b}', // 显示名称 | ||||
|         position: 'top', // 名称在图片上方 | ||||
|         color: '#fff', | ||||
|         fontSize: 12, | ||||
|         // 可选:添加文字背景以增强可读性 | ||||
|         backgroundColor: 'rgba(3, 26, 52, 0.7)', | ||||
|         padding: [3, 6], | ||||
|         borderRadius: 3 | ||||
|       } | ||||
|   // 从接口数据生成散点数据 | ||||
|   const scatterData = projectData.value.map((item) => ({ | ||||
|     name: item.projectName, | ||||
|     value: [parseFloat(item.lng), parseFloat(item.lat)], // 转换为数值类型 | ||||
|     shortName: item.shortName, | ||||
|     projectSite: item.projectSite, | ||||
|     symbol: 'diamond', | ||||
|     itemStyle: { color: '#0166d6' }, | ||||
|     symbolSize: [20, 20], | ||||
|     label: { | ||||
|       show: true, | ||||
|       formatter: '{b}', // 显示项目名称 | ||||
|       position: 'top', | ||||
|       color: '#fff', | ||||
|       fontSize: 12, | ||||
|       backgroundColor: 'rgba(3, 26, 52, 0.7)', | ||||
|       padding: [3, 6], | ||||
|       borderRadius: 3 | ||||
|     } | ||||
|   ]; | ||||
|   // 初始化新实例,强制清除缓存 | ||||
|   })); | ||||
|  | ||||
|   myChart = echarts.init(mapRef.value, null, { | ||||
|     renderer: 'canvas', // 明确指定渲染器 | ||||
|     useDirtyRect: false // 禁用脏矩形渲染,避免样式缓存 | ||||
|     renderer: 'canvas', | ||||
|     useDirtyRect: false | ||||
|   }); | ||||
|   // 配置项 | ||||
|  | ||||
|   const option: any = { | ||||
|     roam: true, // 关键配置:允许鼠标滚轮缩放和拖拽平移 | ||||
|     roam: true, // 允许缩放和平移 | ||||
|     geo: { | ||||
|       type: 'map', | ||||
|       map: 'china', | ||||
|       zoom: 5, | ||||
|       center: [107.15, 23.63], | ||||
|       label: { | ||||
|         show: false, | ||||
|         color: '#fff' | ||||
|       }, | ||||
|       zoom: 4, // 调整初始缩放级别 | ||||
|       center: [108.95, 34.27], // 中国中心位置经纬度 | ||||
|       label: { show: false, color: '#fff' }, | ||||
|       itemStyle: { | ||||
|         areaColor: '#031a34', // 地图区域底色 | ||||
|         borderColor: '#1e3a6e', // 区域边框颜色 | ||||
|         areaColor: '#031a34', | ||||
|         borderColor: '#1e3a6e', | ||||
|         borderWidth: 1 | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       trigger: 'item', | ||||
|       formatter: function (params: any) { | ||||
|         return params.name + (params.value ? `:${params.value}` : ''); | ||||
|       formatter: (params: any) => { | ||||
|         // 自定义提示框内容,显示更多项目信息 | ||||
|         const data = params.data; | ||||
|         return ` | ||||
|           <div style="font-weight: bold;">${data.name}</div> | ||||
|           <div>地点:${data.projectSite}</div> | ||||
|           <div>经纬度:${data.value[0].toFixed(6)}, ${data.value[1].toFixed(6)}</div> | ||||
|         `; | ||||
|       } | ||||
|     }, | ||||
|     series: [ | ||||
| @ -148,47 +162,39 @@ const initEcharts = () => { | ||||
|         type: 'map', | ||||
|         map: 'china', | ||||
|         geoIndex: 0, | ||||
|         // 关键:在series级别定义emphasis,优先级最高 | ||||
|         emphasis: { | ||||
|           areaColor: '#fff', // 强制设置hover颜色 | ||||
|           label: { | ||||
|             show: true, | ||||
|             color: '#fff' | ||||
|           }, | ||||
|           itemStyle: { | ||||
|             areaColor: '#02417e' // 重复设置确保生效 | ||||
|           } | ||||
|           areaColor: '#fff', | ||||
|           label: { show: true, color: '#fff' }, | ||||
|           itemStyle: { areaColor: '#02417e' } | ||||
|         }, | ||||
|         // 确保没有使用默认样式 | ||||
|         select: { | ||||
|           itemStyle: { | ||||
|             areaColor: '#02417e' | ||||
|           } | ||||
|         }, | ||||
|         data: mapData | ||||
|         select: { itemStyle: { areaColor: '#02417e' } }, | ||||
|         data: [] // 可以留空,或根据需要添加区域数据 | ||||
|       }, | ||||
|       { | ||||
|         type: 'scatter', | ||||
|         coordinateSystem: 'geo', | ||||
|         data: scatterData | ||||
|         data: scatterData, | ||||
|         emphasis: { | ||||
|           scale: true, // 鼠标悬停时放大 | ||||
|           symbolSize: [25, 25] | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   }; | ||||
|  | ||||
|   // 设置配置项 | ||||
|   myChart.setOption(option); | ||||
| }; | ||||
|  | ||||
| // 组件挂载时初始化 | ||||
| // 组件生命周期 | ||||
| onMounted(() => { | ||||
|   // 确保DOM渲染完成 | ||||
|   nextTick(() => { | ||||
|   nextTick(async () => { | ||||
|     // 先获取合同数据和项目地理数据,再初始化地图 | ||||
|     await Promise.all([getContractData(), getProjectGisData()]); | ||||
|     initEcharts(); | ||||
|     window.addEventListener('resize', handleResize); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| // 组件卸载时清理 | ||||
| onUnmounted(() => { | ||||
|   window.removeEventListener('resize', handleResize); | ||||
|   if (myChart) { | ||||
| @ -196,15 +202,6 @@ onUnmounted(() => { | ||||
|     myChart = null; | ||||
|   } | ||||
| }); | ||||
| // const getDataList = () => { | ||||
| //   getPowerStationOverview().then((res) => { | ||||
| //     console.log(res); | ||||
| //     if (res.code == 200) { | ||||
| //       data.value = res.data; | ||||
| //     } | ||||
| //   }); | ||||
| // }; | ||||
| // getDataList(); | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| @ -215,14 +212,11 @@ onUnmounted(() => { | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
|   padding: 0 10px 10px 10px; | ||||
|  | ||||
|   box-sizing: border-box; | ||||
|  | ||||
|   .centerPage_map { | ||||
|     width: 100%; | ||||
|     height: 60%; | ||||
|   } | ||||
|  | ||||
|    | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -1,56 +1,307 @@ | ||||
| <template> | ||||
|   <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"  | ||||
|       <TitleComponent :title="'支付KPI'" style="margin-bottom: 20px" /> | ||||
|       <ProgressComponent | ||||
|         title="应收账款" | ||||
|         :value="formatCurrency(incomeData.planAmount)" | ||||
|         :percentageChange="getPercentageChange(incomeData.planAmount, incomeData.actualAmount)" | ||||
|         :progressPercentage="getProgressPercentage(incomeData.planAmount, incomeData.actualAmount)" | ||||
|         progressColor="rgba(255, 77, 79, 1)" | ||||
|       /> | ||||
|       <ProgressComponent  | ||||
|         title="应付账款"  | ||||
|         value="123,456.78"  | ||||
|         percentageChange="+25.30%"  | ||||
|         progressPercentage="25"  | ||||
|       <ProgressComponent | ||||
|         title="应付账款" | ||||
|         :value="formatCurrency(expensesData.planAmount)" | ||||
|         :percentageChange="getPercentageChange(expensesData.planAmount, expensesData.actualAmount)" | ||||
|         :progressPercentage="getProgressPercentage(expensesData.planAmount, expensesData.actualAmount)" | ||||
|         progressColor="rgba(29, 214, 255, 1)" | ||||
|       /> | ||||
|       <ProgressComponent  | ||||
|         title="本月付款"  | ||||
|         value="123,456.78"  | ||||
|         percentageChange="+25.30%"  | ||||
|         progressPercentage="45"  | ||||
|       <ProgressComponent | ||||
|         title="本月付款" | ||||
|         :value="formatCurrency(expensesData.actualAmount)" | ||||
|         :percentageChange="getPercentageChange(expensesData.planAmount, expensesData.actualAmount)" | ||||
|         :progressPercentage="getProgressPercentage(expensesData.planAmount, expensesData.actualAmount)" | ||||
|         progressColor="rgba(0, 227, 150, 1)" | ||||
|       /> | ||||
|       <ProgressComponent  | ||||
|         title="本月收款"  | ||||
|         value="123,456.78"  | ||||
|         percentageChange="+25.30%"  | ||||
|         progressPercentage="10"  | ||||
|       <ProgressComponent | ||||
|         title="本月收款" | ||||
|         :value="formatCurrency(incomeData.actualAmount)" | ||||
|         :percentageChange="getPercentageChange(incomeData.planAmount, incomeData.actualAmount)" | ||||
|         :progressPercentage="getProgressPercentage(incomeData.planAmount, incomeData.actualAmount)" | ||||
|         progressColor="rgba(255, 147, 42, 1)" | ||||
|       /> | ||||
|     </div> | ||||
|     <div class="contract_box"> | ||||
|       <EchartBox :option="barOption" /> | ||||
|       <!-- 切换按钮 --> | ||||
|       <TitleComponent :title="'收支合同分析'" style="margin-bottom: 20px" /> | ||||
|       <div style="margin-bottom: 10px; text-align: center"> | ||||
|         <button @click="switchChart('income')" :style="activeChart === 'income' ? activeBtnStyle : defaultBtnStyle">收入合同</button> | ||||
|         <button @click="switchChart('expenses')" :style="activeChart === 'expenses' ? activeBtnStyle : defaultBtnStyle" style="margin-left: 15px"> | ||||
|           支出合同 | ||||
|         </button> | ||||
|       </div> | ||||
|       <!-- 环形图容器:固定高度确保图表不拉伸,添加flex居中 --> | ||||
|       <div class="chart-container"> | ||||
|         <EchartBox :option="pieOption" /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, reactive, onMounted, computed, toRefs, getCurrentInstance, nextTick } from 'vue'; | ||||
| // import echarts from 'echarts'; | ||||
| import TitleComponent  from './TitleComponent.vue'; | ||||
| import { ref, onMounted, watch } from 'vue'; | ||||
| 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(); | ||||
| import { incomePay, expensesPay, incomeAnalyze, expensesAnalyze } from '@/api/largeScreen/index'; | ||||
|  | ||||
| // 初始化数据容器 | ||||
| const incomeData = ref({ planAmount: '0.00', actualAmount: '0.00' }); // 收入相关数据(应收/收款) | ||||
| const expensesData = ref({ planAmount: '0.00', actualAmount: '0.00' }); // 支出相关数据(应付/付款) | ||||
| const pieOption = ref({}); // 环形图配置 | ||||
| const activeChart = ref('income'); // 当前激活的图表类型:income-收入合同,expenses-支出合同 | ||||
| // 新增:存储合同数据及对应总数(避免重复计算) | ||||
| const chartData = ref({ | ||||
|   income: { firstCount: 0, secondCount: 0, thirdCount: 0, fourthCount: 0, total: 0 }, // 收入合同+总数 | ||||
|   expenses: { firstCount: 0, secondCount: 0, thirdCount: 0, fourthCount: 0, total: 0 } // 支出合同+总数 | ||||
| }); | ||||
|  | ||||
| // 按钮样式 | ||||
| const defaultBtnStyle = { | ||||
|   padding: '4px 16px', | ||||
|   border: '1px solid rgba(29, 214, 255, 0.5)', | ||||
|   backgroundColor: 'transparent', | ||||
|   color: 'rgba(29, 214, 255, 1)', | ||||
|   cursor: 'pointer', | ||||
|   borderRadius: '4px' | ||||
| }; | ||||
| onMounted(() => { | ||||
|   getCapitalData(); | ||||
| const activeBtnStyle = { | ||||
|   ...defaultBtnStyle, | ||||
|   backgroundColor: 'rgba(29, 214, 255, 0.2)', | ||||
|   borderColor: 'rgba(29, 214, 255, 1)' | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 格式化金额为带千分位的格式 | ||||
|  * @param {string} amount 金额字符串 | ||||
|  * @returns {string} 格式化后的金额 | ||||
|  */ | ||||
| const formatCurrency = (amount) => { | ||||
|   if (!amount) return '0.00'; | ||||
|   return Number(amount).toLocaleString('zh-CN', { | ||||
|     minimumFractionDigits: 2, | ||||
|     maximumFractionDigits: 2 | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 计算百分比变化(保留两位小数) | ||||
|  * @param {string} plan 计划金额 | ||||
|  * @param {string} actual 实际金额 | ||||
|  * @returns {string} 带正负号的百分比字符串 | ||||
|  */ | ||||
| const getPercentageChange = (plan, actual) => { | ||||
|   const planNum = Number(plan); | ||||
|   const actualNum = Number(actual); | ||||
|  | ||||
|   if (planNum === 0) { | ||||
|     return planNum === actualNum ? '0.00%' : '+100.00%'; | ||||
|   } | ||||
|  | ||||
|   const change = ((actualNum - planNum) / planNum) * 100; | ||||
|   return `${change >= 0 ? '+' : ''}${change.toFixed(2)}%`; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 计算进度百分比(保留整数) | ||||
|  * @param {string} plan 计划金额 | ||||
|  * @param {string} actual 实际金额 | ||||
|  * @returns {number} 进度百分比(0-100) | ||||
|  */ | ||||
| const getProgressPercentage = (plan, actual) => { | ||||
|   const planNum = Number(plan); | ||||
|   const actualNum = Number(actual); | ||||
|  | ||||
|   if (planNum === 0) { | ||||
|     return actualNum === 0 ? 0 : 100; | ||||
|   } | ||||
|  | ||||
|   const percentage = (actualNum / planNum) * 100; | ||||
|   return Math.min(Math.round(percentage), 100); // 限制最大为100 | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 获取并处理资金数据(原逻辑保留) | ||||
|  */ | ||||
| const getCapitalData = async () => { | ||||
|   try { | ||||
|     const [incomeRes, expensesRes] = await Promise.all([incomePay(), expensesPay()]); | ||||
|  | ||||
|     if (incomeRes.code === 200) { | ||||
|       incomeData.value = incomeRes.data; | ||||
|     } | ||||
|     if (expensesRes.code === 200) { | ||||
|       expensesData.value = expensesRes.data; | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('获取资金数据失败:', error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 计算单类合同总数(复用方法) | ||||
|  * @param {Object} data 单类合同数据(收入/支出) | ||||
|  * @returns {number} 合同总数 | ||||
|  */ | ||||
| const calculateTotalCount = (data) => { | ||||
|   return data.firstCount + data.secondCount + data.thirdCount + data.fourthCount; | ||||
| }; | ||||
|  | ||||
| // 新增:标记合同图表数据是否加载完成 | ||||
| const isChartDataLoaded = ref(false); | ||||
|  | ||||
| // 修改 getContractChartData 方法:请求成功后标记加载完成 | ||||
| const getContractChartData = async () => { | ||||
|   try { | ||||
|     const [incomeChartRes, expensesChartRes] = await Promise.all([incomeAnalyze(), expensesAnalyze()]); | ||||
|  | ||||
|     // 收入合同数据处理(原逻辑保留) | ||||
|     if (incomeChartRes.code === 200) { | ||||
|       const incomeRaw = incomeChartRes.data; | ||||
|       chartData.value.income = { | ||||
|         ...incomeRaw, | ||||
|         total: calculateTotalCount(incomeRaw) | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     // 支出合同数据处理(原逻辑保留) | ||||
|     if (expensesChartRes.code === 200) { | ||||
|       const expensesRaw = expensesChartRes.data; | ||||
|       chartData.value.expenses = { | ||||
|         ...expensesRaw, | ||||
|         total: calculateTotalCount(expensesRaw) | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     // ✅ 关键:所有数据请求完成后,标记为“已加载” | ||||
|     isChartDataLoaded.value = true; | ||||
|   } catch (error) { | ||||
|     console.error('获取合同图表数据失败:', error); | ||||
|     // 可选:失败时也标记加载完成,避免一直显示“加载中”(可替换为“加载失败”提示) | ||||
|     isChartDataLoaded.value = true; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const generatePieOption = (data) => { | ||||
|   // 1. 未加载:返回“加载中”配置 | ||||
|   if (!isChartDataLoaded.value) { | ||||
|     return { | ||||
|       tooltip: { trigger: 'none' }, // 未加载时不需要tooltip | ||||
|       legend: { show: false }, // 隐藏图例(无数据可展示) | ||||
|       series: [ | ||||
|         { | ||||
|           name: '合同数量', | ||||
|           type: 'pie', | ||||
|           radius: ['45%', '65%'], | ||||
|           center: ['50%', '60%'], | ||||
|           data: [], // 空数据,避免显示圆环 | ||||
|           itemStyle: { borderColor: '#000', borderWidth: 1 }, | ||||
|           // 中心显示“加载中”提示 | ||||
|           label: { | ||||
|             show: true, | ||||
|             position: 'center', | ||||
|             formatter: '合同数据加载中...', | ||||
|             textStyle: { color: 'rgba(255, 255, 255, 0.9)', fontSize: 16 } | ||||
|           }, | ||||
|           emphasis: { label: { show: false } }, | ||||
|           labelLine: { show: false } | ||||
|         } | ||||
|       ], | ||||
|       grid: { top: '15%', bottom: '5%', left: '5%', right: '5%' } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   // 2. 已加载:返回真实数据配置(原逻辑保留,仅优化中心文本) | ||||
|   const pieData = [ | ||||
|     { name: '100万以下', value: data.firstCount }, | ||||
|     { name: '100-500万', value: data.secondCount }, | ||||
|     { name: '500-1000万', value: data.thirdCount }, | ||||
|     { name: '1000万以上', value: data.fourthCount } | ||||
|   ]; | ||||
|  | ||||
|   return { | ||||
|     tooltip: { | ||||
|       trigger: 'item', | ||||
|       formatter: '{b}: {c} 份' | ||||
|     }, | ||||
|     legend: { | ||||
|       top: '5%', | ||||
|       left: 'center', | ||||
|       textStyle: { color: 'rgba(255, 255, 255, 0.8)' }, | ||||
|       itemWidth: 12, | ||||
|       itemHeight: 12 | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         name: '合同数量', | ||||
|         type: 'pie', | ||||
|         radius: ['70%', '80%'], // 恢复原半径(原代码中70%/80%可能过窄,可根据需求调整) | ||||
|         center: ['50%', '60%'], | ||||
|         avoidLabelOverlap: false, | ||||
|         itemStyle: { | ||||
|           borderRadius: 8, | ||||
|           borderColor: '#000', | ||||
|           borderWidth: 1 | ||||
|         }, | ||||
|         // 中心文本:显示真实总数(此时data.total已可靠) | ||||
|         label: { | ||||
|           show: true, | ||||
|           position: 'center', | ||||
|           formatter: `合同总数\n{total|${data.total} 份}`, | ||||
|           rich: { | ||||
|             total: { | ||||
|               fontSize: 20, | ||||
|               fontWeight: 'bold', | ||||
|               color: 'rgba(29, 214, 255, 1)', | ||||
|               marginTop: 8 | ||||
|             } | ||||
|           }, | ||||
|           textStyle: { color: 'rgba(255, 255, 255, 0.9)', fontSize: 14 } | ||||
|         }, | ||||
|         emphasis: { label: { show: false } }, | ||||
|         labelLine: { show: false }, | ||||
|         data: pieData, | ||||
|         color: ['rgba(255, 77, 79, 1)', 'rgba(29, 214, 255, 1)', 'rgba(0, 227, 150, 1)', 'rgba(255, 147, 42, 1)'] | ||||
|       } | ||||
|     ], | ||||
|     grid: { | ||||
|       top: '15%', | ||||
|       bottom: '5%', | ||||
|       left: '5%', | ||||
|       right: '5%' | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 切换图表类型(收入/支出) | ||||
|  * @param {string} type 目标类型:income/expenses | ||||
|  */ | ||||
| const switchChart = (type) => { | ||||
|   activeChart.value = type; | ||||
| }; | ||||
|  | ||||
| watch( | ||||
|   [activeChart, isChartDataLoaded], // 监听两个变量:图表类型 + 加载状态 | ||||
|   ([newType]) => { | ||||
|     pieOption.value = generatePieOption(chartData.value[newType]); | ||||
|   }, | ||||
|   { immediate: true } | ||||
| ); | ||||
|  | ||||
| // 组件挂载后初始化数据(并行请求,提升速度) | ||||
| onMounted(async () => { | ||||
|   await Promise.all([getCapitalData(), getContractChartData()]); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| @ -58,13 +309,20 @@ onMounted(() => { | ||||
| .leftPage { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   .kpi_box{ | ||||
|   .kpi_box { | ||||
|     margin-bottom: 10px; | ||||
|   } | ||||
|  .contract_box{ | ||||
|   .contract_box { | ||||
|     height: 35vh; | ||||
|     display: flex; | ||||
|     flex-direction: column; // 按钮区和图表区垂直排列 | ||||
|   } | ||||
|   .kpi_box,.contract_box { | ||||
|   .chart-container { | ||||
|     flex: 1; // 占满剩余高度,确保图表容器足够大 | ||||
|     min-height: 200px; // 最小高度,避免容器过矮 | ||||
|   } | ||||
|   .kpi_box, | ||||
|   .contract_box { | ||||
|     padding: 10px; | ||||
|     box-sizing: border-box; | ||||
|     border: 1px solid rgba(29, 214, 255, 0.3); | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| <template> | ||||
| ID<template> | ||||
|   <div class="large-screen"> | ||||
|     <Header /> | ||||
|     <div class="nav"> | ||||
|  | ||||
| @ -13,8 +13,7 @@ | ||||
|             <el-form-item> | ||||
|               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> | ||||
|               <el-button icon="Refresh" @click="resetQuery">重置</el-button> | ||||
|               <el-button type="primary" plain :icon="Plus" @click="openDialog" | ||||
|                 v-hasPermi="['tender:segmentedIndicatorPlanning:add']">新增</el-button> | ||||
|               <el-button type="primary" plain :icon="Plus" @click="openDialog" v-hasPermi="['tender:segmentedIndicatorPlanning:add']">新增</el-button> | ||||
|             </el-form-item> | ||||
|           </el-form> | ||||
|         </el-card> | ||||
| @ -28,23 +27,24 @@ | ||||
|           <el-table-column prop="content" label="内容" /> | ||||
|           <el-table-column prop="plannedBiddingTime" label="计划招标时间" align="center"> | ||||
|             <template #default="scope"> | ||||
|               <el-date-picker v-model="scope.row.plannedBiddingTime" type="date" value-format="YYYY-MM-DD" | ||||
|                 placeholder="选择时间" /> | ||||
|               <el-date-picker v-model="scope.row.plannedBiddingTime" type="date" value-format="YYYY-MM-DD" placeholder="选择时间" /> | ||||
|             </template> | ||||
|           </el-table-column> | ||||
|           <el-table-column prop="price" label="操作" align="center"> | ||||
|             <template #default="scope"> | ||||
|               <el-button type="warning" size="small" @click="handleDetail(scope.row)" | ||||
|                 v-hasPermi="['tender:segmentedIndicatorPlanning:getMore']">详情</el-button> | ||||
|               <el-button type="primary" size="small" @click="handleSave(scope.row)" | ||||
|                 v-hasPermi="['tender:segmentedIndicatorPlanning:edit']">修改</el-button> | ||||
|               <el-button type="danger" size="small" @click="delHandle(scope.row)" | ||||
|                 v-hasPermi="['tender:segmentedIndicatorPlanning:remove']">删除</el-button> | ||||
|               <el-button type="warning" size="small" @click="handleDetail(scope.row)" v-hasPermi="['tender:segmentedIndicatorPlanning:getMore']" | ||||
|                 >详情</el-button | ||||
|               > | ||||
|               <el-button type="primary" size="small" @click="handleSave(scope.row)" v-hasPermi="['tender:segmentedIndicatorPlanning:edit']" | ||||
|                 >修改</el-button | ||||
|               > | ||||
|               <el-button type="danger" size="small" @click="delHandle(scope.row)" v-hasPermi="['tender:segmentedIndicatorPlanning: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" /> | ||||
|         <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> | ||||
|       </el-tab-pane> | ||||
|     </el-tabs> | ||||
|     <el-dialog title="新增" v-model="dialogVisible" width="75%" draggable> | ||||
| @ -66,8 +66,7 @@ | ||||
|             <el-form :model="treeForm" :inline="true"> | ||||
|               <el-form-item label="版本号" prop="versions"> | ||||
|                 <el-select v-model="treeForm.versions" placeholder="选择版本号" @change="changeVersions"> | ||||
|                   <el-option v-for="item in options" :key="item.versions" :label="item.versions" | ||||
|                     :value="item.versions" /> | ||||
|                   <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"> | ||||
| @ -83,8 +82,16 @@ | ||||
|               </el-form-item> | ||||
|             </el-form> | ||||
|           </el-card> | ||||
|           <el-table :data="treeData" ref="treeTableRef" v-loading="treeLoading" row-key="id" border lazy | ||||
|             default-expand-all @selection-change="handleSelection"> | ||||
|           <el-table | ||||
|             :data="treeData" | ||||
|             ref="treeTableRef" | ||||
|             v-loading="treeLoading" | ||||
|             row-key="id" | ||||
|             border | ||||
|             lazy | ||||
|             default-expand-all | ||||
|             @selection-change="handleSelection" | ||||
|           > | ||||
|             <el-table-column type="selection" width="55" /> | ||||
|             <el-table-column prop="num" label="编号" /> | ||||
|             <el-table-column prop="name" label="工程或费用名称" /> | ||||
| @ -92,13 +99,20 @@ | ||||
|             <!-- <el-table-column prop="quantity" label="数量" /> --> | ||||
|             <el-table-column prop="selectNum" label="选择数量" align="center"> | ||||
|               <template #default="scope"> | ||||
|                 <el-input-number :model-value="scope.row.selectNum" @change=" | ||||
|                   (val) => { | ||||
|                     scope.row.selectNum = val; | ||||
|                     handleNumberChange(scope.row); | ||||
|                   } | ||||
|                 " :precision="2" :step="1" :controls="false" :max="Math.floor(scope.row.quantity)" | ||||
|                   v-if="scope.row.quantity && scope.row.quantity != 0" /> | ||||
|                 <el-input-number | ||||
|                   :model-value="scope.row.selectNum" | ||||
|                   @change=" | ||||
|                     (val) => { | ||||
|                       scope.row.selectNum = val; | ||||
|                       handleNumberChange(scope.row); | ||||
|                     } | ||||
|                   " | ||||
|                   :precision="2" | ||||
|                   :step="1" | ||||
|                   :controls="false" | ||||
|                   :max="Math.floor(scope.row.quantity)" | ||||
|                   v-if="scope.row.quantity && scope.row.quantity != 0" | ||||
|                 /> | ||||
|               </template> | ||||
|             </el-table-column> | ||||
|             <el-table-column prop="unitPrice" label="单价" align="center" /> | ||||
|  | ||||
| @ -323,18 +323,21 @@ | ||||
| <script setup name="SupplierInput" lang="ts"> | ||||
| import { ComponentInternalInstance, getCurrentInstance, onMounted, ref, reactive, toRefs, computed } from 'vue'; | ||||
| import { ElFormInstance } from 'element-plus'; | ||||
| import { listSupplierInput, getSupplierInput, delSupplierInput } from '@/api/supplierInput/supplierInput'; | ||||
| import { listSupplierInput, getSupplierInput, delSupplierInput } from '@/api/supplierInput/supplierInput/index'; | ||||
| import { SupplierInputVO, SupplierInputQuery, SupplierInputForm, PageData, DialogOption } from '@/api/supplierInput/supplierInput/types'; | ||||
| import Pagination from '@/components/Pagination/index.vue'; | ||||
| import RightToolbar from '@/components/RightToolbar/index.vue'; | ||||
| import FileUpload from '@/components/FileUpload/index.vue'; | ||||
| import { useUserStoreHook } from '@/store/modules/user'; | ||||
| 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>(); | ||||
| const supplierInputFormRef = ref<ElFormInstance>(); | ||||
|  | ||||
| // 获取用户 store | ||||
| const userStore = useUserStoreHook(); | ||||
| const currentProject = computed(() => userStore.selectedProject); | ||||
| // 基础数据 | ||||
| const supplierInputList = ref<SupplierInputVO[]>([]); | ||||
| const buttonLoading = ref(false); | ||||
| @ -393,6 +396,7 @@ const initFormData: any = { | ||||
| const data = reactive<PageData<SupplierInputForm, SupplierInputQuery>>({ | ||||
|   form: { ...initFormData }, | ||||
|   queryParams: { | ||||
|     projectId: currentProject.value?.id, | ||||
|     pageNum: 1, | ||||
|     pageSize: 10, | ||||
|     supplierType: undefined, | ||||
| @ -506,7 +510,7 @@ const cancel = () => { | ||||
|  | ||||
| /** 表单重置:清空所有字段 */ | ||||
| const reset = () => { | ||||
|   form.value = { ...initFormData }; | ||||
|   form.value = { ...initFormData, projectId: currentProject.value?.id }; | ||||
|   fileUrl.value = ''; | ||||
|   form.value.inputFile = ''; | ||||
|   supplierInputFormRef.value?.resetFields(); | ||||
| @ -606,8 +610,6 @@ const handleUpdate = async (row?: SupplierInputVO) => { | ||||
|  | ||||
| /** 提交表单 */ | ||||
| const submitForm = () => { | ||||
|   console.log(1); | ||||
|  | ||||
|   supplierInputFormRef.value?.validate(async (valid: boolean) => { | ||||
|     if (!valid) return; | ||||
|     if (form.value.supplierType === '劳务') { | ||||
| @ -662,10 +664,29 @@ const handleDelete = async (row?: SupplierInputVO) => { | ||||
| const handleExport = () => { | ||||
|   proxy?.download('supplierInput/supplierInput/export', { ...queryParams.value }, `supplierInput_${new Date().getTime()}.xlsx`); | ||||
| }; | ||||
|  | ||||
| /** 页面挂载时查询列表 */ | ||||
| //调用projectId并获取列表 | ||||
| onMounted(() => { | ||||
|   getList(); | ||||
|   // 若需要依赖项目ID,可先判断项目是否已选中,再调用接口 | ||||
|   if (currentProject.value?.id) { | ||||
|     queryParams.value.projectId = currentProject.value.id; | ||||
|     form.value.projectId = currentProject.value.id; | ||||
|     getList(); // 首次进入自动调取listSupplierInput接口 | ||||
|   } else { | ||||
|     // 若项目未选中,可提示或后续监听项目变化时再加载(已有监听逻辑) | ||||
|     getList(); | ||||
|   } | ||||
| }); | ||||
| // 监听项目id刷新数据 | ||||
| const listeningProject = watch( | ||||
|   () => currentProject.value?.id, | ||||
|   (nid, oid) => { | ||||
|     queryParams.value.projectId = nid; | ||||
|     form.value.projectId = nid; | ||||
|     getList(); | ||||
|   } | ||||
| ); | ||||
| onUnmounted(() => { | ||||
|   listeningProject(); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|  | ||||
| @ -437,9 +437,7 @@ onMounted(() => { | ||||
|   .el-input__inner, | ||||
|   .el-select .el-input__inner { | ||||
|     border-radius: 4px; | ||||
|     transition: | ||||
|       border-color 0.2s, | ||||
|       box-shadow 0.2s; | ||||
|     transition: border-color 0.2s, box-shadow 0.2s; | ||||
|  | ||||
|     &:focus { | ||||
|       border-color: var(--primary-light); | ||||
| @ -449,9 +447,7 @@ onMounted(() => { | ||||
|  | ||||
|   .el-textarea__inner { | ||||
|     border-radius: 4px; | ||||
|     transition: | ||||
|       border-color 0.2s, | ||||
|       box-shadow 0.2s; | ||||
|     transition: border-color 0.2s, box-shadow 0.2s; | ||||
|  | ||||
|     &:focus { | ||||
|       border-color: var(--primary-light); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user