Commit 0706a370 by guibin

任原设备利用率设备添加排序

parent d67f5b13
......@@ -23,6 +23,14 @@ class RokeMesThreeColourLight(http.Controller):
template = env.get_template('equipment_status_qdry.html')
return template.render(data)
@http.route("/roke/three_color_light/device_analysis", type="http", auth='none', cors='*', csrf=False)
def device_analysis(self, **kwargs):
_self = http.request
factory_code = http.request.env(user=SUPERUSER_ID)['ir.config_parameter'].get_param('database.uuid', default="")
data = {"code": 1, "message": "请求通过", "data": {"factory_code": factory_code}}
template = env.get_template('oee_analysis.html')
return template.render(data)
@http.route('/roke/workstation/plant/tree', type='json', auth='none', csrf=False, cors="*")
def get_roke_workstation_plant(self):
_self = http.request
......
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>OEE时间利用率分析</title>
<meta content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=0" name="viewport" />
<!-- /roke_workstation_api/static/html/routing -->
<link rel="stylesheet" href="/roke_workstation_api/static/html/routing/element-ui/index.css" />
<script src="/roke_workstation_api/static/html/routing/js/echarts.min.js"></script>
<script src="/roke_workstation_api/static/html/routing/js/moment.min.js"></script>
<script src="/roke_workstation_api/static/html/routing/js/vue.js"></script>
<script src="/roke_workstation_api/static/html/routing/js/axios.min.js"></script>
<script src="/roke_workstation_api/static/html/routing/element-ui/index.js"></script>
</head>
<body id="bodyId" style="display: none">
<div id="app" v-loading.body.fullscreen.lock="loading">
<!-- 页面标题 -->
<div class="dashboard-header">
<div class="header-content">
<img src="/roke_workstation_api/static/html/routing/image/header_bg.png" class="header-bg"
alt="header background" />
<span class="header-text">OEE时间利用率分析</span>
</div>
</div>
<!-- 替换原有的产线选择器 -->
<div class="floating-selector" id="floatingSelector">
<div class="floating-button" @click="showSelectionPanel">
<i class="el-icon-menu"></i>
</div>
<!-- 选择面板 -->
<div class="selection-panel" v-show="selectionPanelVisible">
<div class="panel-header">
<div class="back-button" v-if="selectionStep === 'line'" @click="selectionStep = 'workshop'">
<i class="el-icon-arrow-left"></i>
</div>
<div class="panel-title">
[[ selectionStep === 'workshop' ? '车间名称' : '产线名称' ]]
</div>
<div class="close-button" @click="selectionPanelVisible = false">
<i class="el-icon-close"></i>
</div>
</div>
<!-- 添加当前选择信息 -->
<div class="current-selection" v-if="selectedWorkshop || selectedLine">
<div class="selection-info" v-if="selectedWorkshop">
<span class="info-label">当前车间:</span>
<span class="info-value">[[ selectedWorkshop.name ]]</span>
</div>
<div class="selection-info" v-if="selectedLine">
<span class="info-label">当前产线:</span>
<span class="info-value">[[ getCurrentLineName() ]]</span>
</div>
</div>
<div class="panel-content">
<!-- 车间选择 -->
<div class="selection-list" v-if="selectionStep === 'workshop'">
<div class="selection-item" v-for="workshop in workshops" :key="workshop.id"
@click="selectWorkshop(workshop)"
:class="{'selected': selectedWorkshop && selectedWorkshop.id === workshop.id}">
[[ workshop.name ]]
<i class="el-icon-arrow-right"></i>
</div>
</div>
<!-- 产线选择 -->
<div class="selection-list" v-else>
<div class="selection-item" v-for="line in filteredProductionLines" :key="line.id"
@click="selectProductionLine(line)" :class="{'selected': selectedLine === line.id}">
[[ line.name ]]
</div>
</div>
</div>
</div>
</div>
<!-- 图表网格布局 -->
<div class="chart-grid">
<!-- 当前机台开机实时状态 -->
<div class="chart-card">
<div class="card-header">
<h3>当前机台开机实时状态</h3>
<span class="current-date">[[currentDate]]</span>
</div>
<div id="statusPieChart" class="chart-container"></div>
</div>
<!-- 设备OEE排名 -->
<div class="chart-card">
<div class="card-header">
<h3>设备OEE时间利用率TOP5</h3>
<el-select v-model="timeRanges.oeeRank" size="small" @change="updateChart('oeeRank')">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div id="oeeRankChart" class="chart-container"></div>
</div>
<!-- 设备效率等待损失量 -->
<div class="chart-card">
<div class="card-header">
<h3>设备效率等待损失量(分钟)</h3>
<el-select v-model="timeRanges.waitingLoss" size="small" @change="updateChart('waitingLoss')">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div id="waitingLossChart" class="chart-container"></div>
</div>
<!-- 设备OEE综合趋势 -->
<div class="chart-card">
<div class="card-header">
<h3>设备OEE时间利用率综合趋势</h3>
<div class="header-controls">
<el-select v-model="selectedDevice" size="small" placeholder="选择设备" @change="updateChart('oeeTrend')"
style="margin-right: 10px">
<el-option v-for="device in devices" :key="device.id" :label="device.name" :value="device.id">
</el-option>
</el-select>
<el-select v-model="timeRanges.oeeTrend" size="small" @change="updateChart('oeeTrend')">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<span class="unit-label">单位:%</span>
</div>
</div>
<div id="oeeTrendChart" class="chart-container"></div>
</div>
<!-- 设备效率故障损失量 -->
<div class="chart-card">
<div class="card-header">
<h3>设备效率故障损失量(分钟)</h3>
<el-select v-model="timeRanges.faultLoss" size="small" @change="updateChart('faultLoss')">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
<div id="faultLossChart" class="chart-container"></div>
</div>
</div>
</div>
</body>
<script>
// 发送消息给父页面(关闭odoo的菜单弹窗)
document.addEventListener("click", () => {
window.parent.postMessage("hidePopover", "*");
});
let vue = new Vue({
el: "#app",
delimiters: ["[[", "]]"],
data() {
return {
loading: false,
currentDate: moment().format("YYYY/MM/DD"),
// dwsURL: "https://workstation.rokeris.com", // 基地址
dwsURL: "", // 基地址
baseURL: "https://dws-platform.xbg.rokeris.com/dev-api", // 基地址
factory_code: "", // 工厂编码
// 产线数据
selectedLine: null,
productionLines: [], // 将从接口获取
// 设备数据
selectedDevice: null,
devices: [],
// 图表实例
charts: {
statusPie: null,
oeeRank: null,
waitingLoss: null,
oeeTrend: null,
faultLoss: null,
},
// 时间范围选择
timeRanges: {
oeeRank: "today",
waitingLoss: "today",
faultLoss: "today",
oeeTrend: "today",
},
timeOptions: [
{ value: "today", label: "今天" },
{ value: "week", label: "本周" },
{ value: "month", label: "本月" },
{ value: "lastMonth", label: "上月" },
],
// 添加接口数据存储
apiData: {
deviceStates: [],
planTimeData: null,
plantTree: [], // 存储车间和产线树形数据
allEquipmentData: [], // 存储所有已绑定设备数据
currentLineEquipment: [], // 存储当前产线的设备数据
waitingLossData: [], // 存储等待损失数据
faultLossData: [], // 存储故障损失数据
oeeTrendData: [], // 存储OEE趋势数据
oeeRankData: [], // 存储OEE排名数据
},
// 浮动选择器相关
selectionPanelVisible: false,
selectionStep: "workshop", // 'workshop' 或 'line'
selectedWorkshop: null,
workshops: [], // 将从接口获取
};
},
created() {
if (this.getUrlSearch("factory_code")) {
this.factory_code = this.getUrlSearch("factory_code"); //截取url后面的参数
}
},
computed: {
filteredProductionLines() {
if (!this.selectedWorkshop) return [];
// 从apiData.plantTree中筛选当前选中车间的产线
const workshop = this.apiData.plantTree.find((w) => w.id === this.selectedWorkshop.id);
return workshop ? workshop.children || [] : [];
},
},
mounted() {
console.log("组件挂载,准备初始化...");
this.init(); // 直接调用init方法
this.$nextTick(() => {
// 这些初始化操作可能会与init方法中的操作重复,可以考虑移除
document.getElementById("bodyId").style.display = "block";
this.charts.statusPie = this.initStatusPieChart();
this.charts.oeeRank = this.initOEERankChart();
this.charts.waitingLoss = this.initWaitingLossChart();
this.charts.oeeTrend = this.initOEETrendChart();
this.charts.faultLoss = this.initFaultLossChart();
// 直接更新图表,不需要先获取设备列表
this.updateAllCharts();
// 设置默认选中的车间和产线
if (this.productionLines.length > 0) {
this.selectedLine = this.productionLines[0].id;
const workshopId = this.productionLines[0].workshopId;
this.selectedWorkshop = this.workshops.find((w) => w.id === workshopId);
}
// 初始化拖拽功能
this.initDraggable();
// 添加窗口大小变化监听
window.addEventListener("resize", this.handleResize);
// 初始化完成后触发一次resize
this.handleResize();
});
// 添加延迟更新,确保图表能正确显示
setTimeout(() => {
console.log("延迟更新所有图表...");
if (this.apiData.faultLossData && this.apiData.faultLossData.length > 0) {
console.log("故障损失数据存在,重新更新故障损失图表");
this.updateFaultLossChart();
}
if (this.apiData.waitingLossData && this.apiData.waitingLossData.length > 0) {
console.log("等待损失数据存在,重新更新等待损失图表");
this.updateWaitingLossChart();
}
}, 1000);
},
methods: {
// 通过网址跳转过来的页面,截取后面的参数
getUrlSearch(name) {
// 未传参,返回空
if (!name) return "";
// 查询参数:先通过search取值,如果取不到就通过hash来取
var after = window.location.search;
after = after.substr(1) || window.location.hash.split("?")[1];
// 地址栏URL没有查询参数,返回空
if (!after) return null;
// 如果查询参数中没有"name",返回空
if (after.indexOf(name) === -1) return null;
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
// 当地址栏参数存在中文时,需要解码,不然会乱码
var r = decodeURI(after).match(reg);
// 如果url中"name"没有值,返回空
if (!r) return null;
return r[2];
},
// 初始化
async init() {
this.loading = true;
try {
// console.log("开始初始化...");
// 初始化图表
this.charts.statusPie = this.initStatusPieChart();
this.charts.oeeRank = this.initOEERankChart();
this.charts.waitingLoss = this.initWaitingLossChart();
this.charts.faultLoss = this.initFaultLossChart();
this.charts.oeeTrend = this.initOEETrendChart();
// 初始化时显示"暂无数据"提示
this.updateStatusPieChart(true);
this.updateOEERankChart(true);
this.updateWaitingLossChart(true);
this.updateFaultLossChart(true);
this.updateOEETrendChart(true);
// 并行请求三个初始化数据
// console.log("开始并行请求车间产线、设备计划时间和设备列表数据...");
const [plantTreeResult, devicePlanTimeResult, allEquipmentDataResult] =
await Promise.all([
this.fetchPlantTree(),
this.getDevicePlanTime(),
this.getAllEquipmentData(),
]);
// console.log("三个初始化数据请求已完成");
// 默认选择第一个车间和产线
if (this.apiData.plantTree.length > 0) {
this.selectedWorkshop = this.apiData.plantTree[0];
// console.log("默认选择车间:", this.selectedWorkshop.name);
if (this.filteredProductionLines.length > 0) {
this.selectedLine = this.filteredProductionLines[0].id;
// console.log("默认选择产线:", this.filteredProductionLines[0].name);
// 处理产线选择,获取设备状态数据
await this.handleLineChange();
} else {
// console.warn("没有可用的产线");
// 获取设备状态数据
await this.getInitialDeviceStates();
}
} else {
// console.warn("没有可用的车间");
// 获取设备状态数据
await this.getInitialDeviceStates();
}
// 初始化拖拽功能
this.initDraggable();
// 添加窗口大小变化监听
window.addEventListener("resize", this.handleResize);
// 初始化完成后触发一次resize
this.handleResize();
} catch (error) {
console.error("初始化失败:", error);
this.$message.error("初始化失败,请刷新页面重试");
} finally {
this.loading = false;
}
},
// 获取车间和产线树形数据
async fetchPlantTree() {
try {
console.log("发起请求: POST " + this.dwsURL + "/roke/workstation/plant/tree");
const response = await axios({
method: "POST",
url: this.dwsURL + "/roke/workstation/plant/tree",
data: { no_icon: true },
headers: {
"Content-Type": "application/json",
"x-session-id": "1d8a662231e9bbab315c736b7551657c18b44366",
},
});
console.log("接口返回数据:", JSON.stringify(response.data));
// 处理jsonrpc格式的返回数据
if (response.data && response.data.jsonrpc === "2.0" && response.data.result) {
const result = response.data.result;
if (result.code === 0) {
// 处理返回的树形数据
this.apiData.plantTree = this.processPlantTreeData(result.data || []);
console.log("处理后的树形数据:", JSON.stringify(this.apiData.plantTree));
// 提取车间数据
this.workshops = this.apiData.plantTree.map((item) => ({
id: item.id,
name: item.name,
}));
console.log("提取的车间数据:", JSON.stringify(this.workshops));
} else {
console.error("获取车间和产线数据失败:", result.message || "未知错误");
// 使用默认数据
this.useDefaultPlantData();
}
} else {
console.error("获取车间和产线数据失败: 返回格式不正确");
// 使用默认数据
this.useDefaultPlantData();
}
} catch (error) {
console.error("获取车间和产线数据异常:", error);
// 使用默认数据
this.useDefaultPlantData();
}
},
// 处理树形数据,适配我们的数据结构
processPlantTreeData(data) {
// 处理从接口返回的真实数据
const plantTree = [];
// 遍历车间数据
data.forEach((plant) => {
if (plant.type === "plant") {
const workshops = [];
// 遍历该车间下的产线
if (plant.workshops && plant.workshops.length > 0) {
plant.workshops.forEach((workshop) => {
if (workshop.type === "workshop") {
workshops.push({
id: workshop.id,
name: workshop.name,
workshopId: plant.id,
centerCount: workshop.center_count || 0,
});
}
});
}
plantTree.push({
id: plant.id,
name: plant.name,
children: workshops,
});
}
});
return plantTree;
},
// 使用默认数据(当接口请求失败时)
useDefaultPlantData() {
console.log("使用空数据代替默认数据");
// 使用空数据
this.workshops = [];
this.apiData.plantTree = [];
},
// 获取所有已绑定设备数据
async getAllEquipmentData() {
try {
// 发送请求获取所有已绑定设备数据
const response = await axios({
method: "post",
url: this.dwsURL + "/roke/workstation/equipment/get_equipment_data",
data: {},
headers: {
"Content-Type": "application/json",
"x-session-id": "1d8a662231e9bbab315c736b7551657c18b44366",
},
});
// 处理JSON-RPC格式的响应
if (response.data && response.data.result && response.data.result.code === 0) {
// 获取所有设备数据
const equipmentData = response.data.result.data || [];
// 存储所有设备数据,供后续使用
this.apiData.allEquipmentData = equipmentData;
console.log("所有已绑定设备数据:", JSON.stringify(equipmentData));
return equipmentData;
} else {
const errorMsg = response.data.result
? response.data.result.message
: "获取所有已绑定设备数据失败";
console.error(errorMsg);
}
} catch (error) {
console.error("获取所有已绑定设备数据出错:", error);
}
},
// 根据产线ID筛选设备
filterEquipmentByLine(lineId) {
console.log("筛选产线设备,产线ID:", lineId);
if (!lineId || !this.apiData.allEquipmentData) {
console.warn("产线ID或设备数据为空,无法筛选");
return [];
}
// 从所有已绑定设备数据中筛选出当前产线的设备
// 注意:根据数据结构,应该使用work_center_id来匹配产线ID
const lineEquipment = this.apiData.allEquipmentData.filter(
(device) => device.work_center_id == lineId
);
console.log(
`筛选到${lineEquipment.length}个设备,产线ID: ${lineId},设备数据:`,
JSON.stringify(lineEquipment)
);
// 如果没有筛选到设备,可能是因为产线ID实际上是workshop_id,尝试用workshop_id筛选
if (lineEquipment.length === 0) {
console.log("使用workshop_id尝试筛选设备");
const workshopEquipment = this.apiData.allEquipmentData.filter(
(device) => device.workshop_id == lineId
);
console.log(`使用workshop_id筛选到${workshopEquipment.length}个设备`);
return workshopEquipment;
}
return lineEquipment;
},
// 处理产线改变
async handleLineChange() {
this.loading = true;
try {
console.log("产线改变,新产线ID:", this.selectedLine);
// 直接从已有数据中筛选当前产线的设备数据
const lineEquipment = this.filterEquipmentByLine(this.selectedLine);
console.log("当前产线设备数据:", JSON.stringify(lineEquipment));
// 存储当前产线的设备数据
this.apiData.currentLineEquipment = lineEquipment;
// 更新设备选择器的设备列表
this.devices = lineEquipment.map((device) => ({
id: device.code,
name: device.name || device.code,
}));
console.log("更新后的设备列表:", JSON.stringify(this.devices));
// 如果有设备,默认选择第一个设备
if (this.devices.length > 0) {
this.selectedDevice = this.devices[0].id;
console.log("默认选择设备:", this.selectedDevice);
} else {
this.selectedDevice = "";
console.warn("当前产线下没有设备");
}
// 准备设备编号列表和计划时间数据
// 使用当前产线的设备编码,而不是所有设备
const deviceList = [];
// 从当前产线的设备数据中获取设备编码
if (this.apiData.currentLineEquipment && this.apiData.currentLineEquipment.length > 0) {
this.apiData.currentLineEquipment.forEach((device) => {
if (device.code) {
deviceList.push(device.code);
}
});
}
console.log("当前产线设备编码:", deviceList);
// 获取计划时间数据
const planTimeList = this.apiData.planTimeData || {};
// 检查参数
if (deviceList.length === 0) {
console.warn("当前产线没有可用的设备编码,无法获取设备状态");
this.apiData.deviceStates = [];
this.updateStatusPieChart(true);
return;
}
console.log("最终参数:", {
planTimeList,
deviceList,
});
// 获取设备状态数据
await this.getDeviceStateList(planTimeList, deviceList);
// 获取OEE排名数据
const { startDate, endDate } = this.getDateRange(this.timeRanges.oeeRank);
await this.getOEERankData(startDate, endDate);
// 获取等待损失数据
const waitingLossDates = this.getDateRange(this.timeRanges.waitingLoss);
await this.getLossData(waitingLossDates.startDate, waitingLossDates.endDate, "waiting");
// 获取故障损失数据
const faultLossDates = this.getDateRange(this.timeRanges.faultLoss);
await this.getLossData(faultLossDates.startDate, faultLossDates.endDate, "fault");
// 获取OEE趋势数据
if (this.selectedDevice) {
const oeeTrendDates = this.getDateRange(this.timeRanges.oeeTrend);
await this.getOEETrendData(
this.selectedDevice,
oeeTrendDates.startDate,
oeeTrendDates.endDate
);
}
// 更新所有图表
console.log("所有数据加载完成,开始更新图表");
this.updateAllCharts();
} catch (error) {
console.error("切换产线失败:", error);
this.$message.error("切换产线失败");
} finally {
this.loading = false;
}
},
// 获取设备状态列表
async getDeviceStateList(planTimeList, deviceList) {
try {
console.log("获取设备状态列表, 原始参数:", {
planTimeList,
deviceList,
});
// 检查参数是否有效
if (!deviceList || deviceList.length === 0) {
console.error("设备列表为空,无法获取设备状态");
this.apiData.deviceStates = [];
this.updateStatusPieChart();
return;
}
// 使用CORS代理
// 发送请求获取设备状态
const response = await axios({
method: "POST",
url: this.baseURL + "/public/device_efficiency/device_state_list",
data: {
factory_code: this.factory_code || "f1991dbb-52d6-11ef-b13f-fa163e5bb392", // 使用工厂代码
plan_time_list: planTimeList, // 直接使用API返回的计划时间数据
device_list: deviceList, // 真实的设备编码列表
},
headers: {
"Content-Type": "application/json",
},
});
console.log("设备状态接口返回:", JSON.stringify(response.data));
// 处理响应
if (response.data && response.data.code === 200) {
// 处理设备状态数据
const deviceStates = response.data.data || [];
// 确保即使返回空数组也能正确处理
this.apiData.deviceStates = deviceStates.length > 0 ? deviceStates : [];
console.log("设备状态数据:", JSON.stringify(this.apiData.deviceStates));
console.log("设备状态数据长度:", this.apiData.deviceStates.length);
// 更新状态饼图
this.updateStatusPieChart(this.apiData.deviceStates.length === 0);
return this.apiData.deviceStates;
} else {
const errorMsg = response.data.msg || "获取设备状态失败";
console.error(errorMsg);
this.$message.error(errorMsg);
this.apiData.deviceStates = [];
this.updateStatusPieChart(true); // 明确传入true表示没有数据
}
} catch (error) {
console.error("获取设备状态出错:", error);
this.$message.error("获取设备状态失败,请稍后重试");
// 不使用假数据,而是显示错误状态
this.apiData.deviceStates = [];
this.updateStatusPieChart(true); // 明确传入true表示没有数据
}
},
// 更新状态饼图
updateStatusPieChart(isEmpty = false) {
if (!this.charts.statusPie) {
console.error("状态饼图未初始化,无法更新");
return;
}
console.log("开始更新状态饼图...");
console.log("设备状态数据:", JSON.stringify(this.apiData.deviceStates));
// 处理设备状态数据
const statusCounts = {
running: 0,
offline: 0,
waiting: 0,
maintenance: 0,
};
if (!isEmpty && this.apiData.deviceStates && this.apiData.deviceStates.length > 0) {
this.apiData.deviceStates.forEach((device) => {
// 使用state字段映射到我们的状态类别
let status = "offline"; // 默认为离线
if (device.state) {
if (device.state === "green") {
status = "running";
} else if (device.state === "yellow") {
status = "waiting";
} else if (device.state === "red") {
status = "maintenance";
} else if (device.state === "gray") {
status = "offline";
}
}
statusCounts[status]++;
});
}
const total = Object.values(statusCounts).reduce((sum, count) => sum + count, 0);
console.log("状态计数:", statusCounts, "总数:", total);
// 完全重新设置图表选项,而不是部分更新
const option = {
tooltip: {
trigger: "item",
formatter: "{b}: {c} ({d}%)",
},
legend: {
orient: "horizontal",
bottom: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#fff", fontSize: 12 },
data: ["运行", "离线", "等待", "维护"],
},
series: [
{
name: "机台状态",
type: "pie",
radius: ["40%", "70%"],
avoidLabelOverlap: true,
emphasis: {
label: {
show: true,
position: "outside",
formatter: "{d}%",
color: "#fff",
fontSize: 12,
},
},
labelLine: {
show: true,
length: 10,
length2: 40,
lineStyle: {
color: "rgba(255, 255, 255, 0.3)",
},
},
data: [
{ value: statusCounts.running, name: "运行", itemStyle: { color: "#67C23A" } },
{ value: statusCounts.offline, name: "离线", itemStyle: { color: "#909399" } },
{ value: statusCounts.waiting, name: "等待", itemStyle: { color: "#E6A23C" } },
{
value: statusCounts.maintenance,
name: "维护",
itemStyle: { color: "#F56C6C" },
},
],
},
{
name: "总数",
type: "pie",
radius: ["0", "30%"],
label: {
position: "center",
formatter: ["{a|总数量}", "{b|" + total + "}"].join("\n"),
rich: {
a: {
fontSize: 14,
color: "#fff",
lineHeight: 20,
},
b: {
fontSize: 24,
color: "#fff",
fontWeight: "bold",
lineHeight: 30,
},
},
},
data: [
{
value: total,
name: "总数",
itemStyle: { color: "rgba(0, 0, 0, 0.2)" },
},
],
},
],
};
// 如果没有数据,添加"暂无数据"提示
if (isEmpty || total === 0) {
option.graphic = [
{
type: "text",
left: "center",
top: "middle",
style: {
text: "暂无数据",
fill: "#999",
font: "14px Microsoft YaHei",
},
},
];
}
// 应用完整的新配置
this.charts.statusPie.setOption(option, true);
console.log("状态饼图已更新,总数:", total);
},
// 选择车间
selectWorkshop(workshop) {
this.selectedWorkshop = workshop;
this.selectionStep = "line";
},
// 选择产线
selectProductionLine(line) {
this.selectedLine = line.id;
this.selectionPanelVisible = false;
this.handleLineChange();
},
// 获取当前产线名称
getCurrentLineName() {
if (!this.selectedLine) return "";
// 从树形数据中查找当前产线
for (const workshop of this.apiData.plantTree) {
if (workshop.children) {
const line = workshop.children.find((line) => line.id === this.selectedLine);
if (line) return line.name;
}
}
return "";
},
// 更新所有图表
updateAllCharts() {
console.log("开始更新所有图表...");
// 更新状态饼图
this.updateStatusPieChart();
console.log("状态饼图更新完成");
// 更新OEE排名图表
this.updateOEERankChart();
console.log("OEE排名图表更新完成");
// 更新等待损失图表
this.updateWaitingLossChart();
console.log("等待损失图表更新完成");
// 更新故障损失图表
this.updateFaultLossChart();
console.log("故障损失图表更新完成");
// 更新OEE趋势图表
this.updateOEETrendChart();
console.log("OEE趋势图表更新完成");
console.log("所有图表更新完成");
},
// 初始化所有图表
initCharts() {
console.log("开始初始化所有图表...");
// 初始化状态饼图
this.charts.statusPie = this.initStatusPieChart();
console.log("状态饼图初始化完成");
// 初始化OEE排名图表
this.charts.oeeRank = this.initOEERankChart();
// 初始化等待损失图表
this.charts.waitingLoss = this.initWaitingLossChart();
// 初始化OEE趋势图表
this.charts.oeeTrend = this.initOEETrendChart();
// 初始化故障损失图
this.charts.faultLoss = this.initFaultLossChart();
console.log("故障损失图表初始化完成");
// 监听窗口大小变化
window.addEventListener("resize", this.handleResize);
console.log("所有图表初始化完成");
},
// 初始化状态饼图
initStatusPieChart() {
// 获取DOM元素
const statusPieChart = document.getElementById("statusPieChart");
if (!statusPieChart) {
console.error("找不到状态饼图DOM元素");
return null;
}
console.log("初始化状态饼图...");
// 初始化图表
const chart = echarts.init(statusPieChart);
// 使用原有的图表配置
const option = {
tooltip: {
trigger: "item",
formatter: "{b}: {c} ({d}%)",
},
legend: {
orient: "horizontal",
bottom: 0,
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#fff", fontSize: 12 },
data: ["运行", "离线", "等待", "维护"],
},
series: [
{
name: "机台状态",
type: "pie",
avoidLabelOverlap: true, // 自动调整标签避免重叠
radius: ["40%", "70%"],
emphasis: {
label: {
show: true,
position: "outside",
formatter: "{d}%",
color: "#fff",
fontSize: 12,
},
},
labelLine: {
show: true,
length: 10,
length2: 10,
lineStyle: {
color: "rgba(255, 255, 255, 0.3)",
},
},
data: [
{
value: 0,
name: "运行",
itemStyle: { color: "#67C23A" },
},
{
value: 0,
name: "离线",
itemStyle: { color: "#909399" },
},
{
value: 0,
name: "等待",
itemStyle: { color: "#E6A23C" },
},
{
value: 0,
name: "维护",
itemStyle: { color: "#F56C6C" },
},
],
},
{
name: "总数",
type: "pie",
radius: ["0", "30%"],
label: {
position: "center",
formatter: ["{a|总数量}", "{b|0}"].join("\n"),
rich: {
a: {
fontSize: 14,
color: "#fff",
lineHeight: 20,
},
b: {
fontSize: 24,
color: "#fff",
fontWeight: "bold",
lineHeight: 30,
},
},
},
data: [
{
value: 0,
name: "总数",
itemStyle: { color: "rgba(0, 0, 0, 0.2)" },
},
],
},
],
};
// 应用配置
chart.setOption(option);
console.log("状态饼图初始化成功");
return chart;
},
// 处理窗口大小变化
handleResize() {
// 延迟执行以确保DOM已更新
setTimeout(() => {
if (this.charts.statusPie) this.charts.statusPie.resize();
if (this.charts.oeeRank) this.charts.oeeRank.resize();
if (this.charts.waitingLoss) this.charts.waitingLoss.resize();
if (this.charts.oeeTrend) this.charts.oeeTrend.resize();
if (this.charts.faultLoss) this.charts.faultLoss.resize();
}, 100);
},
// 组件销毁前清理
beforeDestroy() {
window.removeEventListener("resize", this.handleResize);
Object.values(this.charts).forEach((chart) => {
chart && chart.dispose();
});
},
// 初始化OEE排名图表
initOEERankChart() {
// 获取DOM元素
const oeeRankChart = document.getElementById("oeeRankChart");
if (!oeeRankChart) {
console.error("找不到OEE排名图表DOM元素");
return null;
}
console.log("初始化OEE排名图表...");
// 初始化图表
const chart = echarts.init(oeeRankChart);
// 设置图表配置,保持原有样式
const option = {
tooltip: {
trigger: "axis",
formatter: "{b}: {c}%",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
dataZoom: [{
type: 'slider',
xAxisIndex: 0, // 指定控制X轴‌:ml-citation{ref="4" data="citationList"}
start: 0, // 初始显示起点‌:ml-citation{ref="1" data="citationList"}
end: 40, // 初始显示终点(示例值,按需计算)
height: 1, // 滚动条高度‌:ml-citation{ref="1" data="citationList"}
bottom: 1, // 滚动条位置‌:ml-citation{ref="1" data="citationList"}
fillerColor: 'rgba(84,112,198,0.6)', // 选中区域颜色‌:ml-citation{ref="1" data="citationList"}
zoomLock: true // 禁止缩放‌:ml-citation{ref="5" data="citationList"}
}, {
type: 'inside', // 内置滚动控制‌:ml-citation{ref="2" data="citationList"}
xAxisIndex: 0,
zoomOnMouseWheel: false, // 禁用滚轮缩放‌:ml-citation{ref="5" data="citationList"}
moveOnMouseWheel: true // 启用滚轮平移‌:ml-citation{ref="5" data="citationList"}
}],
xAxis: {
type: "category",
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff", interval: 0, fontSize: 10, align: 'center' },
data: [],
},
yAxis: {
type: "value",
max: 100,
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: {
color: "#fff",
formatter: "{value}%",
},
splitLine: {
lineStyle: { color: "rgba(255,255,255,0.1)" },
},
},
series: [
{
type: "bar",
barWidth: "40%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#67C23A" },
{ offset: 1, color: "#409EFF" },
]),
},
label: {
show: true,
position: "top",
color: "#fff",
formatter: "{c}%",
},
data: [],
},
],
};
// 应用配置
chart.setOption(option);
console.log("OEE排名图表初始化成功");
return chart;
},
calculateRange() {
const showNum = 3;
const total = option.xAxis.data.length;
return total <= showNum ? 100 : (showNum / total * 100);
},
// 更新OEE排名图表
updateOEERankChart(isEmpty = false) {
if (!this.charts.oeeRank) {
console.error("OEE排名图表未初始化,无法更新");
return;
}
// 处理OEE排名数据
let deviceNames = [];
let deviceValues = [];
let hasRealData = false; // 添加标志,表示是否有真实数据
if (!isEmpty && this.apiData.oeeRankData && this.apiData.oeeRankData.length > 0) {
// 按照利用率排序(从高到低)
const sortedData = this.apiData.oeeRankData
.sort((a, b) => b.utilization_rate - a.utilization_rate)
.slice(0, 5); // 只取前5个
// 提取设备名称和利用率值
sortedData.forEach((item) => {
// 修改这里:从所有设备列表中获取准确的设备名称
let name = item.code; // 默认使用设备编码
// 在所有设备列表中查找匹配的设备
if (this.apiData.allEquipmentData && this.apiData.allEquipmentData.length > 0) {
const matchedDevice = this.apiData.allEquipmentData.find(
(device) => device.code === item.code
);
// 如果找到匹配的设备,使用其名称
if (matchedDevice && matchedDevice.name) {
name = matchedDevice.name;
}
}
// 转换为百分比并保留一位小数
const value = parseFloat(item.utilization_rate.toFixed(1));
const truncateBasic = (str) => str.slice(0, 12);
deviceNames.push(truncateBasic(name));
deviceValues.push(value);
});
// 如果有真实数据,设置标志
if (deviceNames.length > 0) {
hasRealData = true;
}
console.log("处理后的OEE排名数据:", {
names: deviceNames,
values: deviceValues,
});
} else {
console.log("没有OEE排名数据,显示暂无数据提示");
// 添加一个空数据,用于显示"暂无数据"提示
deviceNames = ["暂无数据"];
deviceValues = [0];
}
// 其余代码保持不变...
// 如果有真实数据,强制重新创建图表
if (hasRealData) {
// 销毁旧图表
this.charts.oeeRank.dispose();
// 重新创建图表
this.charts.oeeRank = this.initOEERankChart();
// 更新图表数据,不包含graphic属性
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
};
// 应用更新
this.charts.oeeRank.setOption(option);
} else {
// 没有真实数据,使用原来的逻辑
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
graphic: [
{
type: "text",
left: "center",
top: "middle",
style: {
text: "暂无数据",
fill: "#999",
font: "14px Microsoft YaHei",
},
},
],
};
// 应用更新
this.charts.oeeRank.setOption(option);
}
console.log(
"OEE排名图表已更新,设备数:",
deviceNames.length,
"是否有真实数据:",
hasRealData
);
},
// 初始化等待损失图表
initWaitingLossChart() {
// 获取DOM元素
const waitingLossChart = document.getElementById("waitingLossChart");
if (!waitingLossChart) {
console.error("找不到等待损失图表DOM元素");
return null;
}
console.log("初始化等待损失图表...");
// 初始化图表
const chart = echarts.init(waitingLossChart);
// 设置图表配置
const option = {
tooltip: {
trigger: "axis",
formatter: "{b}: {c}分钟",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "5%", // 增加左侧边距
right: "4%",
bottom: "3%",
containLabel: true,
},
dataZoom: [{
type: 'slider',
xAxisIndex: 0, // 指定控制X轴‌:ml-citation{ref="4" data="citationList"}
start: 0, // 初始显示起点‌:ml-citation{ref="1" data="citationList"}
end: 40, // 初始显示终点(示例值,按需计算)
height: 1, // 滚动条高度‌:ml-citation{ref="1" data="citationList"}
bottom: 1, // 滚动条位置‌:ml-citation{ref="1" data="citationList"}
fillerColor: 'rgba(84,112,198,0.6)', // 选中区域颜色‌:ml-citation{ref="1" data="citationList"}
zoomLock: true // 禁止缩放‌:ml-citation{ref="5" data="citationList"}
}, {
type: 'inside', // 内置滚动控制‌:ml-citation{ref="2" data="citationList"}
xAxisIndex: 0,
zoomOnMouseWheel: false, // 禁用滚轮缩放‌:ml-citation{ref="5" data="citationList"}
moveOnMouseWheel: true // 启用滚轮平移‌:ml-citation{ref="5" data="citationList"}
}],
xAxis: {
type: "category",
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff", interval: 0, fontSize: 10, align: 'center' },
data: [],
},
yAxis: {
type: "value",
name: "分钟",
nameGap: 38,
nameTextStyle: { color: "#fff" },
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff" },
splitLine: {
lineStyle: { color: "rgba(255,255,255,0.1)" },
},
},
series: [
{
type: "bar",
barWidth: "40%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#409EFF" },
{ offset: 1, color: "#36CE9E" },
]),
},
label: {
show: true,
position: "top",
color: "#fff",
formatter: "{c}分钟",
},
data: [],
},
],
};
// 应用配置
chart.setOption(option);
console.log("等待损失图表初始化成功");
return chart;
},
// 更新等待损失图表
updateWaitingLossChart(isEmpty = false) {
if (!this.charts.waitingLoss) {
console.error("等待损失图表未初始化,无法更新");
return;
}
// 处理等待损失数据
let deviceNames = [];
let deviceValues = [];
let hasRealData = false; // 添加标志,表示是否有真实数据
if (!isEmpty && this.apiData.waitingLossData && this.apiData.waitingLossData.length > 0) {
// 按照损失时间排序(从高到低)
const sortedData = this.apiData.waitingLossData
.sort((a, b) => b.analysis_loss - a.analysis_loss)
.slice(0, 5); // 只取前5个
// 提取设备名称和损失时间值
sortedData.forEach((item) => {
// 修改这里:从所有设备列表中获取准确的设备名称
let name = item.code; // 默认使用设备编码
// 在所有设备列表中查找匹配的设备
if (this.apiData.allEquipmentData && this.apiData.allEquipmentData.length > 0) {
const matchedDevice = this.apiData.allEquipmentData.find(
(device) => device.code === item.code
);
// 如果找到匹配的设备,使用其名称
if (matchedDevice && matchedDevice.name) {
name = matchedDevice.name;
}
}
// 转换为分钟并保留整数
const value = Math.round(item.analysis_loss);
const truncateBasic = (str) => str.slice(0, 12);
deviceNames.push(truncateBasic(name));
deviceValues.push(value);
});
// 如果有真实数据,设置标志
if (deviceNames.length > 0) {
hasRealData = true;
}
console.log("处理后的等待损失数据:", {
names: deviceNames,
values: deviceValues,
});
} else {
console.log("没有等待损失数据,显示暂无数据提示");
// 添加一个空数据,用于显示"暂无数据"提示
deviceNames = ["暂无数据"];
deviceValues = [0];
}
// 如果有真实数据,强制重新创建图表
if (hasRealData) {
// 销毁旧图表
this.charts.waitingLoss.dispose();
// 重新创建图表
this.charts.waitingLoss = this.initWaitingLossChart();
// 更新图表数据,不包含graphic属性
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
};
// 应用更新
this.charts.waitingLoss.setOption(option);
} else {
// 没有真实数据,使用原来的逻辑
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
graphic: [
{
type: "text",
left: "center",
top: "middle",
style: {
text: "暂无数据",
fill: "#999",
font: "14px Microsoft YaHei",
},
},
],
};
// 应用更新
this.charts.waitingLoss.setOption(option);
}
console.log(
"等待损失图表已更新,设备数:",
deviceNames.length,
"是否有真实数据:",
hasRealData
);
},
// 初始化故障损失图表
initFaultLossChart() {
// 获取DOM元素
const faultLossChart = document.getElementById("faultLossChart");
if (!faultLossChart) {
console.error("找不到故障损失图表DOM元素");
return null;
}
console.log("初始化故障损失图表...");
// 初始化图表
const chart = echarts.init(faultLossChart);
// 设置图表配置
const option = {
tooltip: {
trigger: "axis",
formatter: "{b}: {c}分钟",
axisPointer: {
type: "shadow",
},
},
grid: {
left: "5%", // 增加左侧边距
right: "4%",
bottom: "3%",
containLabel: true,
},
dataZoom: [{
type: 'slider',
xAxisIndex: 0, // 指定控制X轴‌:ml-citation{ref="4" data="citationList"}
start: 0, // 初始显示起点‌:ml-citation{ref="1" data="citationList"}
end: 40, // 初始显示终点(示例值,按需计算)
height: 1, // 滚动条高度‌:ml-citation{ref="1" data="citationList"}
bottom: 1, // 滚动条位置‌:ml-citation{ref="1" data="citationList"}
fillerColor: 'rgba(84,112,198,0.6)', // 选中区域颜色‌:ml-citation{ref="1" data="citationList"}
zoomLock: true // 禁止缩放‌:ml-citation{ref="5" data="citationList"}
}, {
type: 'inside', // 内置滚动控制‌:ml-citation{ref="2" data="citationList"}
xAxisIndex: 0,
zoomOnMouseWheel: false, // 禁用滚轮缩放‌:ml-citation{ref="5" data="citationList"}
moveOnMouseWheel: true // 启用滚轮平移‌:ml-citation{ref="5" data="citationList"}
}],
xAxis: {
type: "category",
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff", interval: 0, fontSize: 10, align: 'center' },
data: [],
},
yAxis: {
type: "value",
name: "分钟",
nameGap: 38,
nameTextStyle: { color: "#fff" },
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff" },
splitLine: {
lineStyle: { color: "rgba(255,255,255,0.1)" },
},
},
series: [
{
type: "bar",
barWidth: "40%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#F56C6C" },
{ offset: 1, color: "#E6A23C" },
]),
},
label: {
show: true,
position: "top",
color: "#fff",
formatter: "{c}分钟",
},
data: [],
},
],
};
// 应用配置
chart.setOption(option);
console.log("故障损失图表初始化成功");
return chart;
},
// 更新故障损失图表
updateFaultLossChart(isEmpty = false) {
if (!this.charts.faultLoss) {
console.error("故障损失图表未初始化,无法更新");
return;
}
// 处理故障损失数据
let deviceNames = [];
let deviceValues = [];
let hasRealData = false; // 添加标志,表示是否有真实数据
if (!isEmpty && this.apiData.faultLossData && this.apiData.faultLossData.length > 0) {
// 按照损失时间排序(从高到低)
const sortedData = this.apiData.faultLossData
.sort((a, b) => b.analysis_loss - a.analysis_loss)
.slice(0, 5); // 只取前5个
console.log("排序后的故障损失数据:", JSON.stringify(sortedData));
// 提取设备名称和损失时间值
sortedData.forEach((item) => {
// 修改这里:从所有设备列表中获取准确的设备名称
let name = item.code; // 默认使用设备编码
// 在所有设备列表中查找匹配的设备
if (this.apiData.allEquipmentData && this.apiData.allEquipmentData.length > 0) {
const matchedDevice = this.apiData.allEquipmentData.find(
(device) => device.code === item.code
);
// 如果找到匹配的设备,使用其名称
if (matchedDevice && matchedDevice.name) {
name = matchedDevice.name;
}
}
// 转换为分钟并保留整数
const value = Math.round(item.analysis_loss);
const truncateBasic = (str) => str.slice(0, 12);
deviceNames.push(truncateBasic(name));
deviceValues.push(value);
});
// 如果有真实数据,设置标志
if (deviceNames.length > 0) {
hasRealData = true;
}
console.log("处理后的故障损失数据:", {
names: deviceNames,
values: deviceValues,
});
} else {
console.log("没有故障损失数据,显示暂无数据提示");
// 添加一个空数据,用于显示"暂无数据"提示
deviceNames = ["暂无数据"];
deviceValues = [0];
}
// 如果有真实数据,强制重新创建图表
if (hasRealData) {
// 销毁旧图表
this.charts.faultLoss.dispose();
// 重新创建图表
this.charts.faultLoss = this.initFaultLossChart();
// 更新图表数据,不包含graphic属性
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
};
// 应用更新
this.charts.faultLoss.setOption(option);
} else {
// 没有真实数据,使用原来的逻辑
const option = {
xAxis: {
data: deviceNames,
},
series: [
{
data: deviceValues,
},
],
graphic: [
{
type: "text",
left: "center",
top: "middle",
style: {
text: "暂无数据",
fill: "#999",
font: "14px Microsoft YaHei",
},
},
],
};
// 应用更新
this.charts.faultLoss.setOption(option);
}
console.log(
"故障损失图表已更新,设备数:",
deviceNames.length,
"是否有真实数据:",
hasRealData
);
},
// 使用默认设备数据
useDefaultDevices() {
// 根据不同的产线ID返回不同的设备列表
let deviceList = [];
// 电源PCB产线
if (this.selectedLine === 108) {
deviceList = [
{ id: 1, name: "PCB贴片机", code: "PCB001" },
{ id: 2, name: "PCB焊接机", code: "PCB002" },
{ id: 3, name: "PCB检测仪", code: "PCB003" },
{ id: 4, name: "PCB切割机", code: "PCB004" },
{ id: 5, name: "PCB包装机", code: "PCB005" },
];
}
// S1产线
else if (this.selectedLine === 101) {
deviceList = [
{ id: 6, name: "S1组装机", code: "S1001" },
{ id: 7, name: "S1测试仪", code: "S1002" },
{ id: 8, name: "S1包装机", code: "S1003" },
];
}
// 小米手表产线
else if (this.selectedLine === 123) {
deviceList = [
{ id: 9, name: "手表屏幕组装机", code: "W001" },
{ id: 10, name: "手表电池安装机", code: "W002" },
{ id: 11, name: "手表测试仪", code: "W003" },
{ id: 12, name: "手表包装机", code: "W004" },
];
}
// 其他产线使用默认设备
else {
deviceList = [
{ id: 1, name: "设备1", code: "DEV001" },
{ id: 2, name: "设备2", code: "DEV002" },
{ id: 3, name: "设备3", code: "DEV003" },
{ id: 4, name: "设备4", code: "DEV004" },
{ id: 5, name: "设备5", code: "DEV005" },
];
}
this.devices = deviceList;
console.log("使用默认设备列表:", this.devices);
// 默认选中第一个设备
if (this.devices.length > 0) {
this.selectedDevice = this.devices[0].id;
}
},
// 显示选择面板
showSelectionPanel() {
this.selectionPanelVisible = true;
this.selectionStep = "workshop";
},
// 添加初始化拖拽功能的方法
initDraggable() {
const floatingSelector = document.getElementById("floatingSelector");
const floatingButton = floatingSelector.querySelector(".floating-button");
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 20; // 初始化为左侧位置
let yOffset = 100; // 初始化为顶部位置
// 修改初始位置为左上角
floatingSelector.style.position = "fixed";
floatingSelector.style.top = yOffset + "px";
floatingSelector.style.left = xOffset + "px";
floatingSelector.style.right = "auto"; // 清除right属性
const dragStart = (e) => {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === floatingButton || e.target.parentNode === floatingButton) {
isDragging = true;
floatingButton.style.cursor = "grabbing";
}
};
const dragEnd = () => {
isDragging = false;
floatingButton.style.cursor = "grab";
};
const drag = (e) => {
if (isDragging) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
// 限制拖动范围,防止按钮被拖出视口
const maxX = window.innerWidth - floatingSelector.offsetWidth;
const maxY = window.innerHeight - floatingSelector.offsetHeight;
xOffset = Math.min(Math.max(xOffset, 0), maxX);
yOffset = Math.min(Math.max(yOffset, 0), maxY);
// 更新位置
floatingSelector.style.top = yOffset + "px";
floatingSelector.style.left = xOffset + "px";
}
};
// 添加触摸事件监听器
floatingButton.addEventListener("touchstart", dragStart, false);
document.addEventListener("touchend", dragEnd, false);
document.addEventListener("touchmove", drag, false);
// 添加鼠标事件监听器
floatingButton.addEventListener("mousedown", dragStart, false);
document.addEventListener("mouseup", dragEnd, false);
document.addEventListener("mousemove", drag, false);
},
// 获取设备计划运行时间
async getDevicePlanTime() {
try {
// 发送请求获取计划运行时间
const response = await axios({
method: "post",
url: this.dwsURL + "/roke/workstation/equipment/get_plan_time",
data: {},
headers: {
"Content-Type": "application/json",
"x-session-id": "1d8a662231e9bbab315c736b7551657c18b44366",
},
});
// 处理JSON-RPC格式的响应
if (response.data && response.data.result && response.data.result.code === 0) {
// 获取计划运行时间数据
const planTimeList = response.data.result.data || {};
// 存储计划时间数据,供后续请求使用
this.apiData.planTimeData = planTimeList;
console.log("计划运行时间数据:", JSON.stringify(planTimeList));
return planTimeList;
} else {
const errorMsg = response.data.result
? response.data.result.message
: "获取计划运行时间失败";
console.error(errorMsg);
// 使用默认计划时间数据
this.useDefaultPlanTimeData();
}
} catch (error) {
console.error("获取计划运行时间出错:", error);
// 使用默认计划时间数据
this.useDefaultPlanTimeData();
}
},
// 使用默认计划时间数据
useDefaultPlanTimeData() {
// 创建默认的计划时间数据
this.apiData.planTimeData = {
// 根据实际需要设置默认值
// 例如:设备ID为键,计划时间为值
};
console.log("使用默认计划运行时间数据");
},
// 获取初始设备状态数据
async getInitialDeviceStates() {
console.log("获取初始设备状态数据...");
try {
// 筛选当前产线的设备数据
const lineEquipment = this.filterEquipmentByLine(this.selectedLine);
console.log("当前产线设备数据:", JSON.stringify(lineEquipment));
// 存储当前产线的设备数据
this.apiData.currentLineEquipment = lineEquipment;
// 更新设备选择器的设备列表
this.devices = lineEquipment.map((device) => ({
id: device.code,
name: device.name || device.code,
}));
// 如果有设备,默认选择第一个设备
if (this.devices.length > 0) {
this.selectedDevice = this.devices[0].id;
} else {
this.selectedDevice = "";
}
// 使用当前产线的设备编码,而不是所有设备
const deviceList = [];
// 从当前产线的设备数据中获取设备编码
if (this.apiData.currentLineEquipment && this.apiData.currentLineEquipment.length > 0) {
this.apiData.currentLineEquipment.forEach((device) => {
if (device.code) {
deviceList.push(device.code);
}
});
}
console.log("初始设备编码列表:", deviceList);
// 获取计划时间数据
const planTimeList = this.apiData.planTimeData || {};
// 检查参数
if (deviceList.length === 0) {
console.warn("没有可用的设备编码,无法获取初始设备状态");
return;
}
console.log("初始设备状态请求参数:", {
planTimeList,
deviceList,
});
// 获取设备状态数据
await this.getDeviceStateList(planTimeList, deviceList);
// 获取初始OEE排名数据
const { startDate, endDate } = this.getDateRange(this.selectedTime);
await this.getOEERankData(startDate, endDate);
// 获取初始等待损失数据
const waitingLossDates = this.getDateRange(this.timeRanges.waitingLoss);
await this.getLossData(waitingLossDates.startDate, waitingLossDates.endDate, "waiting");
// 获取初始故障损失数据
const faultLossDates = this.getDateRange(this.timeRanges.faultLoss);
await this.getLossData(faultLossDates.startDate, faultLossDates.endDate, "fault");
// 获取初始OEE趋势数据
if (this.selectedDevice) {
const oeeTrendDates = this.getDateRange(this.timeRanges.oeeTrend);
await this.getOEETrendData(
this.selectedDevice,
oeeTrendDates.startDate,
oeeTrendDates.endDate
);
}
// 确保所有数据都已加载完成后,再更新图表
console.log("所有数据加载完成,开始更新图表");
// 单独更新每个图表,确保它们都能正确显示
this.updateStatusPieChart();
this.updateOEERankChart();
this.updateWaitingLossChart();
this.updateFaultLossChart();
this.updateOEETrendChart();
console.log("所有图表已更新");
} catch (error) {
console.error("获取初始设备状态数据出错:", error);
this.$message.error("获取初始设备状态数据失败,请稍后重试");
}
},
// 处理时间选择变化
async handleTimeChange() {
console.log("时间选择变化:", this.selectedTime);
// 获取选定时间范围的开始和结束日期
const { startDate, endDate } = this.getDateRange(this.selectedTime);
console.log("时间范围:", { startDate, endDate });
// 获取OEE排名数据
await this.getOEERankData(startDate, endDate);
// 调用updateChart方法更新OEE排名图表
this.updateChart("oeeRank");
},
// 获取日期范围
getDateRange(timeOption) {
const now = new Date();
let startDate, endDate;
// 格式化日期为 YYYY-MM-DD
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
switch (timeOption) {
case "today":
// 今天
startDate = formatDate(now);
endDate = formatDate(now);
break;
case "week":
// 本周(周一到今天)
const dayOfWeek = now.getDay() || 7; // 将周日的0转换为7
const monday = new Date(now);
monday.setDate(now.getDate() - (dayOfWeek - 1));
startDate = formatDate(monday);
endDate = formatDate(now);
break;
case "month":
// 本月(1号到今天)
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
startDate = formatDate(firstDayOfMonth);
endDate = formatDate(now);
break;
case "lastMonth":
// 上月(上月1号到上月最后一天)
const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
const lastDayOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
startDate = formatDate(firstDayOfLastMonth);
endDate = formatDate(lastDayOfLastMonth);
break;
default:
// 默认为今天
startDate = formatDate(now);
endDate = formatDate(now);
}
return { startDate, endDate };
},
// 获取OEE排名数据
async getOEERankData(startDate, endDate) {
try {
console.log("获取OEE排名数据, 参数:", {
startDate,
endDate,
});
// 使用当前产线的设备列表,而不是所有设备
const deviceList = [];
// 从当前产线的设备数据中获取设备编码
if (this.apiData.currentLineEquipment && this.apiData.currentLineEquipment.length > 0) {
this.apiData.currentLineEquipment.forEach((device) => {
if (device.code) {
deviceList.push(device.code);
}
});
}
console.log(`当前产线设备列表:`, deviceList);
// 获取计划时间数据
const planTimeList = this.apiData.planTimeData || {};
// 检查参数
if (deviceList.length === 0) {
console.warn("当前产线没有可用的设备编码,无法获取OEE排名数据");
// 设置空数据但不影响图表显示
this.apiData.oeeRankData = [];
// 更新OEE排名图表,显示"暂无数据"
this.updateOEERankChart(true);
return;
}
// 发送请求获取OEE排名数据
const response = await axios({
method: "POST",
url: this.baseURL + "/public/device_efficiency/device_utilization_rate_sort",
data: {
factory_code: this.factory_code || "f1991dbb-52d6-11ef-b13f-fa163e5bb392",
plan_time_list: planTimeList,
device_list: deviceList,
start_date: startDate,
end_date: endDate,
},
headers: {
"Content-Type": "application/json",
},
});
console.log("OEE排名接口返回:", JSON.stringify(response.data));
// 处理响应
if (response.data && response.data.code === 200) {
// 处理OEE排名数据
const oeeRankData = response.data.data || [];
// 存储OEE排名数据
this.apiData.oeeRankData = oeeRankData;
if (this.apiData.allEquipmentData && this.apiData.allEquipmentData.length > 0) {
this.apiData.allEquipmentData.forEach(equipment => {
equipment.utilization_rate = this.apiData.oeeRankData.find(
(device) => device.code === equipment.code
);
});
this.apiData.allEquipmentData.sort((a, b) => b.utilization_rate - a.utilization_rate);
}
// 更新OEE排名图表
this.updateOEERankChart();
return oeeRankData;
} else {
const errorMsg = response.data.msg || "获取OEE排名数据失败";
console.error(errorMsg);
this.$message.error(errorMsg);
// 设置空数据但不影响图表显示
this.apiData.oeeRankData = [];
// 更新OEE排名图表,显示"暂无数据"
this.updateOEERankChart(true);
}
} catch (error) {
console.error("获取OEE排名数据出错:", error);
this.$message.error("获取OEE排名数据失败,请稍后重试");
// 设置空数据但不影响图表显示
this.apiData.oeeRankData = [];
// 更新OEE排名图表,显示"暂无数据"
this.updateOEERankChart(true);
}
},
// 添加获取设备OEE趋势数据的方法
async getOEETrendData(deviceCode, startDate, endDate) {
try {
console.log("获取设备OEE趋势数据, 参数:", {
deviceCode,
startDate,
endDate,
});
// 检查参数
if (!deviceCode) {
console.warn("没有设备编码,无法获取OEE趋势数据");
return;
}
// 获取计划时间数据
const planTimeList = this.apiData.planTimeData || {};
// 发送请求获取OEE趋势数据
const response = await axios({
method: "POST",
url: this.baseURL + "/public/device_efficiency/device_oee_trend",
data: {
factory_code: this.factory_code || "f1991dbb-52d6-11ef-b13f-fa163e5bb392",
device_code: deviceCode,
start_date: startDate,
end_date: endDate,
plan_time_list: planTimeList,
},
headers: {
"Content-Type": "application/json",
},
});
console.log("OEE趋势接口返回:", JSON.stringify(response.data));
// 处理响应
if (response.data && response.data.code === 200) {
// 处理OEE趋势数据
const oeeTrendData = response.data.data || [];
// 存储OEE趋势数据
this.apiData.oeeTrendData = oeeTrendData;
return oeeTrendData;
} else {
const errorMsg = response.data.msg || "获取OEE趋势数据失败";
console.error(errorMsg);
this.$message.error(errorMsg);
}
} catch (error) {
console.error("获取OEE趋势数据出错:", error);
this.$message.error("获取OEE趋势数据失败,请稍后重试");
}
},
// 更新OEE趋势图表
updateOEETrendChart() {
if (!this.charts.oeeTrend) {
console.error("OEE趋势图表未初始化,无法更新");
return;
}
console.log("开始更新OEE趋势图表...");
console.log("OEE趋势数据:", JSON.stringify(this.apiData.oeeTrendData));
// 处理OEE趋势数据
let dates = [];
let values = [];
if (this.apiData.oeeTrendData && this.apiData.oeeTrendData.length > 0) {
// 按照日期排序
const sortedData = this.apiData.oeeTrendData.sort((a, b) => {
return new Date(a.date) - new Date(b.date);
});
console.log("排序后的OEE趋势数据:", JSON.stringify(sortedData));
// 提取日期和OEE值
sortedData.forEach((item) => {
// 格式化日期为MM-DD
const date = new Date(item.date);
const formattedDate = `${date.getMonth() + 1}-${date.getDate()}`;
// 使用utilization_rate字段,而不是oee字段
// 转换为百分比并保留一位小数
const value = parseFloat((item.utilization_rate || 0).toFixed(1));
dates.push(formattedDate);
values.push(value);
});
console.log("处理后的OEE趋势数据:", {
dates,
values,
});
} else {
console.log("没有OEE趋势数据,使用空数据");
}
// 计算最大值,用于设置y轴的最大值
const maxValue = Math.max(...values, 100); // 至少为100%
const yAxisMax = Math.ceil(maxValue / 10) * 10; // 向上取整到最接近的10的倍数
// 更新图表数据
const option = {
xAxis: {
data: dates,
},
yAxis: {
max: yAxisMax, // 动态设置y轴最大值
},
series: [
{
data: values,
},
],
};
// 应用更新
this.charts.oeeTrend.setOption(option);
console.log(
"OEE趋势图表已更新,日期数:",
dates.length,
"最大值:",
maxValue,
"Y轴最大值:",
yAxisMax
);
},
// 修改updateChart方法
async updateChart(chartType) {
console.log(`更新图表: ${chartType}`);
// 根据图表类型执行不同的操作
switch (chartType) {
case "oeeRank":
// 获取当前选择的时间范围
const oeeTimeRange = this.timeRanges.oeeRank;
console.log("OEE排名时间范围:", oeeTimeRange);
// 获取对应的日期范围
const oeeRankDates = this.getDateRange(oeeTimeRange);
console.log("OEE排名日期范围:", oeeRankDates);
// 获取新的OEE排名数据
await this.getOEERankData(oeeRankDates.startDate, oeeRankDates.endDate);
// 更新图表
this.updateOEERankChart();
break;
case "waitingLoss":
// 获取当前选择的时间范围
const waitingTimeRange = this.timeRanges.waitingLoss;
console.log("等待损失时间范围:", waitingTimeRange);
// 获取对应的日期范围
const waitingLossDates = this.getDateRange(waitingTimeRange);
console.log("等待损失日期范围:", waitingLossDates);
// 获取新的等待损失数据
await this.getLossData(
waitingLossDates.startDate,
waitingLossDates.endDate,
"waiting"
);
// 更新图表
this.updateWaitingLossChart();
break;
case "faultLoss":
// 获取当前选择的时间范围
const faultTimeRange = this.timeRanges.faultLoss;
console.log("故障损失时间范围:", faultTimeRange);
// 获取对应的日期范围
const faultLossDates = this.getDateRange(faultTimeRange);
console.log("故障损失日期范围:", faultLossDates);
// 获取新的故障损失数据
await this.getLossData(faultLossDates.startDate, faultLossDates.endDate, "fault");
// 更新图表
this.updateFaultLossChart();
break;
case "oeeTrend":
// 获取当前选择的时间范围
const oeeTrendTimeRange = this.timeRanges.oeeTrend;
console.log("OEE趋势时间范围:", oeeTrendTimeRange);
// 获取对应的日期范围
const oeeTrendDates = this.getDateRange(oeeTrendTimeRange);
console.log("OEE趋势日期范围:", oeeTrendDates);
// 获取当前选择的设备
const deviceCode = this.selectedDevice;
console.log("当前选择的设备:", deviceCode);
// 获取新的OEE趋势数据
await this.getOEETrendData(
deviceCode,
oeeTrendDates.startDate,
oeeTrendDates.endDate
);
// 更新图表
this.updateOEETrendChart();
break;
case "statusPie":
this.updateStatusPieChart();
break;
default:
console.warn(`未知的图表类型: ${chartType}`);
}
},
// 初始化OEE趋势图表
initOEETrendChart() {
// 获取DOM元素
const oeeTrendChart = document.getElementById("oeeTrendChart");
if (!oeeTrendChart) {
console.error("找不到OEE趋势图表DOM元素");
return null;
}
console.log("初始化OEE趋势图表...");
// 初始化图表
const chart = echarts.init(oeeTrendChart);
// 设置图表配置
const option = {
tooltip: {
trigger: "axis",
formatter: "{b}: {c}%",
axisPointer: {
type: "line",
},
},
grid: {
left: "5%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff" },
data: [],
},
yAxis: {
type: "value",
name: "OEE(%)",
nameGap: 38,
nameTextStyle: { color: "#fff" },
min: 0,
// 移除max限制,让图表自动计算最大值
axisLine: { lineStyle: { color: "#fff" } },
axisLabel: { color: "#fff" },
splitLine: {
lineStyle: { color: "rgba(255,255,255,0.1)" },
},
},
series: [
{
name: "OEE",
type: "line",
smooth: true,
symbol: "circle",
symbolSize: 8,
itemStyle: {
color: "#409EFF",
},
lineStyle: {
width: 3,
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: "#409EFF" },
{ offset: 1, color: "#36CE9E" },
]),
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(64, 158, 255, 0.5)" },
{ offset: 1, color: "rgba(64, 158, 255, 0)" },
]),
},
label: {
show: true,
position: "top",
color: "#fff",
formatter: "{c}%",
},
data: [],
},
],
};
// 应用配置
chart.setOption(option);
console.log("OEE趋势图表初始化成功");
return chart;
},
// 获取损失数据(等待损失和故障损失共用)
async getLossData(startDate, endDate, lossType) {
try {
console.log(`获取${lossType === "waiting" ? "等待" : "故障"}损失数据, 参数:`, {
startDate,
endDate,
lossType,
});
// 使用当前产线的设备列表,而不是所有设备
const deviceList = [];
// 从当前产线的设备数据中获取设备编码
if (this.apiData.currentLineEquipment && this.apiData.currentLineEquipment.length > 0) {
this.apiData.currentLineEquipment.forEach((device) => {
if (device.code) {
deviceList.push(device.code);
}
});
}
console.log(`当前产线设备列表:`, deviceList);
// 获取计划时间数据
const planTimeList = this.apiData.planTimeData || {};
// 检查参数
if (deviceList.length === 0) {
console.warn(
`当前产线没有可用的设备编码,无法获取${lossType === "waiting" ? "等待" : "故障"
}损失数据`
);
// 设置空数据但不影响图表显示
if (lossType === "waiting") {
this.apiData.waitingLossData = [];
// 更新等待损失图表,显示"暂无数据"
this.updateWaitingLossChart(true);
} else {
this.apiData.faultLossData = [];
// 更新故障损失图表,显示"暂无数据"
this.updateFaultLossChart(true);
}
return;
}
// 发送请求获取损失数据
const response = await axios({
method: "POST",
url: this.baseURL + "/public/device_efficiency/device_analysis_loss",
data: {
factory_code: this.factory_code || "f1991dbb-52d6-11ef-b13f-fa163e5bb392",
plan_time_list: planTimeList,
device_list: deviceList,
start_date: startDate,
end_date: endDate,
loss_type: lossType,
},
headers: {
"Content-Type": "application/json",
},
});
console.log(
`${lossType === "waiting" ? "等待" : "故障"}损失接口返回:`,
JSON.stringify(response.data)
);
// 处理响应
if (response.data && response.data.code === 200) {
// 处理损失数据
const lossData = response.data.data || [];
// 存储损失数据
if (lossType === "waiting") {
this.apiData.waitingLossData = lossData;
} else {
this.apiData.faultLossData = lossData;
}
return lossData;
} else {
const errorMsg =
response.data.msg || `获取${lossType === "waiting" ? "等待" : "故障"}损失数据失败`;
console.error(errorMsg);
this.$message.error(errorMsg);
// 设置空数据但不影响图表显示
if (lossType === "waiting") {
this.apiData.waitingLossData = [];
// 更新等待损失图表,显示"暂无数据"
this.updateWaitingLossChart(true);
} else {
this.apiData.faultLossData = [];
// 更新故障损失图表,显示"暂无数据"
this.updateFaultLossChart(true);
}
}
} catch (error) {
console.error(`获取${lossType === "waiting" ? "等待" : "故障"}损失数据出错:`, error);
this.$message.error(
`获取${lossType === "waiting" ? "等待" : "故障"}损失数据失败,请稍后重试`
);
// 设置空数据但不影响图表显示
if (lossType === "waiting") {
this.apiData.waitingLossData = [];
// 更新等待损失图表,显示"暂无数据"
this.updateWaitingLossChart(true);
} else {
this.apiData.faultLossData = [];
// 更新故障损失图表,显示"暂无数据"
this.updateFaultLossChart(true);
}
}
},
// 获取设备状态数据
async getDeviceStates() {
try {
console.log("获取设备状态数据...");
// 使用当前产线的设备列表,而不是所有设备
const deviceList = [];
// 从当前产线的设备数据中获取设备编码
if (this.apiData.currentLineEquipment && this.apiData.currentLineEquipment.length > 0) {
this.apiData.currentLineEquipment.forEach((device) => {
if (device.code) {
deviceList.push(device.code);
}
});
}
console.log(`当前产线设备列表:`, deviceList);
// 检查参数
if (deviceList.length === 0) {
console.warn("当前产线没有可用的设备编码,无法获取设备状态数据");
// 设置空数据但不影响图表显示
this.apiData.deviceStates = [];
// 更新状态饼图,显示"暂无数据"
this.updateStatusPieChart(true);
return;
}
// 发送请求获取设备状态数据
const response = await axios({
method: "POST",
url: this.baseURL + "/public/device_efficiency/device_state",
data: {
factory_code: this.factory_code || "f1991dbb-52d6-11ef-b13f-fa163e5bb392",
device_list: deviceList,
},
headers: {
"Content-Type": "application/json",
},
});
console.log("设备状态接口返回:", JSON.stringify(response.data));
// 处理响应
if (response.data && response.data.code === 200) {
// 处理设备状态数据
const deviceStates = response.data.data || [];
// 存储设备状态数据
this.apiData.deviceStates = deviceStates;
// 更新状态饼图
this.updateStatusPieChart();
return deviceStates;
} else {
const errorMsg = response.data.msg || "获取设备状态数据失败";
console.error(errorMsg);
this.$message.error(errorMsg);
// 设置空数据但不影响图表显示
this.apiData.deviceStates = [];
// 更新状态饼图,显示"暂无数据"
this.updateStatusPieChart(true);
}
} catch (error) {
console.error("获取设备状态数据出错:", error);
this.$message.error("获取设备状态数据失败,请稍后重试");
// 设置空数据但不影响图表显示
this.apiData.deviceStates = [];
// 更新状态饼图,显示"暂无数据"
this.updateStatusPieChart(true);
}
},
},
});
</script>
<style>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: url("/roke_workstation_api/static/html/routing/image/bg-ok.png") no-repeat center center fixed;
background-size: cover;
color: #fff;
min-height: 100vh;
}
#app {
padding: 20px 10px;
}
/* 标题样式 */
.dashboard-header {
width: 100vw;
margin: -20px -20px 10px -20px;
display: flex;
justify-content: center;
align-items: center;
}
.header-content {
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.header-bg {
width: 100%;
height: auto;
}
.header-text {
position: absolute;
font-size: 28px;
font-weight: 450;
color: #fff;
text-shadow: 0 0 10px rgba(0, 195, 255, 0.5);
z-index: 1;
}
/* 浮动选择器样式 */
.floating-selector {
position: fixed;
top: 100px;
left: 20px;
/* 改为left而不是right */
z-index: 1000;
}
/* 修改浮动按钮样式,增加拖动时的视觉反馈 */
.floating-button {
background-color: rgba(6, 32, 65, 0.9);
color: #fff;
width: 45px;
height: 45px;
border-radius: 50%;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5), 0 0 15px rgba(64, 158, 255, 0.5);
cursor: grab;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
border: 2px solid rgba(64, 158, 255, 0.5);
animation: pulse 2s infinite;
}
.floating-button:active {
cursor: grabbing;
transform: scale(0.95);
}
.floating-button i {
font-size: 22px;
color: #409eff;
}
/* 添加脉冲动画使按钮更醒目 */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(64, 158, 255, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(64, 158, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(64, 158, 255, 0);
}
}
/* 选择面板样式 */
.selection-panel {
position: absolute;
top: 50px;
left: 0;
width: 200px;
background-color: rgba(6, 32, 65, 0.95);
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
overflow: hidden;
border: 1px solid rgba(64, 158, 255, 0.2);
}
.panel-header {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.back-button,
.close-button {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #fff;
}
.panel-title {
flex: 1;
text-align: center;
color: #fff;
font-size: 14px;
}
.panel-content {
max-height: 300px;
overflow-y: auto;
}
.selection-list {
padding: 5px 0;
}
.selection-item {
padding: 10px 15px;
color: #fff;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.selection-item:hover {
background-color: rgba(64, 158, 255, 0.2);
}
/* 图表网格布局 */
.chart-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto auto;
gap: 20px;
padding: 0 10px;
margin-top: 20px;
width: 100%;
}
/* 第一行的三个图表 */
.chart-grid>div:nth-child(1) {
grid-column: 1;
grid-row: 1;
}
.chart-grid>div:nth-child(2) {
grid-column: 2;
grid-row: 1;
}
.chart-grid>div:nth-child(3) {
grid-column: 3;
grid-row: 1;
}
/* 第二行的两个图表 */
.chart-grid>div:nth-child(4) {
grid-column: 1 / span 2;
grid-row: 2;
}
.chart-grid>div:nth-child(5) {
grid-column: 3;
grid-row: 2;
}
/* 图表卡片样式 */
.chart-card {
background: rgba(6, 32, 65, 0.9);
border-radius: 4px;
padding: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
height: 32px;
}
.card-header h3 {
font-size: 14px;
color: #fff;
margin: 0;
position: relative;
padding-left: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 70%;
}
.card-header h3::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 14px;
background-color: #409eff;
}
.current-date {
color: #fff;
font-size: 14px;
white-space: nowrap;
}
.header-controls {
display: flex;
align-items: center;
gap: 10px;
}
.chart-container {
height: 280px;
width: 100%;
overflow: hidden;
}
/* Element UI 样式覆盖 */
.el-select {
width: 100px;
display: flex;
align-items: center;
}
.el-select .el-input__inner {
height: 28px;
line-height: 28px;
background-color: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #fff !important;
}
.el-select .el-input__icon {
color: #fff !important;
}
.el-select-dropdown {
background-color: rgba(6, 32, 65, 0.95) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
.el-select-dropdown__item {
color: #fff !important;
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover {
background-color: rgba(64, 158, 255, 0.2) !important;
}
.el-select-dropdown__item.selected {
color: #409eff !important;
background-color: rgba(64, 158, 255, 0.1) !important;
}
.el-input.is-focus .el-input__inner,
.el-input.is-focus .el-input__inner:focus {
color: #fff !important;
}
/* 修改单位标签样式 */
.unit-label {
color: #fff;
font-size: 12px;
margin-left: 10px;
display: flex;
align-items: center;
height: 28px;
}
/* 添加当前选择信息样式 */
.current-selection {
padding: 10px 15px;
background-color: rgba(64, 158, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.selection-info {
display: flex;
justify-content: space-between;
margin: 3px 0;
}
.info-label {
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
}
.info-value {
color: #409eff;
font-size: 12px;
font-weight: bold;
}
/* 选中项样式 */
.selection-item.selected {
background-color: rgba(64, 158, 255, 0.2);
color: #409eff;
}
</style>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment