Files
cattleTransportation/datav/js/visual.js
2025-12-11 17:30:38 +08:00

734 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 订单列表逻辑
(function() {
// 存储 Token
let authToken = '';
// 登录并获取订单数据
function loginAndFetchOrders() {
// 如果已有 Token直接获取订单
if (authToken) {
fetchOrders();
return;
}
// 登录接口
$.ajax({
url: '/api/login',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
mobile: '18888888888', // 请替换为真实账号
password: '123456', // 请替换为真实密码
loginType: 0
}),
success: function(res) {
console.log('登录接口完整响应:', res);
// 兼容多种 Token 返回结构
// 1. res.data.tokenValue (Sa-Token 常见)
// 2. res.data.token (常见 JWT)
// 3. res.token (根层级)
// 4. res.data (直接返回字符串)
let token = '';
if (res && res.code === 200) {
if (res.data && res.data.tokenValue) {
token = res.data.tokenValue;
} else if (res.data && res.data.token) {
token = res.data.token;
} else if (res.token) {
token = res.token;
} else if (typeof res.data === 'string') {
token = res.data;
}
}
if (token) {
authToken = token;
console.log('登录成功获取到Token:', authToken);
fetchOrders();
fetchCattleOverview();
fetchSlaughterOverview();
} else {
console.error('登录成功但未能提取Token请检查响应结构:', res);
}
},
error: function(err) {
console.error('登录请求错误:', err);
}
});
}
// 获取订单列表
function fetchOrders() {
if (!authToken) return;
$.ajax({
url: '/api/delivery/pageQueryList',
method: 'POST', // 假设是 POST 分页查询
contentType: 'application/json',
headers: {
'Authorization': authToken,
// 或者根据后端要求可能是其他 header key如 satoken
'satoken': authToken
},
data: JSON.stringify({
pageNo: 1,
pageSize: 300,
interfaceType: 2
}),
success: function(res) {
console.log('订单列表响应:', res);
let list = [];
if (res && res.data && Array.isArray(res.data.rows)) {
// 适配 { code: 200, data: { rows: [...] } } 结构
list = res.data.rows;
} else if (res && res.data && Array.isArray(res.data.records)) {
// 适配 { code: 200, data: { records: [...] } } 结构
list = res.data.records;
} else if (res && Array.isArray(res.data)) {
// 适配 { code: 200, data: [...] } 结构
list = res.data;
}
renderOrderList(list);
renderOrderStatusStats(list);
updateMapWithFlyLines(list);
},
error: function(err) {
console.error('获取订单列表失败:', err);
if (err.status === 401) {
// Token 过期,重新登录
authToken = '';
loginAndFetchOrders();
}
}
});
}
function renderOrderStatusStats(orders) {
if (!orders || !Array.isArray(orders)) return;
let pendingCount = 0; // status: 1
let shippingCount = 0; // status: 2
let doneCount = 0; // status: 3
orders.forEach(order => {
const status = parseInt(order.status);
if (status === 1) {
pendingCount++;
} else if (status === 2) {
shippingCount++;
} else if (status === 3) {
doneCount++;
}
});
// 更新 DOM
$('.stat-value[data-field="status_pending"]').text(pendingCount);
$('.stat-value[data-field="status_shipping"]').text(shippingCount);
$('.stat-value[data-field="status_done"]').text(doneCount);
}
function updateMapWithFlyLines(data) {
if (!data || !Array.isArray(data)) return;
const flyLineData = [];
const pointMap = new Map(); // 用于去重地点
data.forEach(item => {
// startLon, startLat, endLon, endLat
if (item.startLon && item.startLat && item.endLon && item.endLat) {
// 1. 飞线数据
flyLineData.push({
fromName: item.startLocation || item.startAddress || '',
toName: item.endLocation || item.endAddress || '',
coords: [
[parseFloat(item.startLon), parseFloat(item.startLat)], // 起点
[parseFloat(item.endLon), parseFloat(item.endLat)] // 终点
]
});
// 2. 起点数据 (用于显示名称)
const startName = item.startLocation || item.startAddress || '';
if (startName) {
const startKey = `${startName}_${item.startLon}_${item.startLat}`;
if (!pointMap.has(startKey)) {
pointMap.set(startKey, {
name: startName,
value: [parseFloat(item.startLon), parseFloat(item.startLat)]
});
}
}
// 3. 终点数据 (用于显示名称)
const endName = item.endLocation || item.endAddress || '';
if (endName) {
const endKey = `${endName}_${item.endLon}_${item.endLat}`;
if (!pointMap.has(endKey)) {
pointMap.set(endKey, {
name: endName,
value: [parseFloat(item.endLon), parseFloat(item.endLat)]
});
}
}
}
});
// 更新 option8 (全局变量)
if (typeof option8 !== 'undefined') {
// series[1] 是线路
if (option8.series && option8.series[1]) {
option8.series[1].data = flyLineData;
}
// series[3] 是运单地点 (我们在 chartMap.js 中新加的)
if (option8.series && option8.series[3]) {
option8.series[3].data = Array.from(pointMap.values());
}
// 尝试获取 echarts 实例并更新
if (typeof myChart8 !== 'undefined') {
myChart8.setOption(option8);
} else if (window.myChart8) {
window.myChart8.setOption(option8);
}
}
}
// 存储滚动定时器,防止重复创建
let orderScrollTimer = null;
function renderOrderList(orders) {
const container = $('.order_list_content');
if (container.length === 0) return;
// 清除旧的定时器和事件监听
if (orderScrollTimer) {
clearInterval(orderScrollTimer);
orderScrollTimer = null;
}
container.off('mouseenter mouseleave');
let html = '';
if (orders.length === 0) {
html = '<div style="text-align:center;color:#ccc;padding:10px;">暂无运单</div>';
container.html(html);
} else {
orders.forEach((order, index) => {
// 状态映射: 1-准备中2-运送中3-已结束
let statusText = '--';
let statusClass = '';
// 确保 status 是数字
const status = parseInt(order.status);
switch(status) {
case 1:
statusText = '准备中';
statusClass = 'status-pending'; // 红色
break;
case 2:
statusText = '运送中';
statusClass = 'status-shipping'; // 黄色
break;
case 3:
statusText = '已结束';
statusClass = 'status-done'; // 绿色
break;
default:
statusText = '未知';
}
html += `
<div class="order_item">
<span>${index + 1}</span>
<span>${order.deliveryNumber || '--'}</span>
<span>${order.startLocation || order.startAddress || '--'}</span>
<span>${order.endLocation || order.endAddress || '--'}</span>
<span class="${statusClass}">${statusText}</span>
<span>${order.estimatedDeliveryTime || '--'}</span>
</div>
`;
});
container.html(html);
// 自动滚动逻辑
// 确保 DOM 渲染完成
setTimeout(() => {
const scrollHeight = container[0].scrollHeight;
const clientHeight = container.height();
// 只有当内容高度超过容器高度时才滚动
if (scrollHeight > clientHeight) {
// 复制一份内容实现无缝滚动
container.append(html);
let currentScroll = 0;
function startScroll() {
if (orderScrollTimer) clearInterval(orderScrollTimer);
orderScrollTimer = setInterval(() => {
currentScroll += 0.5; // 滚动速度,数值越小越慢
// 当滚动到第一份内容的末尾时,瞬间切换回顶部
if (currentScroll >= scrollHeight) {
currentScroll = 0;
}
container.scrollTop(currentScroll);
}, 20); // 刷新频率
}
startScroll();
// 鼠标悬停暂停
container.on('mouseenter', function() {
if (orderScrollTimer) clearInterval(orderScrollTimer);
});
// 鼠标移开继续
container.on('mouseleave', function() {
startScroll();
});
}
}, 100);
}
}
// 初始化
$(function() {
loginAndFetchOrders();
// 定时刷新 (每30秒)
setInterval(() => {
if (authToken) {
fetchOrders();
fetchCattleOverview();
fetchSlaughterOverview();
} else {
loginAndFetchOrders();
}
}, 30000);
});
// 获取牛只概况(仓库列表)
function fetchCattleOverview() {
if (!authToken) return;
$.ajax({
url: '/api/warehouse/list',
method: 'POST', // 改为 POST
contentType: 'application/json',
headers: {
'Authorization': 'Bearer ' + authToken,
'satoken': authToken
},
data: JSON.stringify({
pageNo: 1,
pageSize: 100 // 假设需要分页参数,如果不传可能会报错
}),
success: function(res) {
console.log('牛只概况响应:', res);
let list = [];
// 适配双层嵌套结构: res.data.data.rows
if (res && res.code === 200 && res.data) {
if (res.data.data && Array.isArray(res.data.data.rows)) {
// 结构: { code: 200, data: { data: { rows: [...] } } }
list = res.data.data.rows;
} else if (res.data.rows && Array.isArray(res.data.rows)) {
// 结构: { code: 200, data: { rows: [...] } }
list = res.data.rows;
} else if (Array.isArray(res.data)) {
list = res.data;
}
}
renderCattleOverview(list);
updateMapWithWarehouseData(list);
},
error: function(err) {
console.error('获取牛只概况失败:', err);
}
});
}
function updateMapWithWarehouseData(data) {
if (!data || !Array.isArray(data)) return;
const mapData = data.map(item => {
if (item.longitude && item.latitude) {
return {
name: item.warehouseName || '未知仓库',
value: [parseFloat(item.longitude), parseFloat(item.latitude)]
};
}
return null;
}).filter(item => item !== null);
// 更新 option8 (全局变量)
if (typeof option8 !== 'undefined') {
// 假设 series[0] 是 scatter/effectScatter 用于显示地点
if (option8.series && option8.series[0]) {
option8.series[0].data = mapData;
}
// 尝试获取 echarts 实例并更新
// myChart8 在 index.html 中定义为 var myChart8 (全局)
if (typeof myChart8 !== 'undefined') {
myChart8.setOption(option8);
} else if (window.myChart8) {
window.myChart8.setOption(option8);
}
}
}
function renderCattleOverview(data) {
const container = $('#cattle_overview_container');
if (container.length === 0) return;
let html = '';
if (!data || data.length === 0) {
html = '<div style="text-align:center;color:#ccc;padding:10px;width:100%;">暂无数据</div>';
} else {
data.forEach(item => {
// 接口字段: warehouseName, account
if (item.account === undefined) {
console.warn('牛只概况接口返回数据缺少 account 字段,请检查接口文档或后端返回:', item);
}
const name = item.warehouseName || '--';
const value = item.account || 0;
html += `
<div class="cattle_item">
<div class="cattle_icon">
<span class="glyphicon glyphicon-home" aria-hidden="true"></span>
</div>
<div class="cattle_info">
<span class="cattle_name" title="${name}">${name}</span>
<div>
<span class="cattle_value">${value}</span>
<span class="cattle_unit">头</span>
</div>
</div>
</div>
`;
});
}
container.html(html);
}
// 获取屠宰场概况
function fetchSlaughterOverview() {
if (!authToken) return;
$.ajax({
url: '/api/slaughter/house/list',
method: 'POST',
contentType: 'application/json',
headers: {
'Authorization': 'Bearer ' + authToken,
'satoken': authToken
},
data: JSON.stringify({
pageNo: 1,
pageSize: 100
}),
success: function(res) {
console.log('屠宰场概况响应:', res);
let list = [];
// 适配双层嵌套结构: res.data.data.rows
if (res && res.code === 200 && res.data) {
if (res.data.data && Array.isArray(res.data.data.rows)) {
list = res.data.data.rows;
} else if (res.data.rows && Array.isArray(res.data.rows)) {
list = res.data.rows;
} else if (Array.isArray(res.data)) {
list = res.data;
}
}
renderSlaughterOverview(list);
updateMapWithSlaughterData(list);
},
error: function(err) {
console.error('获取屠宰场概况失败:', err);
}
});
}
function updateMapWithSlaughterData(data) {
if (!data || !Array.isArray(data)) return;
const mapData = data.map(item => {
if (item.longitude && item.latitude) {
return {
name: item.houseName || '未知屠宰场',
value: [parseFloat(item.longitude), parseFloat(item.latitude)]
};
}
return null;
}).filter(item => item !== null);
// 更新 option8 (全局变量)
if (typeof option8 !== 'undefined') {
// series[2] 是屠宰场
if (option8.series && option8.series[2]) {
option8.series[2].data = mapData;
}
// 尝试获取 echarts 实例并更新
if (typeof myChart8 !== 'undefined') {
myChart8.setOption(option8);
} else if (window.myChart8) {
window.myChart8.setOption(option8);
}
}
}
function renderSlaughterOverview(data) {
const container = $('#slaughterhouse_overview_container');
if (container.length === 0) return;
let html = '';
if (!data || data.length === 0) {
html = '<div style="text-align:center;color:#ccc;padding:10px;width:100%;">暂无数据</div>';
} else {
data.forEach(item => {
// 接口字段: houseName, account
const name = item.houseName || '--';
const value = item.account || 0;
html += `
<div class="cattle_item">
<div class="cattle_icon">
<span class="glyphicon glyphicon-oil" aria-hidden="true"></span>
</div>
<div class="cattle_info">
<span class="cattle_name" title="${name}">${name}</span>
<div>
<span class="cattle_value">${value}</span>
<span class="cattle_unit">头</span>
</div>
</div>
</div>
`;
});
}
container.html(html);
}
})();
// 移除旧的模拟数据逻辑
/*
(function() {
// ... 旧代码 ...
})();
*/
// 牛只行情列表逻辑
(function() {
let scrollInterval;
function fetchAndRenderMarketList() {
$.ajax({
// 使用本地代理接口解决跨域问题
// 原接口: https://ad.yunmainiu.com/api/cattle-data
// 代理配置在 server.js 中: /api/cattle-market-data -> https://ad.yunmainiu.com/api/cattle-data
url: '/api/cattle-market-data',
method: 'GET',
success: function(response) {
// 尝试解析字符串响应
if (typeof response === 'string') {
try {
response = JSON.parse(response);
} catch (e) {
console.error('JSON解析失败:', e);
}
}
let data = [];
// 兼容可能的返回格式:直接数组 或 { data: [] }
if (Array.isArray(response)) {
data = response;
} else if (response && Array.isArray(response.data)) {
data = response.data;
} else if (response && Array.isArray(response.list)) {
data = response.list;
}
console.log('获取到的牛只行情数据:', data);
renderMarketList(data);
},
error: function(err) {
console.error('获取牛只行情数据失败:', err);
// 失败时显示空或错误提示,或者保留旧数据
}
});
}
function renderMarketList(data) {
const container = $('.market_list_content');
if (container.length === 0) return;
let html = '';
if (data.length === 0) {
html = '<div style="text-align:center;color:#ccc;padding:20px;">暂无数据</div>';
} else {
data.forEach((item, index) => {
// 接口字段: province(省份), location(地区), type(品种), price(单价)
html += `
<div class="market_item">
<span>${index + 1}</span>
<span>${item.province || '--'}</span>
<span>${item.location || '--'}</span>
<span>${item.type || '--'}</span>
<span class="market_price">${item.price || '--'}</span>
</div>
`;
});
}
container.html(html);
// 清除旧的滚动定时器
if (scrollInterval) {
clearInterval(scrollInterval);
scrollInterval = null;
}
// 简单的自动滚动效果
// 需要等待DOM渲染完成后计算高度这里简单使用setTimeout
setTimeout(() => {
let scrollPos = 0;
// 重新获取DOM元素属性确保准确
const scrollHeight = container[0].scrollHeight;
const clientHeight = container[0].clientHeight;
// 只有当内容高度超过容器高度时才滚动
if (scrollHeight > clientHeight) {
scrollInterval = setInterval(() => {
scrollPos += 1;
// 当滚动到底部时,回到顶部
if (scrollPos >= scrollHeight - clientHeight) {
scrollPos = 0;
}
container.scrollTop(scrollPos);
}, 50);
}
}, 100);
}
$(function() {
fetchAndRenderMarketList();
// 每60秒刷新一次数据
setInterval(fetchAndRenderMarketList, 60000);
});
})();
// 出肉率图表逻辑
(function() {
// 确保 DOM 加载完成
$(function() {
const chartDom = document.getElementById('meat_yield_chart');
if (!chartDom) return;
const myChart = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '10%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'value',
axisLabel: {
color: '#fff',
fontSize: 12
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(255,255,255,0.1)'
}
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
}
},
yAxis: {
type: 'category',
data: ['鲁西黄牛', '西门塔尔', '夏洛莱', '利木赞', '安格斯'],
axisLabel: {
color: '#fff',
fontSize: 12
},
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
}
},
series: [
{
name: '出肉率',
type: 'bar',
data: [45, 52, 58, 55, 60],
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#2b86ff' },
{ offset: 1, color: '#1effc0' }
]),
barBorderRadius: [0, 5, 5, 0]
}
},
label: {
show: true,
position: 'right',
formatter: '{c}%',
color: '#fff'
},
barWidth: '40%'
}
]
};
myChart.setOption(option);
// 窗口大小改变时重置图表大小
window.addEventListener("resize", function () {
myChart.resize();
});
// 模拟数据动态变化
setInterval(() => {
const newData = option.series[0].data.map(val => {
let change = Math.floor(Math.random() * 3) - 1;
let newVal = val + change;
if (newVal > 70) newVal = 70;
if (newVal < 35) newVal = 35;
return newVal;
});
myChart.setOption({
series: [{
data: newData
}]
});
}, 5000);
});
})();