首页一级页面接口调试完成

This commit is contained in:
BianLzhaoMin 2025-11-04 17:33:27 +08:00
parent 5b3e877f0c
commit 85d9e7fe5f
20 changed files with 771 additions and 356 deletions

View File

@ -1,4 +1,4 @@
# VITE_API_BASE_URL = http://112.29.103.165:1616
# VITE_API_BASE_URL = /api
VITE_API_BASE_URL = http://192.168.0.14:1999/hd-real-name
VITE_API_BASE_URL = /api
# VITE_API_BASE_URL = http://192.168.0.14:1999/hd-real-name
# VITE_API_BASE_URL = http://192.168.0.234:38080

View File

@ -10,7 +10,7 @@
<u-icon :name="item.icon" size="32" color="#165DFF"></u-icon>
</view>
<view class="data-content">
<text class="data-value">{{ item.value }}</text>
<text class="data-value">{{ projectInfoViewData[item.key] }}</text>
<text class="data-label">{{ item.label }}</text>
</view>
</view>
@ -21,32 +21,50 @@
<script setup>
import { ref, reactive } from 'vue'
import AllProjectIcon from '@/static/image/home/all_project.png'
import LotProjectIcon from '@/static/image/home/lot_project.png'
import SubIcon from '@/static/image/home/sub.png'
import TeamIcon from '@/static/image/home/team.png'
import PersonIcon from '@/static/image/home/person.png'
import AttIcon from '@/static/image/home/att.png'
const props = defineProps({
projectInfoViewData: {
type: Object,
default: () => {},
},
})
//
const dataList = ref([
{
icon: 'file-text',
value: '319',
icon: AllProjectIcon,
key: 'mainProNum',
label: '总工程',
},
{
icon: LotProjectIcon,
key: 'proNum',
label: '标段工程',
},
{
icon: 'checkmark-circle',
value: '111',
icon: SubIcon,
key: 'subNum',
label: '在用分包',
},
{
icon: 'file-text',
value: '1,234',
icon: TeamIcon,
key: 'teamNum',
label: '在用班组',
},
{
icon: 'account',
value: '14,563',
icon: PersonIcon,
key: 'attNum',
label: '在场人员',
},
{
icon: 'clock',
value: '9,563',
icon: AttIcon,
key: 'einNum',
label: '今日打卡',
},
])
@ -96,14 +114,14 @@ const dataList = ref([
}
.data-icon {
width: 60rpx;
/* width: 60rpx;
height: 60rpx;
background: rgba(22, 93, 255, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15rpx;
margin-bottom: 15rpx; */
}
.data-content {

View File

@ -5,8 +5,12 @@
</view>
<view style="width: 74%">
<MultiColorNoticeBar
:textArray="[`黄灯人数:${20}`, `黄灯超7天人数:${60}`, `出场未上传结算单:${80}`]"
:textStyle="[{ color: '#bc2843' }, { color: '#ffed51' }, { color: '#7dda76' }]"
:textArray="[
`黄灯人数:${noticeListData.yellowNum}`,
`黄灯超7天人数:${noticeListData.yellowThanSevenDayNum}`,
`出场未上传结算单:${noticeListData.exitNoFileNum}`,
]"
:textStyle="[{ color: '#ffed51' }, { color: '#bc2843' }, { color: '#f81115' }]"
:scrollSpeed="0.5"
/>
</view>
@ -14,8 +18,13 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import MultiColorNoticeBar from '@/components/MultiColorNoticeBar/index.vue'
const props = defineProps({
noticeListData: {
type: Object,
default: () => {},
},
})
</script>
<style scoped lang="scss">

View File

@ -6,11 +6,10 @@
</view>
<!-- 人员状态 -->
<view class="person-status">
<!-- <view class="person-status">
<view class="status-title">人员状态</view>
<view class="status-cards">
<view class="status-card" v-for="(item, index) in personStatus" :key="index">
<!-- 环形图 -->
<view class="circular-chart">
<view class="chart-container">
<view class="chart-bg"></view>
@ -29,16 +28,16 @@
</view>
</view>
</view>
<!-- 百分比文字 -->
<view class="percentage-text">
<text class="percentage">{{ item.percentage }}%</text>
<text class="count">({{ item.count }})</text>
</view>
<!-- 标签 -->
<view class="status-label">{{ item.label }}</view>
</view>
</view>
</view>
</view> -->
<!-- 性别比例象形图 -->
<view class="gender-ratio">
@ -76,7 +75,7 @@
</view>
<!-- 固定临时人员 -->
<view class="personnel-type">
<!-- <view class="personnel-type">
<view class="type-title">固定临时人员</view>
<view class="type-content">
<view class="type-items">
@ -108,7 +107,7 @@
<text class="type-label">临时占比 {{ temporaryPercentage }}%</text>
</view>
</view>
</view>
</view> -->
<!-- 年龄分布 -->
<view class="age-distribution">
@ -117,10 +116,6 @@
<view class="age-chart">
<qiun-data-charts type="ring" :opts="pieOpts" :chartData="ageChartData" />
</view>
<view class="age-center-info">
<text class="age-total-count">{{ ageTotalCount }}</text>
<text class="age-total-label">人数</text>
</view>
</view>
<!-- 自定义 legend两列布局显示名称/百分比/数量 -->
<view class="age-legend">
@ -144,13 +139,68 @@
<!-- 工种分布 -->
<view class="work-type-distribution">
<view class="work-title">工种分布</view>
<view class="work-chart">
<qiun-data-charts
type="pie"
:opts="pieOpts"
:chartData="workTypeChartData"
@getIndex="handleWorkTypeChartClick"
/>
<view class="age-chart-container">
<view class="age-chart">
<qiun-data-charts
type="ring"
:opts="workPieOpts"
:chartData="workTypeChartData"
/>
</view>
</view>
<view class="age-legend">
<view class="age-legend-item" v-for="(row, idx) in workLegendRows" :key="idx">
<view class="legend-left" v-if="row[0]">
<view class="dot" :style="{ backgroundColor: row[0].color }"></view>
<text class="name">{{ row[0].name }}</text>
<text class="percent">{{ row[0].percent }}%</text>
<text class="count">({{ row[0].value }})</text>
</view>
<view class="legend-right" v-if="row[1]">
<view class="dot" :style="{ backgroundColor: row[1].color }"></view>
<text class="name">{{ row[1].name }}</text>
<text class="percent">{{ row[1].percent }}%</text>
<text class="count">({{ row[1].value }})</text>
</view>
</view>
</view>
</view>
<!-- 红黄绿灯占比 -->
<view class="work-type-distribution">
<view class="work-title">红黄绿灯占比</view>
<view class="age-chart-container">
<view class="age-chart">
<qiun-data-charts
type="ring"
:opts="redYellowGreenPieOpts"
:chartData="redYellowGreenChartData"
/>
</view>
</view>
<view class="age-legend">
<view
class="age-legend-item"
v-for="(row, idx) in redYellowGreenLegendRows"
:key="idx"
>
<view class="legend-left" v-if="row[0]">
<view class="dot" :style="{ backgroundColor: row[0].color }"></view>
<text class="name">{{ row[0].name }}</text>
<text class="percent">{{ row[0].percent }}%</text>
<text class="count">({{ row[0].value }})</text>
</view>
<view class="legend-right" v-if="row[1]">
<view class="dot" :style="{ backgroundColor: row[1].color }"></view>
<text class="name">{{ row[1].name }}</text>
<text class="percent">{{ row[1].percent }}%</text>
<text class="count">({{ row[1].value }})</text>
</view>
</view>
</view>
</view>
</view>
@ -159,6 +209,8 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { getHomeIndexPersonMsgAPI } from '@/services/home-index'
import { getRandomColor } from '@/utils'
//
const personStatus = ref([
@ -189,8 +241,8 @@ const personStatus = ref([
])
//
const maleCount = ref(7500)
const femaleCount = ref(2500)
const maleCount = ref(0)
const femaleCount = ref(0)
const malePercentage = computed(() =>
Math.round((maleCount.value / (maleCount.value + femaleCount.value)) * 100),
)
@ -218,6 +270,16 @@ const pieOpts = reactive({
dataLabel: false, // 线
enableScroll: false,
legend: { show: false }, // legend
title: {
name: '30',
fontSize: 18,
color: '#000',
},
subtitle: {
name: '人数',
fontSize: 12,
color: '#666',
},
extra: {
ring: {
ringWidth: 28, //
@ -233,12 +295,83 @@ const pieOpts = reactive({
},
})
const workPieOpts = reactive({
rotate: false,
rotateLock: false,
color: [],
// padding
padding: [5, 5, 5, 5],
dataLabel: false, // 线
enableScroll: false,
legend: { show: false }, // legend
title: {
name: '30',
fontSize: 18,
color: '#000',
},
subtitle: {
name: '总数量',
fontSize: 12,
color: '#666',
},
extra: {
ring: {
ringWidth: 28, //
activeOpacity: 0.6,
activeRadius: 6,
offsetAngle: 0,
border: true,
borderWidth: 2,
borderColor: '#FFFFFF',
linearType: 'custom',
customColor: [],
},
},
})
const redYellowGreenPieOpts = reactive({
rotate: false,
rotateLock: false,
color: ['#67C23A', '#F8F807'],
// padding
padding: [5, 5, 5, 5],
dataLabel: false, // 线
enableScroll: false,
legend: { show: false }, // legend
title: {
name: '30',
fontSize: 18,
color: '#000',
},
subtitle: {
name: '总数量',
fontSize: 12,
color: '#666',
},
extra: {
ring: {
ringWidth: 28, //
activeOpacity: 0.6,
activeRadius: 6,
offsetAngle: 0,
border: true,
borderWidth: 2,
borderColor: '#FFFFFF',
linearType: 'custom',
customColor: ['#67C23A', '#F8F807'],
},
},
})
//
const ageChartData = ref({})
//
const workTypeChartData = ref({})
// 绿
const redYellowGreenChartData = ref({})
//
const ageTotalCount = computed(() => {
if (ageChartData.value.series && ageChartData.value.series[0]) {
@ -256,7 +389,7 @@ const ageLegend = computed(() => {
name: d.name,
value: d.value,
percent: ((d.value / total) * 100).toFixed(2),
color: pieOpts.color[i % pieOpts.color.length],
color: ['#FF8C00', '#165DFF', '#2ED573', '#00D4AA', '#FF69B4'][i % 5],
}))
})
@ -269,45 +402,142 @@ const ageLegendRows = computed(() => {
return rows
})
onMounted(() => {
//
setTimeout(() => {
const ageData = {
series: [
{
name: '年龄分布',
data: [
{ name: '<20', value: 10 },
{ name: '20-30', value: 13 },
{ name: '30-40', value: 16 },
{ name: '40-50', value: 40 },
{ name: '50-60', value: 10 },
],
},
],
}
ageChartData.value = JSON.parse(JSON.stringify(ageData))
}, 500)
const workLegend = computed(() => {
if (!workTypeChartData.value.series || !workTypeChartData.value.series[0]) return []
const data = workTypeChartData.value.series[0].data
const total = data.reduce((s, d) => s + d.value, 0) || 1
return data.map((d, i) => ({
name: d.name,
value: d.value,
percent: ((d.value / total) * 100).toFixed(2),
color: workTypeChartData.value.series[0].color[i],
}))
})
//
setTimeout(() => {
const workTypeData = {
const workLegendRows = computed(() => {
const items = workLegend.value
const rows = []
for (let i = 0; i < items.length; i += 2) {
rows.push([items[i], items[i + 1]])
}
return rows
})
const redYellowGreenLegend = computed(() => {
if (!redYellowGreenChartData.value.series || !redYellowGreenChartData.value.series[0]) return []
const data = redYellowGreenChartData.value.series[0].data
const total = data.reduce((s, d) => s + d.value, 0) || 1
return data.map((d, i) => ({
name: d.name,
value: d.value,
percent: ((d.value / total) * 100).toFixed(2),
color: ['#67C23A', '#F8F807'][i % 2],
}))
})
const redYellowGreenLegendRows = computed(() => {
const items = redYellowGreenLegend.value
const rows = []
for (let i = 0; i < items.length; i += 2) {
rows.push([items[i], items[i + 1]])
}
return rows
})
const initAgeLabel = (age) => {
switch (age) {
case 20:
return '<20'
case 30:
return '20-30'
case 40:
return '30-40'
case 50:
return '40-50'
case 60:
return '50-60'
}
}
//
const getPersonMsg = async () => {
const { data: res } = await getHomeIndexPersonMsgAPI({ subComId: '' })
const { feMailNum, mailNum, postMsg, greenNum, yellowNum } = res
maleCount.value = mailNum
femaleCount.value = feMailNum
let xData = []
let yData = []
let yColor = []
for (const key in res) {
if (key.slice(0, 3) === 'num') {
xData.push({
name: initAgeLabel(Number(key.split('num')[1])),
value: res[key],
})
const ageData = {
series: [
{
name: '年龄分布',
data: xData,
},
],
}
ageChartData.value = JSON.parse(JSON.stringify(ageData))
}
}
pieOpts.title.name = xData.reduce((sum, item) => sum + item.value, 0)
if (postMsg && postMsg.length > 0) {
postMsg.forEach((item) => {
yData.push({
name: item.key,
value: item.value * 1,
})
yColor.push(getRandomColor())
})
const workData = {
series: [
{
name: '工种分布',
data: yData,
color: yColor,
},
],
}
workTypeChartData.value = JSON.parse(JSON.stringify(workData))
}
if (greenNum && yellowNum) {
redYellowGreenChartData.value = {
series: [
{
name: '红黄绿灯占比',
data: [
{ name: '普工', value: 600 },
{ name: '焊工', value: 500 },
{ name: '钳工', value: 400 },
{ name: '电工', value: 300 },
{ name: '木工', value: 200 },
{ name: '其他', value: 150 },
{
name: '绿灯',
value: greenNum,
},
{
name: '黄灯',
value: yellowNum,
},
],
},
],
}
workTypeChartData.value = JSON.parse(JSON.stringify(workTypeData))
}, 800)
}
workPieOpts.title.name = yData.reduce((sum, item) => sum + item.value, 0)
workPieOpts.extra.ring.customColor = yColor
workPieOpts.color = yColor
redYellowGreenPieOpts.title.name = greenNum + yellowNum
}
onMounted(() => {
getPersonMsg()
})
//
@ -323,7 +553,8 @@ const handleWorkTypeChartClick = (index) => {
<style scoped>
.person-container {
margin: 0 20rpx 20rpx;
margin: 0 20rpx;
padding-bottom: 60px;
}
.person-card {

View File

@ -15,7 +15,7 @@
>
<view class="status-bar" :style="{ backgroundColor: item.color }"></view>
<text class="status-label">{{ item.label }}</text>
<text class="status-value">{{ item.value }}</text>
<text class="status-value">{{ projectCount[item.key] }}</text>
</view>
</view>
</view>
@ -67,211 +67,59 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getHomeIndexProjectMsgAPI } from '@/services/home-index'
//
const projectStatus = ref([
{ label: '在建', value: '319', color: '#165DFF' },
{ label: '筹建', value: '62', color: '#FF8C00' },
{ label: '停工', value: '2', color: '#FF4757' },
{ label: '完工', value: '200', color: '#2ED573' },
{ label: '在建工程', key: 'buildProNum', color: '#165DFF' },
{ label: '筹建工程', key: 'prepareProNum', color: '#FF8C00' },
{ label: '停工工程', key: 'stopProNum', color: '#FF4757' },
{ label: '完工工程', key: 'completeProNum', color: '#2ED573' },
{ label: '遗留收尾', key: 'legacyProNum', color: '#2ED573' },
])
//
const projectCount = ref({
buildProNum: 0, //
prepareProNum: 0, //
stopProNum: 0, //
completeProNum: 0, //
legacyProNum: 0, //
})
//
const voltageLevel = ref([
{
label: '110kV',
value: '114',
percentage: 85,
color: 'linear-gradient(90deg, #2ED573 0%, #7BED9F 100%)',
},
{
label: '220kV',
value: '101',
percentage: 75,
color: 'linear-gradient(90deg, #FFA502 0%, #FFD32A 100%)',
},
{
label: '500kV',
value: '31',
percentage: 25,
color: 'linear-gradient(90deg, #2ED573 0%, #7BED9F 100%)',
},
])
const voltageLevel = ref([])
//
const radarOpts = reactive({
timing: 'easeOut',
duration: 1000,
rotate: false,
rotateLock: false,
color: ['#165DFF', '#9C88FF', '#FF6B6B', '#00D4AA', '#2ED573', '#FF8C00'],
padding: [20, 20, 0, 20],
fontSize: 13,
fontColor: '#333333',
dataLabel: true,
dataPointShape: true,
dataPointShapeType: 'solid',
touchMoveLimit: 60,
color: [
'#1890FF',
'#91CB74',
'#FAC858',
'#EE6666',
'#73C0DE',
'#3CA272',
'#FC8452',
'#9A60B4',
'#ea7ccc',
],
padding: [5, 5, 5, 5],
dataLabel: false,
enableScroll: false,
enableMarkLine: false,
legend: {
show: false,
position: 'right',
float: 'center',
padding: 10,
margin: 10,
backgroundColor: 'rgba(0,0,0,0)',
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
fontSize: 13,
fontColor: '#333333',
lineHeight: 20,
hiddenColor: '#CECECE',
itemGap: 15,
lineHeight: 25,
},
extra: {
radar: {
gridType: 'radar',
gridColor: '#E5E5E5',
gridCount: 4,
opacity: 0.3,
labelOffset: 8,
radarType: 'polygon',
radarGradient: true,
radarActiveBgColor: '#000000',
radarActiveBgOpacity: 0.05,
radarBorder: true,
radarBorderColor: '#E5E5E5',
radarBorderWidth: 1,
radarFillColor: 'rgba(22, 93, 255, 0.1)',
radarFillOpacity: 0.15,
radarPointCount: 6,
radarPointShape: 'circle',
radarPointShapeType: 'solid',
radarPointSize: 4,
radarPointColor: '#165DFF',
radarPointOpacity: 1,
radarLabelShow: true,
radarLabelOffset: 15,
radarLabelColor: '#333333',
radarLabelFontSize: 13,
radarLabelFontWeight: 'normal',
radarLabelRotate: false,
radarLabelRotateAngle: 0,
radarLabelPosition: 'outside',
radarLabelAlign: 'center',
radarLabelVerticalAlign: 'middle',
radarLabelLineHeight: 13,
radarLabelPadding: 8,
radarLabelBackgroundColor: 'rgba(255,255,255,0.8)',
radarLabelBackgroundOpacity: 0.8,
radarLabelBorderColor: 'rgba(0,0,0,0)',
radarLabelBorderWidth: 0,
radarLabelBorderRadius: 4,
radarLabelShadowColor: 'rgba(0,0,0,0)',
radarLabelShadowBlur: 0,
radarLabelShadowOffsetX: 0,
radarLabelShadowOffsetY: 0,
radarLabelShadowOpacity: 0,
radarLabelTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextShadowBlur: 0,
radarLabelTextShadowOffsetX: 0,
radarLabelTextShadowOffsetY: 0,
radarLabelTextShadowOpacity: 0,
radarLabelTextStrokeColor: 'rgba(0,0,0,0)',
radarLabelTextStrokeWidth: 0,
radarLabelTextStrokeOpacity: 0,
radarLabelTextFillColor: '#666666',
radarLabelTextFillOpacity: 1,
radarLabelTextAlign: 'center',
radarLabelTextVerticalAlign: 'middle',
radarLabelTextLineHeight: 12,
radarLabelTextPadding: 5,
radarLabelTextBackgroundColor: 'rgba(0,0,0,0)',
radarLabelTextBackgroundOpacity: 0,
radarLabelTextBorderColor: 'rgba(0,0,0,0)',
radarLabelTextBorderWidth: 0,
radarLabelTextBorderRadius: 0,
radarLabelTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextShadowBlur: 0,
radarLabelTextShadowOffsetX: 0,
radarLabelTextShadowOffsetY: 0,
radarLabelTextShadowOpacity: 0,
radarLabelTextTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextTextShadowBlur: 0,
radarLabelTextTextShadowOffsetX: 0,
radarLabelTextTextShadowOffsetY: 0,
radarLabelTextTextShadowOpacity: 0,
radarLabelTextTextStrokeColor: 'rgba(0,0,0,0)',
radarLabelTextTextStrokeWidth: 0,
radarLabelTextTextStrokeOpacity: 0,
radarLabelTextTextFillColor: '#666666',
radarLabelTextTextFillOpacity: 1,
radarLabelTextTextAlign: 'center',
radarLabelTextTextVerticalAlign: 'middle',
radarLabelTextTextLineHeight: 12,
radarLabelTextTextPadding: 5,
radarLabelTextTextBackgroundColor: 'rgba(0,0,0,0)',
radarLabelTextTextBackgroundOpacity: 0,
radarLabelTextTextBorderColor: 'rgba(0,0,0,0)',
radarLabelTextTextBorderWidth: 0,
radarLabelTextTextBorderRadius: 0,
radarLabelTextTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextTextShadowBlur: 0,
radarLabelTextTextShadowOffsetX: 0,
radarLabelTextTextShadowOffsetY: 0,
radarLabelTextTextShadowOpacity: 0,
radarLabelTextTextTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextTextTextShadowBlur: 0,
radarLabelTextTextTextShadowOffsetX: 0,
radarLabelTextTextTextShadowOffsetY: 0,
radarLabelTextTextTextShadowOpacity: 0,
radarLabelTextTextTextStrokeColor: 'rgba(0,0,0,0)',
radarLabelTextTextTextStrokeWidth: 0,
radarLabelTextTextTextStrokeOpacity: 0,
radarLabelTextTextTextFillColor: '#666666',
radarLabelTextTextTextFillOpacity: 1,
radarLabelTextTextTextAlign: 'center',
radarLabelTextTextTextVerticalAlign: 'middle',
radarLabelTextTextTextLineHeight: 12,
radarLabelTextTextTextPadding: 5,
radarLabelTextTextTextBackgroundColor: 'rgba(0,0,0,0)',
radarLabelTextTextTextBackgroundOpacity: 0,
radarLabelTextTextTextBorderColor: 'rgba(0,0,0,0)',
radarLabelTextTextTextBorderWidth: 0,
radarLabelTextTextTextBorderRadius: 0,
radarLabelTextTextTextShadowColor: 'rgba(0,0,0,0)',
radarLabelTextTextTextShadowBlur: 0,
radarLabelTextTextTextShadowOffsetX: 0,
radarLabelTextTextTextShadowOffsetY: 0,
radarLabelTextTextTextShadowOpacity: 0,
},
tooltip: {
showBox: true,
showArrow: true,
showCategory: false,
borderWidth: 0,
borderRadius: 0,
borderColor: '#000000',
borderOpacity: 0.7,
bgColor: '#000000',
bgOpacity: 0.7,
gridType: 'solid',
dashLength: 4,
gridColor: '#CCCCCC',
boxPadding: 3,
fontSize: 13,
lineHeight: 20,
fontColor: '#FFFFFF',
legendShow: true,
legendShape: 'auto',
splitLine: true,
horizentalLine: false,
xAxisLabel: false,
yAxisLabel: false,
labelBgColor: '#FFFFFF',
labelBgOpacity: 0.7,
labelFontColor: '#666666',
gridCount: 3,
opacity: 0.4,
max: 10,
labelShow: true,
border: true,
},
},
})
@ -281,27 +129,167 @@ const radarChartData = ref({})
//
const legendData = ref([
{ label: '基建线路', value: 86, color: '#165DFF' },
{ label: '基建变电', value: 87, color: '#9C88FF' },
{ label: '生产线路', value: 61, color: '#FF6B6B' },
{ label: '生产变电', value: 38, color: '#00D4AA' },
{ label: '配网', value: 1, color: '#2ED573' },
{ label: '其他', value: 2, color: '#FF8C00' },
// { label: '线', value: 86, color: '#165DFF' },
// { label: '', value: 87, color: '#9C88FF' },
// { label: '线', value: 61, color: '#FF6B6B' },
// { label: '', value: 38, color: '#00D4AA' },
// { label: '', value: 1, color: '#2ED573' },
// { label: '', value: 2, color: '#FF8C00' },
])
// HSL RGB
const hslToHex = (h, s, l) => {
s /= 100
l /= 100
const c = (1 - Math.abs(2 * l - 1)) * s
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
const m = l - c / 2
let r = 0,
g = 0,
b = 0
if (0 <= h && h < 60) {
r = c
g = x
b = 0
} else if (60 <= h && h < 120) {
r = x
g = c
b = 0
} else if (120 <= h && h < 180) {
r = 0
g = c
b = x
} else if (180 <= h && h < 240) {
r = 0
g = x
b = c
} else if (240 <= h && h < 300) {
r = x
g = 0
b = c
} else if (300 <= h && h < 360) {
r = c
g = 0
b = x
}
r = Math.round((r + m) * 255)
g = Math.round((g + m) * 255)
b = Math.round((b + m) * 255)
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}
//
const getRandomColor = () => {
// 使 HSL
// 0-360
// 60-90%
// 55-70%
const hue = Math.floor(Math.random() * 360) // 0-360
const saturation1 = Math.floor(Math.random() * 30) + 60 // 60-90%
const saturation2 = Math.floor(Math.random() * 30) + 60 // 60-90%
const lightness1 = Math.floor(Math.random() * 15) + 55 // 55-70%
const lightness2 = Math.floor(Math.random() * 15) + 55 // 55-70%
// 30
const hue2 = (hue + Math.floor(Math.random() * 30) - 15 + 360) % 360
const color1 = hslToHex(hue, saturation1, lightness1)
const color2 = hslToHex(hue2, saturation2, lightness2)
return `linear-gradient(90deg, ${color1} 0%, ${color2} 100%)`
}
//
const getRandomColor2 = () => {
// 使 HSL
// 0-360
// 50-100%
// 40-75%
const hue = Math.floor(Math.random() * 360) // 0-360
const saturation = Math.floor(Math.random() * 50) + 50 // 50-100%
const lightness = Math.floor(Math.random() * 35) + 40 // 40-75% 100%
return hslToHex(hue, saturation, lightness)
}
//
const getHomeIndexProjectMsg = async () => {
const { data: res } = await getHomeIndexProjectMsgAPI({ subComId: '' })
const {
buildProNum,
prepareProNum,
stopProNum,
completeProNum,
legacyProNum,
proByProStatus,
proByVolLevel,
} = res
projectCount.value = {
buildProNum,
prepareProNum,
stopProNum,
completeProNum,
legacyProNum,
}
if (proByVolLevel && proByVolLevel.length > 0) {
proByVolLevel.forEach((item) => {
voltageLevel.value.push({
label: item.key,
value: item.value,
percentage: item.value,
color: getRandomColor(),
})
})
}
let xData = []
let yData = []
if (proByProStatus && proByProStatus.length > 0) {
xData = proByProStatus.map((item) => item.key)
yData = proByProStatus.map((item) => item.value)
legendData.value = proByProStatus.map((item) => ({
label: item.key,
value: item.value,
color: getRandomColor2(),
}))
radarOpts.extra.radar.max = Math.max(...yData) + 3
}
radarChartData.value = {
categories: xData,
series: [
{
name: '工程类型',
data: yData,
},
],
}
}
onMounted(() => {
setTimeout(() => {
const data = {
categories: ['基建线路', '基建变电', '生产线路', '生产变电', '配网', '其他'],
series: [
{
name: '工程类型',
data: [86, 87, 61, 38, 1, 2],
},
],
}
radarChartData.value = JSON.parse(JSON.stringify(data))
}, 500)
getHomeIndexProjectMsg()
// setTimeout(() => {
// const data = {
// categories: ['线', '', '线', '', '', ''],
// series: [
// {
// name: '',
// data: [86, 87, 61, 38, 1, 2],
// },
// ],
// }
// radarChartData.value = JSON.parse(JSON.stringify(data))
// }, 500)
})
//

View File

@ -18,32 +18,21 @@
<view class="project-info">
<view class="info-left">
<view class="icon-container">
<view class="project-icon">
<text class="icon-text">项目信息</text>
</view>
<up-image width="68" height="68" :src="ProjectIcon" />
<text class="icon-text">项目信息</text>
</view>
</view>
<view class="info-right">
<view class="metric-item">
<text class="metric-label">在建工程</text>
<view
class="metric-item"
:key="item.title"
v-for="item in swiperList_1"
>
<text class="metric-label">{{ item.title }}</text>
<text class="metric-value">{{
item.data.constructionProjects
swiperInfo_1[item.key]
}}</text>
</view>
<view class="metric-item">
<text class="metric-label">投入分包</text>
<text class="metric-value">{{
item.data.subcontractors
}}</text>
</view>
<view class="metric-item">
<text class="metric-label">在用班组</text>
<text class="metric-value">{{ item.data.teams }}</text>
</view>
<view class="metric-item">
<text class="metric-label">在场人员</text>
<text class="metric-value">{{ item.data.personnel }}</text>
</view>
</view>
</view>
</template>
@ -54,25 +43,31 @@
<view class="stat-card">
<view class="stat-icon">
<view class="hexagon-icon">
<u-icon name="account" size="24" color="#fff"></u-icon>
<up-image width="48" height="48" :src="PersonnelIcon" />
</view>
</view>
<view class="stat-content">
<text class="stat-label">在场人数</text>
<text class="stat-value">{{ item.data.onSiteCount }}</text>
<view>
<text class="stat-label">在场人员</text>
<text class="stat-value">
{{ einNum }}
</text>
</view>
</view>
</view>
<view class="stat-card">
<view class="stat-icon">
<view class="hexagon-icon">
<u-icon name="clock" size="24" color="#fff"></u-icon>
<up-image
width="48"
height="48"
:src="AttendanceRateIcon"
/>
</view>
</view>
<view class="stat-content">
<text class="stat-label">今日考勤</text>
<text class="stat-value">{{
item.data.todayAttendance
}}</text>
<text class="stat-label">今日打卡 </text>
<text class="stat-value">{{ attNum }}</text>
</view>
</view>
</view>
@ -84,31 +79,39 @@
<view class="rate-card">
<view class="stat-icon">
<view class="hexagon-icon">
<u-icon size="24" color="#fff"></u-icon>
<up-image
width="32"
height="32"
:src="AttendanceRateIcon"
/>
</view>
</view>
<view class="rate-content">
<text class="rate-label">考勤率</text>
<text class="rate-value"
>{{ item.data.attendanceRate }}%</text
>
<text class="rate-label">出场未结算</text>
<text class="rate-value">
{{ exitNoFileNum }}
</text>
</view>
</view>
<view class="rate-card">
<view class="stat-icon">
<view class="hexagon-icon">
<u-icon name="account" size="24" color="#fff"></u-icon>
<up-image
width="32"
height="32"
:src="GreenLightIcon"
/>
</view>
</view>
<view class="rate-content">
<text class="rate-label">红绿灯人员</text>
<view class="traffic-lights">
<text class="light-item green"
>绿灯: {{ item.data.greenLight }}</text
>
<text class="light-item yellow"
>黄灯: {{ item.data.yellowLight }}</text
>
<text class="light-item green">
黄灯人数: {{ yellowNum }}
</text>
<text class="light-item yellow">
黄灯7天: {{ yellowThanSevenDayNum }}
</text>
</view>
</view>
</view>
@ -123,7 +126,65 @@
<script setup>
import { ref, reactive } from 'vue'
import ProjectIcon from '@/static/image/home/project_info.png'
import PersonnelIcon from '@/static/image/home/person_count.png'
import AttendanceRateIcon from '@/static/image/home/att_ratio.png'
import GreenLightIcon from '@/static/image/home/red_green.png'
import { getHomeIndexSwiperAPI } from '@/services/home-index'
const einNum = ref(0)
const attNum = ref(0)
const yellowNum = ref(0)
const exitNoFileNum = ref(0)
const yellowThanSevenDayNum = ref(0)
// 1
const swiperList_1 = ref([
{ title: '总工程', key: 'mainProNum' },
{ title: '标段工程', key: 'proNum' },
{ title: '在用分包单位', key: 'subNum' },
{ title: '在用班组', key: 'teamNum' },
])
const swiperInfo_1 = ref({
mainProNum: 0,
proNum: 0,
subNum: 0,
teamNum: 0,
})
const emit = defineEmits(['setNoticeListData', 'setProjectInfoViewData'])
//
const getHomeIndexSwiper = async () => {
const { data: res } = await getHomeIndexSwiperAPI({ subComId: '' })
swiperInfo_1.value.proNum = res?.proNum
swiperInfo_1.value.subNum = res?.subNum
swiperInfo_1.value.teamNum = res?.teamNum
swiperInfo_1.value.mainProNum = res?.mainProNum
einNum.value = res?.einNum
attNum.value = res?.attNum
yellowNum.value = res?.yellowNum
exitNoFileNum.value = res?.exitNoFileNum
yellowThanSevenDayNum.value = res?.yellowThanSevenDayNum
emit('setNoticeListData', {
yellowNum: yellowNum.value,
exitNoFileNum: exitNoFileNum.value,
yellowThanSevenDayNum: yellowThanSevenDayNum.value,
})
emit('setProjectInfoViewData', {
mainProNum: swiperInfo_1.value.mainProNum,
proNum: swiperInfo_1.value.proNum,
subNum: swiperInfo_1.value.subNum,
teamNum: swiperInfo_1.value.teamNum,
attNum: attNum.value,
einNum: einNum.value,
})
}
getHomeIndexSwiper()
//
const swiperList = ref([
{
@ -136,14 +197,14 @@ const swiperList = ref([
},
},
{
background: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
background: 'linear-gradient(180deg, #33FFCC 0%, #06A5CC 100%)', //
data: {
onSiteCount: 6500,
todayAttendance: 3500,
},
},
{
background: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
background: 'linear-gradient(180deg, #31C8FD 0%, #1C98E8 100%)',
data: {
attendanceRate: 64.68,
greenLight: 20000,
@ -168,7 +229,7 @@ const swiperList = ref([
}
.swiper-content {
padding: 30rpx;
/* padding: 30rpx; */
height: 100%;
display: flex;
align-items: center;
@ -179,6 +240,7 @@ const swiperList = ref([
.project-info {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
@ -187,14 +249,12 @@ const swiperList = ref([
}
.icon-container {
width: 100rpx;
height: 100rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
backdrop-filter: blur(10rpx);
gap: 10rpx;
margin-right: 20rpx;
}
.project-icon {
@ -208,13 +268,12 @@ const swiperList = ref([
}
.icon-text {
font-size: 20rpx;
font-size: 24rpx;
color: #fff;
font-weight: bold;
font-family: 'PingFang SC';
}
.info-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 15rpx;
@ -227,13 +286,14 @@ const swiperList = ref([
}
.metric-label {
font-size: 28rpx;
margin: 0 12rpx;
font-size: 26rpx;
color: #fff;
font-weight: 500;
}
.metric-value {
font-size: 32rpx;
font-size: 24rpx;
color: #fff;
font-weight: bold;
}
@ -241,49 +301,36 @@ const swiperList = ref([
/* 人员统计样式 */
.personnel-stats {
display: flex;
gap: 40rpx;
gap: 5rpx;
width: 100%;
flex-direction: column;
justify-content: center;
}
.stat-card {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
background: rgba(255, 255, 255, 0.15);
/* background: rgba(255, 255, 255, 0.15); */
padding: 20rpx;
border-radius: 15rpx;
backdrop-filter: blur(10rpx);
}
.stat-icon {
flex: 0 0 60rpx;
}
.hexagon-icon {
width: 50rpx;
height: 50rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10rpx);
}
.stat-content {
flex: 1;
margin-left: 6%;
}
.stat-label {
display: block;
font-size: 24rpx;
color: #fff;
margin-bottom: 8rpx;
margin-right: 10rpx;
}
.stat-value {
font-size: 36rpx;
font-size: 32rpx;
color: #fff;
font-weight: bold;
}
@ -335,10 +382,10 @@ const swiperList = ref([
}
.light-item.green {
color: #4ade80;
color: #f89817;
}
.light-item.yellow {
color: #facc15;
color: #f81115;
}
</style>

View File

@ -1,20 +1,54 @@
<template>
<view class="home-page">
<!-- 首页 -->
<SwiperModel />
<NoticeModel />
<DataModel />
<SwiperModel
@setNoticeListData="setNoticeListData"
@setProjectInfoViewData="setProjectInfoViewData"
/>
<NoticeModel :noticeListData="noticeListData" />
<DataModel :projectInfoViewData="projectInfoViewData" />
<ProjectModel />
<PersonModel />
</view>
</template>
<script setup name="index">
import { ref } from 'vue'
import SwiperModel from './components/swiperModel.vue'
import NoticeModel from './components/noticeModel.vue'
import DataModel from './components/dataModel.vue'
import ProjectModel from './components/projectModel.vue'
import PersonModel from './components/personModel.vue'
const noticeListData = ref({
yellowNum: 0,
yellowThanSevenDayNum: 0,
exitNoFileNum: 0,
})
const projectInfoViewData = ref({
mainProNum: 0,
proNum: 0,
subNum: 0,
teamNum: 0,
attNum: 0,
einNum: 0,
})
/**
* 因为轮播图模块已经通过接口获取了预警信息数据和数据概览数据为了减少重复请求所以将数据传递给子组件
*/
//
const setNoticeListData = (data) => {
noticeListData.value = data
}
//
const setProjectInfoViewData = (data) => {
projectInfoViewData.value = data
}
</script>
<style scoped>

View File

@ -14,7 +14,7 @@
<text>{{ userInfo.nickName || '未登录' }}</text>
<text class="my-info-name-job">{{ userInfo?.roleName || '无' }}</text>
</view>
<view style="color: #999"> {{ userInfo?.phonenumber || '未登录' }} </view>
<view style="color: #999"> {{ userInfo?.phonenumber || '' }} </view>
<view style="color: #999"> {{ activeProjectName || '' }} </view>
</view>

View File

@ -0,0 +1,28 @@
import { http } from '@/utils/http'
// 首页接口 ---- 获取轮播图
export const getHomeIndexSwiperAPI = (data = {}) => {
return http({
method: 'GET',
url: '/bmw/homePage/getDataOverview',
data,
})
}
// 首页接口 ---- 获取工程数量信息
export const getHomeIndexProjectMsgAPI = (data = {}) => {
return http({
method: 'GET',
url: '/bmw/homePage/getProjectMsg',
data,
})
}
// 首页接口 ---- 获取人员概况信息
export const getHomeIndexPersonMsgAPI = (data = {}) => {
return http({
method: 'GET',
url: '/bmw/homePage/getEinWorkerDistribution',
data,
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -13,3 +13,63 @@ export function formatCustomNumber(num) {
return `${formattedRest},${lastTwo}`
}
// HSL 转 RGB 十六进制颜色(共享函数)
const hslToHex = (h, s, l) => {
s /= 100
l /= 100
const c = (1 - Math.abs(2 * l - 1)) * s
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
const m = l - c / 2
let r = 0,
g = 0,
b = 0
if (0 <= h && h < 60) {
r = c
g = x
b = 0
} else if (60 <= h && h < 120) {
r = x
g = c
b = 0
} else if (120 <= h && h < 180) {
r = 0
g = c
b = x
} else if (180 <= h && h < 240) {
r = 0
g = x
b = c
} else if (240 <= h && h < 300) {
r = x
g = 0
b = c
} else if (300 <= h && h < 360) {
r = c
g = 0
b = x
}
r = Math.round((r + m) * 255)
g = Math.round((g + m) * 255)
b = Math.round((b + m) * 255)
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}
// 获取随机颜色 // 获取随机颜色(避免白色,生成好看的颜色)
export const getRandomColor = () => {
// 使用 HSL 颜色空间,避免生成白色或接近白色的颜色
// 色相0-360全色相范围
// 饱和度50-100%(确保有足够的色彩,避免灰色)
// 亮度40-75%(避免太深和太浅,确保不是白色)
const hue = Math.floor(Math.random() * 360) // 0-360 度色相
const saturation = Math.floor(Math.random() * 50) + 50 // 50-100% 饱和度(避免低饱和度的灰色)
const lightness = Math.floor(Math.random() * 35) + 40 // 40-75% 亮度避免太深和太浅特别是避免接近100%的白色)
return hslToHex(hue, saturation, lightness)
}