first commit

This commit is contained in:
2026-02-06 20:21:10 +08:00
commit f9d7f980bb
3994 changed files with 1146030 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
<template>
<view class="business-data-page">
<!-- 使用子头部组件 -->
<sub-header
title="业务数据总览"
@goBack="goBack"
@showMoreOptions="showMoreOptions"
/>
<!-- 数据分类列表 -->
<view class="data-categories">
<view
class="category-item"
v-for="category in dataCategories"
:key="category.id"
@click="navigateToDetail(category)"
>
<view
class="category-icon"
:style="{ backgroundColor: category.bgColor }"
>
<i :class="category.icon"></i>
</view>
<view class="category-content">
<view class="category-title">{{ category.title }}</view>
<view class="category-desc">{{ category.description }}</view>
<view class="category-stats">
<text class="stat-item">{{ category.dataCount }}个数据源</text>
<text class="stat-item">{{ category.updateTime }}</text>
</view>
</view>
<view class="category-arrow">
<i class="fas fa-chevron-right"></i>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import SubHeader from "../components/subHeader.vue";
// 数据分类列表
const dataCategories = ref([
{
id: 1,
title: "港口数据",
description: "港口吞吐量等相关数据",
icon: "fas fa-chart-line",
bgColor: "#4ECDC4",
dataCount: 8,
updateTime: "2小时前更新",
},
{
id: 2,
title: "财务数据",
description: "收入、支出、报销、预算等财务相关数据",
icon: "fas fa-money-bill-wave",
bgColor: "#FECA57",
dataCount: 12,
updateTime: "1小时前更新",
},
{
id: 3,
title: "客户数据",
description: "客户信息、满意度、活跃度等客户相关数据",
icon: "fas fa-user-friends",
bgColor: "#45B7D1",
dataCount: 6,
updateTime: "30分钟前更新",
},
{
id: 4,
title: "运营数据",
description: "访问量、转化率、用户行为等运营相关数据",
icon: "fas fa-chart-bar",
bgColor: "#FF6B6B",
dataCount: 10,
updateTime: "15分钟前更新",
},
{
id: 5,
title: "人力资源数据",
description: "员工信息、考勤、绩效等人力资源相关数据",
icon: "fas fa-users",
bgColor: "#96CEB4",
dataCount: 15,
updateTime: "1天前更新",
},
{
id: 6,
title: "项目数据",
description: "项目进度、任务完成情况、资源分配等项目相关数据",
icon: "fas fa-project-diagram",
bgColor: "#A8E6CF",
dataCount: 9,
updateTime: "3小时前更新",
},
]);
// 导航到详情页面
const navigateToDetail = (category) => {
// 特殊处理港口数据,直接跳转到portData页面
if (category.id === 1) {
uni.navigateTo({
url: "/pages/businessDatas/portData",
});
} else {
// 其他数据跳转到通用详情页面
uni.navigateTo({
url: `/pages/businessDatas/detail?categoryId=${category.id}&title=${category.title}`,
});
}
};
// 头部相关方法
const goBack = () => {
uni.navigateBack();
};
const showMoreOptions = () => {
uni.showActionSheet({
itemList: ["刷新", "返回首页"],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 刷新数据
uni.reLaunch({
url: "/pages/businessDatas/index",
});
uni.showToast({ title: "数据已刷新", icon: "success" });
break;
case 1:
uni.reLaunch({
url: "/pages/index/index",
});
uni.showToast({ title: "已返回工作台", icon: "success" });
break;
}
},
});
};
</script>
<style lang="scss" scoped>
.business-data-page {
background: #f9f9fb;
min-height: 100vh;
padding-top: calc(var(--status-bar-height) + 88rpx);
padding-bottom: 40rpx;
}
/* 支持安全区域的设备 */
@supports (padding: max(0px)) {
.business-data-page {
padding-top: calc(
var(--status-bar-height) + 88rpx + env(safe-area-inset-top)
);
}
}
/* 数据分类列表 */
.data-categories {
padding: 24rpx;
}
.category-item {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
transition: all 0.3s ease;
border: 1rpx solid #f0f0f0;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
&:last-child {
margin-bottom: 0;
}
}
.category-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
flex-shrink: 0;
i {
font-size: 36rpx;
color: #fff;
}
}
.category-content {
flex: 1;
min-width: 0;
}
.category-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
line-height: 1.4;
}
.category-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
.category-stats {
display: flex;
gap: 20rpx;
flex-wrap: wrap;
}
.stat-item {
font-size: 22rpx;
color: #999;
background: #f5f5f5;
padding: 4rpx 12rpx;
border-radius: 12rpx;
line-height: 1.2;
}
.category-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
flex-shrink: 0;
i {
font-size: 24rpx;
}
}
</style>
+810
View File
@@ -0,0 +1,810 @@
<template>
<view class="data-detail-page" :class="{ mobile: isMobile }">
<!-- 页面头部 -->
<sub-header
title="港口数据"
@goBack="goBack"
@showMoreOptions="showMoreOptions"
/>
<!-- 数据卡片 -->
<view class="cards-section">
<view class="section-title">数据概览</view>
<view class="card-grid">
<view v-for="card in cardData" :key="card.id" class="data-card">
<view class="card-icon">
<i :class="card.icon"></i>
</view>
<view class="card-content">
<text class="card-value">{{ card.value }}</text>
<text class="card-label">{{ card.title }}</text>
</view>
</view>
</view>
</view>
<!-- 图表分析 -->
<view class="charts-section">
<view class="section-title">图表分析</view>
<view class="chart-tabs">
<view
class="chart-tab"
:class="{ active: activeTab === 'cargo' }"
@click="switchTab('cargo')"
>
船舶作业货物信息
</view>
<view
class="chart-tab"
:class="{ active: activeTab === 'customer' }"
@click="switchTab('customer')"
>
客户排名
</view>
<view
class="chart-tab"
:class="{ active: activeTab === 'location' }"
@click="switchTab('location')"
>
作业地点作业量
</view>
</view>
<view class="chart-content">
<!-- 船舶作业货物信息 -->
<view v-show="activeTab === 'cargo'" class="charts-container">
<view class="chartsMain">
<l-echart ref="chartRef" type="2d" :height="'auto'" />
</view>
<view class="chartsMain">
<l-echart ref="cargoChartRef" type="2d" :height="'auto'" />
</view>
</view>
<!-- 客户排名 -->
<view v-show="activeTab === 'customer'" class="charts-container">
<view class="chartsMain">
<l-echart ref="customerChartRef" type="2d" :height="'auto'" />
</view>
</view>
<!-- 作业地点作业量 -->
<view v-show="activeTab === 'location'" class="charts-container">
<view class="chartsMain">
<l-echart ref="locationChartRef" type="2d" :height="'auto'" />
</view>
</view>
</view>
</view>
<!-- 数据表格 -->
<view class="table-section">
<view class="section-title">详细数据</view>
<view class="table-container">
<view class="data-table">
<view class="table-header">
<view
v-for="header in tableHeaders"
:key="header.key"
class="table-cell header-cell"
>
{{ header.title }}
</view>
</view>
<view
v-for="(row, index) in tableData"
:key="index"
class="table-row"
>
<view
v-for="header in tableHeaders"
:key="header.key"
class="table-cell"
>
{{ row[header.key] }}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from "vue";
import SubHeader from "../components/subHeader.vue";
import * as echarts from "echarts";
// 声明uni全局对象
declare const uni: any;
// 声明全局类型
declare global {
interface Window {
uni: any;
}
}
// 类型定义
interface CardData {
id: number;
title: string;
value: string;
icon: string;
}
interface TableHeader {
key: string;
title: string;
}
interface TableData {
port: string;
berth: string;
ship: string;
cargo: string;
weight: string;
status: string;
}
// 响应式数据
const pageTitle = ref("港口数据");
const activeTab = ref("cargo");
const chartRef = ref<any>(null);
const cargoChartRef = ref<any>(null);
const customerChartRef = ref<any>(null);
const locationChartRef = ref<any>(null);
// 设备信息
const isMobile = ref(false);
const cardData = ref<CardData[]>([
{ id: 1, title: "报港次数", value: "173次", icon: "fas fa-ship" },
{ id: 2, title: "完船次数", value: "197次", icon: "fas fa-anchor" },
{
id: 3,
title: "作业票作业量",
value: "260193.687吨",
icon: "fas fa-weight",
},
{
id: 4,
title: "船舶报港吨数",
value: "218928.400吨",
icon: "fas fa-ship",
},
{
id: 5,
title: "完船作业吨数",
value: "252643.653吨",
icon: "fas fa-anchor",
},
]);
const tableHeaders = ref<TableHeader[]>([
{ key: "port", title: "港口" },
{ key: "berth", title: "泊位" },
{ key: "ship", title: "船舶" },
{ key: "cargo", title: "货物" },
{ key: "weight", title: "重量(吨)" },
{ key: "status", title: "状态" },
]);
const tableData = ref<TableData[]>([
{
port: "上海港",
berth: "A1",
ship: "中远海运",
cargo: "集装箱",
weight: "50000",
status: "作业中",
},
{
port: "宁波港",
berth: "B2",
ship: "马士基",
cargo: "散货",
weight: "75000",
status: "已完成",
},
{
port: "青岛港",
berth: "C3",
ship: "长荣海运",
cargo: "油品",
weight: "30000",
status: "待作业",
},
]);
// 方法
const goBack = () => {
uni.navigateBack();
};
const showMoreOptions = () => {
uni.showActionSheet({
itemList: ["刷新", "返回首页"],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 刷新数据
uni.reLaunch({
url: "/pages/businessDatas/portData",
});
uni.showToast({ title: "数据已刷新", icon: "success" });
break;
//返回首页
case 1:
uni.reLaunch({
url: "/pages/index/index",
});
uni.showToast({ title: "已返回工作台", icon: "success" });
break;
}
},
});
};
// 客户排名原始数据
const customerRankingRawData = [
{ name: "江苏鑫顺通新材料有限公司", value: 64641.52 },
{ name: "连云港耀捷建设工程有限公司", value: 44569.54 },
{ name: "江苏德邦兴华化工科技有限公司", value: 27887.37 },
{ name: "连云港海鲲物流有限公司", value: 19028.98 },
{ name: "张家港红喜物流有限公司", value: 16538.4 },
{ name: "成忠", value: 15557.77 },
{ name: "江苏米耘环保科技有限公司", value: 13952.03 },
{ name: "南京恒之通船务有限公司", value: 12684 },
{ name: "连云港市瑞豪贸易有限公司", value: 8098 },
{ name: "佰鼎风禾(江苏)供应链管理有限公司", value: 6546.48 },
];
// 作业地点作业量原始数据
const locationWorkloadRawData = [
{ name: "抓1", value: 12163.26 },
{ name: "抓2", value: 11872.35 },
{ name: "倒台", value: 93673.52 },
{ name: "1#", value: 59712.06 },
{ name: "2#", value: 9359.546 },
{ name: "3#", value: 8825.58 },
{ name: "4#", value: 14898.896 },
{ name: "5#", value: 19450.075 },
{ name: "6#", value: 17650.25 },
{ name: "7#", value: 21368.56 },
{ name: "8#", value: 35275.316 },
{ name: "抓2对面", value: 222.0 },
{ name: "二期大库", value: 9.1 },
{ name: "调度室后", value: 679.966 },
{ name: "直灌", value: 0 },
];
// 自动排序客户排名数据
const customerRankingData = (() => {
// 按数值从小到大排序
const sortedData = customerRankingRawData.sort((a, b) => a.value - b.value);
return {
legend: ["10月"],
yAxisData: sortedData.map((item) => item.name),
seriesData: sortedData.map((item) => item.value),
};
})();
// 作业地点作业量数据(不排序,按原始顺序)
const locationWorkloadData = {
legend: ["10月"],
yAxisData: locationWorkloadRawData.map((item) => item.name),
seriesData: locationWorkloadRawData.map((item) => item.value),
};
// 初始化客户排名图表
const initCustomerChart = (chart: any) => {
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
legend: {
show: false,
},
grid: {
left: "3%",
right: "8%",
bottom: "3%",
top: "5%",
containLabel: true,
},
xAxis: {
type: "value",
boundaryGap: [0, 0.01],
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
},
yAxis: {
type: "category",
data: customerRankingData.yAxisData,
axisLabel: {
fontSize: 12,
width: 80,
overflow: "truncate",
},
},
series: [
{
name: customerRankingData.legend[0],
type: "bar",
data: customerRankingData.seriesData,
label: {
show: true,
position: "right",
formatter: "{c}",
fontSize: 11,
color: "#333",
},
},
],
};
chart.setOption(option);
console.log("客户排名图表初始化成功");
};
// 初始化作业地点作业量图表
const initLocationChart = (chart: any) => {
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
legend: {
show: false,
},
grid: {
left: "3%",
right: "8%",
bottom: "3%",
top: "5%",
containLabel: true,
},
xAxis: {
type: "value",
boundaryGap: [0, 0.01],
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
},
yAxis: {
type: "category",
data: locationWorkloadData.yAxisData,
axisLabel: {
fontSize: 12,
width: 80,
overflow: "truncate",
},
},
series: [
{
name: locationWorkloadData.legend[0],
type: "bar",
data: locationWorkloadData.seriesData,
label: {
show: true,
position: "right",
formatter: "{c}",
fontSize: 11,
color: "#333",
},
},
],
};
chart.setOption(option);
console.log("作业地点作业量图表初始化成功");
};
// 切换tab
const switchTab = (tab: string) => {
activeTab.value = tab;
// 如果切换到客户排名tab,确保图表正确显示
if (tab === "customer") {
nextTick(() => {
setTimeout(() => {
if (customerChartRef.value) {
customerChartRef.value.init(echarts, initCustomerChart);
}
}, 100);
});
}
// 如果切换到作业地点作业量tab,确保图表正确显示
if (tab === "location") {
nextTick(() => {
setTimeout(() => {
if (locationChartRef.value) {
locationChartRef.value.init(echarts, initLocationChart);
}
}, 100);
});
}
};
// 生命周期
onMounted(() => {
// 检测设备类型
try {
const systemInfo = uni.getSystemInfoSync();
isMobile.value =
systemInfo.platform !== "devtools" &&
(systemInfo.platform === "ios" || systemInfo.platform === "android");
} catch (e) {
isMobile.value = false;
}
// 初始化第一个图表 - 货物类型统计
chartRef.value.init(echarts, (chart) => {
const option = {
title: {
text: "货物类型统计",
subtext: "各类型作业量分布",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "horizontal",
bottom: "bottom",
itemGap: 10,
itemWidth: 14,
itemHeight: 14,
textStyle: {
fontSize: 12,
},
},
series: [
{
name: "作业类型",
type: "pie",
radius: "50%",
center: ["50%", "50%"],
data: [
{ value: 67187.753, name: "门机件杂货" },
{ value: 26582.55, name: "门机散货" },
{ value: 15576.03, name: "抓机散货" },
{ value: 82319.55, name: "倒台散货" },
{ value: 45963.8, name: "挖机散货" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
chart.setOption(option);
});
// 初始化第二个图表 - 货物品类统计
cargoChartRef.value.init(echarts, (chart) => {
const option = {
title: {
text: "货物品类统计",
subtext: "各类货物占比分布",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
right: "right",
itemGap: 5,
itemWidth: 14,
itemHeight: 14,
textStyle: {
fontSize: 12,
},
},
series: [
{
name: "货物品类",
type: "pie",
radius: "50%",
center: ["50%", "55%"],
data: [
{ value: 64641.52, name: "铁精粉" },
{ value: 44734.8, name: "PTA" },
{ value: 44569.54, name: "粉质粘土" },
{ value: 33427.37, name: "盐" },
{ value: 15557.77, name: "石粉砂" },
{ value: 13952.03, name: "低卡煤" },
{ value: 4804.04, name: "氯化钾" },
{ value: 4140.0, name: "元明粉" },
{ value: 3664.0, name: "轻碱" },
{ value: 23152.583, name: "其他" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
chart.setOption(option);
});
// 初始化客户排名图表 - 延迟初始化
setTimeout(() => {
if (customerChartRef.value) {
customerChartRef.value.init(echarts, initCustomerChart);
}
}, 500);
// 初始化作业地点作业量图表 - 延迟初始化
setTimeout(() => {
if (locationChartRef.value) {
locationChartRef.value.init(echarts, initLocationChart);
}
}, 500);
});
</script>
<style scoped>
.data-detail-page {
min-height: 100vh;
background: var(--background);
padding-top: 88rpx;
}
/* 移动设备需要额外的状态栏高度 */
.data-detail-page.mobile {
padding-top: calc(var(--status-bar-height) + 88rpx);
}
/* 支持安全区域的设备 */
@supports (padding: max(0px)) {
.data-detail-page.mobile {
padding-top: calc(
var(--status-bar-height) + 88rpx + env(safe-area-inset-top)
);
}
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: var(--title-color);
margin-bottom: 24rpx;
padding: 0 30rpx;
}
/* 卡片样式 */
.cards-section {
padding: 30rpx 0;
}
.card-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 30rpx;
}
.data-card {
background: var(--surface);
border-radius: 16rpx;
padding: 30rpx;
box-shadow: var(--shadow);
display: flex;
align-items: center;
gap: 20rpx;
border: 1rpx solid var(--border-light);
}
.card-icon {
width: 80rpx;
height: 80rpx;
background: var(--gradient-primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.card-icon i {
font-size: 32rpx;
color: var(--white);
}
.card-content {
flex: 1;
min-width: 0;
}
.card-value {
display: block;
font-size: 28rpx;
font-weight: 600;
color: var(--title-color);
margin-bottom: 8rpx;
}
.card-label {
display: block;
font-size: 24rpx;
color: var(--text-color);
}
/* 图表样式 */
.charts-section {
padding: 30rpx 0;
}
.chart-tabs {
display: flex;
padding: 0 30rpx;
margin-bottom: 30rpx;
overflow-x: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
white-space: nowrap;
}
.chart-tabs::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
.chart-tab {
flex-shrink: 0;
padding: 16rpx 32rpx;
margin-right: 16rpx;
background: var(--gray-light);
border-radius: 32rpx;
font-size: 28rpx;
color: var(--text-color);
transition: all 0.3s ease;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.chart-tab:last-child {
margin-right: 0;
}
.chart-tab.active {
background: var(--primary-color);
color: var(--white);
box-shadow: var(--shadow-md);
}
.chart-content {
padding: 0 30rpx;
}
/* vchart样式 */
.chartsMain {
width: 100%;
min-height: 400rpx;
padding: 15rpx;
background: var(--surface);
margin-bottom: 24rpx;
border-radius: 12rpx;
box-shadow: var(--shadow-md);
border: 1rpx solid var(--border-light);
box-sizing: border-box;
position: relative;
overflow: hidden;
}
/* 移动端优化 */
@media screen and (max-width: 768px) {
.chartsMain {
min-height: 300rpx;
}
}
/* 柱状图特殊样式 - 支持横向滚动 */
.chartsMain.bar-chart {
overflow-x: auto;
overflow-y: hidden;
}
/* l-echart组件样式 */
.chartsMain :deep(.l-echart) {
width: 100% !important;
height: auto !important;
min-height: 300px !important;
background-color: transparent !important;
}
/* 柱状图横向滚动时的样式 */
.chartsMain.bar-chart :deep(.l-echart) {
min-width: 800rpx;
width: auto !important;
}
/* 表格样式 */
.table-section {
padding: 30rpx 0;
}
.table-container {
padding: 0 30rpx;
}
.data-table {
background: var(--surface);
border-radius: 16rpx;
overflow: hidden;
box-shadow: var(--shadow);
border: 1rpx solid var(--border-light);
}
.table-header {
background: var(--gray-lighter);
display: flex;
}
.table-row {
display: flex;
border-bottom: 1rpx solid var(--border-light);
}
.table-row:last-child {
border-bottom: none;
}
.table-cell {
flex: 1;
padding: 24rpx 16rpx;
font-size: 26rpx;
color: var(--text-color);
text-align: center;
border-right: 1rpx solid var(--border-light);
word-break: break-all;
}
.table-cell:last-child {
border-right: none;
}
.header-cell {
font-weight: 600;
color: var(--title-color);
}
</style>