首页一级页面接口调试完成
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
||||
// 处理图表点击
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||