增加项目成本页面

This commit is contained in:
砂糖
2025-10-14 13:58:33 +08:00
parent d0520e7872
commit de492664c3
10 changed files with 715 additions and 297 deletions

View File

@@ -1,8 +1,8 @@
<template>
<view class="charts-box">
<qiun-data-charts
type="column"
:chartData="chartData"
<qiun-data-charts
type="column"
:chartData="chartData"
:opts="chartOpts"
/>
</view>
@@ -16,7 +16,7 @@ export default {
chartData: {
type: Object,
default: {
categories: [], // 日期1日-当前日或当月总天数)
categories: [],
series: [] // 库存趋势数据
}
}
@@ -25,9 +25,18 @@ export default {
return {
// 图表配置
chartOpts: {
title: { text:'月度收支', left:'center' },
tooltip:{ trigger:'axis' },
legend:{ data:['支出','收入(CNY)','收入(USD)'], bottom:0 },
legend: true, // 显示图例
dataLabel: false, // 不显示数据标签
xAxis: {
fontColor: "#666",
fontSize: 12
},
yAxis: {
fontColor: "#666",
fontSize: 12,
gridColor: "#eee"
},
color: ["#007aff", "#ff9500", '#22bbff']
}
};
},

View File

@@ -0,0 +1,241 @@
<template>
<view class="cost-detail-container">
<!-- 新增根元素包裹所有内容 -->
<view class="content-wrapper">
<!-- 1. Tab 切换 -->
<view class="tab-header">
<view
class="tab-item"
:class="activeTab === 'material' ? 'tab-active' : ''"
@click="activeTab = 'material'"
>
物料花费详情
</view>
<view
class="tab-item"
:class="activeTab === 'user' ? 'tab-active' : ''"
@click="activeTab = 'user'"
>
人力成本
</view>
</view>
<!-- 2. 加载状态 -->
<view v-if="loading" class="loading-view">
<uni-loading-icon size="24" color="#007aff"></uni-loading-icon>
<text class="loading-text">加载中...</text>
</view>
<!-- 3. 物料花费详情 Tab -->
<view v-else-if="activeTab === 'material'" class="list-container">
<!-- 卡片列表 -->
<view
class="list-card"
v-for="(item, index) in detailData.materialList"
:key="item.id || index"
>
<view class="field-group">
<text class="field-label">出账名称</text>
<text class="field-value">{{ item.detailTitle || '-' }}</text>
</view>
<view class="field-group">
<text class="field-label">金额</text>
<text class="field-value text-red">-{{ item.price || 0 }}</text>
</view>
<view class="field-group">
<text class="field-label">经手人</text>
<text class="field-value">{{ item.financeParties || '-' }}</text>
</view>
<view class="field-group field-remark">
<text class="field-label">备注</text>
<text class="field-value">{{ item.remark || '无备注' }}</text>
</view>
</view>
<!-- 空数据提示 -->
<view v-if="!detailData.materialList.length" class="empty-tip">
<text>暂无物料花费数据</text>
</view>
</view>
<!-- 4. 人力成本 Tab -->
<view v-else-if="activeTab === 'user'" class="list-container">
<!-- 卡片列表 -->
<view
class="list-card"
v-for="(item, index) in detailData.userCostList"
:key="item.id || index"
>
<view class="field-group">
<text class="field-label">人员名称</text>
<text class="field-value">{{ item.nickName || '-' }}</text>
</view>
<view class="field-group">
<text class="field-label">人员成本</text>
<text class="field-value text-blue">{{ item.laborCost || 0 }}</text>
</view>
<view class="field-group">
<text class="field-label">人天计算</text>
<text class="field-value">{{ item.attendanceNum || '-' }}</text>
</view>
</view>
<!-- 空数据提示 -->
<view v-if="!detailData.userCostList.length" class="empty-tip">
<text>暂无人力成本数据</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
detailData: {
required: true,
type: Object,
default: () => ({
materialList: [],
userCostList: []
})
},
loading: {
type: Boolean,
default: false
}
},
data() {
return {
activeTab: 'material'
};
}
};
</script>
<style scoped>
.cost-detail-container {
width: 100vw;
max-height: 90vh;
padding: 12rpx;
overflow-y: scroll;
box-sizing: border-box;
background-color: #f5f5f5;
}
/* 新增的根容器样式(可根据需要调整) */
.content-wrapper {
width: 100%;
}
.tab-header {
display: flex;
flex-direction: row;
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 8rpx;
overflow: hidden;
}
.tab-item {
flex: 1;
text-align: center;
padding: 18rpx 0;
font-size: 26rpx;
color: #666;
cursor: pointer;
}
.tab-active {
color: #007aff;
font-weight: 500;
position: relative;
}
.tab-active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 4rpx;
background-color: #007aff;
}
.loading-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 150rpx 0;
}
.loading-text {
margin-top: 20rpx;
font-size: 24rpx;
color: #666;
}
.list-container {
width: 100%;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.list-card {
background-color: #fff;
border-radius: 12rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.field-group {
display: flex;
flex-direction: row;
margin-bottom: 18rpx;
align-items: flex-start;
}
.field-group:last-child {
margin-bottom: 0;
}
.field-label {
width: 140rpx;
font-size: 24rpx;
color: #999;
white-space: nowrap;
}
.field-value {
flex: 1;
font-size: 24rpx;
color: #333;
line-height: 36rpx;
}
.text-red {
color: #ff4d4f;
}
.text-blue {
color: #007aff;
}
.field-remark .field-value {
word-wrap: break-word;
word-break: break-all;
}
.empty-tip {
display: flex;
justify-content: center;
align-items: center;
padding: 150rpx 0;
font-size: 24rpx;
color: #999;
background-color: #fff;
border-radius: 12rpx;
margin-top: 16rpx;
}
</style>

View File

@@ -1,102 +1,60 @@
<template>
<view class="charts-box">
<qiun-data-charts
type="line"
:chartData="chartData"
:opts="chartOpts"
/>
</view>
<view class="charts-box">
<qiun-data-charts type="line" :chartData="chartData" :opts="chartOpts" />
</view>
</template>
<script>
import { monthDataAnalysis } from "@/api/oa/wms/oaWarehouse";
export default {
data() {
return {
// 图表数据符合uCharts格式
chartData: {
categories: [], // 月份
series: [] // 入库量、出库量数据
},
// 图表配置项
chartOpts: {
legend: true, // 显示图例
dataLabel: false, // 不显示数据标签
column: {
type: "group" // 分组柱状图
},
xAxis: {
fontColor: "#666",
fontSize: 12
},
yAxis: {
fontColor: "#666",
fontSize: 12,
gridColor: "#eee"
},
color: ["#007aff", "#ff9500"] // 入库、出库颜色
}
};
},
mounted() {
// 页面就绪后获取数据
this.getServerData();
},
methods: {
getServerData() {
// 显示加载提示
uni.showLoading({
title: '加载数据中...',
mask: true
});
// 调用接口获取数据(当前年份作为参数)
const currentYear = new Date().getFullYear() + '-01';
monthDataAnalysis({ month: currentYear }).then(res => {
// 隐藏加载提示
uni.hideLoading();
// 格式化接口数据为uCharts所需格式
this.chartData = {
categories: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
series: [
{
name: "入库量",
data: res.data.inData["月"] || [] // 从接口取入库数据
},
{
name: "出库量",
data: res.data.outData["月"] || [] // 从接口取出库数据
}
]
};
}).catch(err => {
// 隐藏加载提示
uni.hideLoading();
// 错误处理
uni.showToast({
title: '数据加载失败',
icon: 'none',
duration: 2000
});
console.error('获取出入库数据失败:', err);
});
}
}
};
export default {
name: 'InventoryTrend',
// 接收父组件传入的日期参数YYYY-mm格式
props: {
chartData: {
type: Object,
default: {
categories: [],
series: [] // 库存趋势数据
}
}
},
data() {
return {
// 图表配置
chartOpts: {
legend: true, // 显示图例
dataLabel: false, // 不显示数据标签
xAxis: {
fontColor: "#666",
fontSize: 12
},
yAxis: {
fontColor: "#666",
fontSize: 12,
gridColor: "#eee"
},
color: ["#007aff", "#ff9500", '#22bbff'],
extra: {
line: {
type: "straight",
width: 2,
activeType: "hollow"
}
}
}
};
},
};
</script>
<style scoped>
.charts-box {
width: 100%;
height: 300px; /* 固定图表高度,适配手机屏幕 */
padding: 16rpx;
box-sizing: border-box;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
margin-bottom: 20rpx;
}
.charts-box {
width: 100%;
height: 300px;
padding: 16rpx;
box-sizing: border-box;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
margin-bottom: 20rpx;
}
</style>

View File

@@ -14,10 +14,16 @@
},
data() {
return {
chartData: {},
chartData: {
series: []
},
chartOpts: {
color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
padding: [5, 5, 5, 5],
legend: {
show: false
},
dataLabel: false, // 不显示数据标签
enableScroll: false,
extra: {
pie: {
@@ -37,20 +43,29 @@
projectList: {
handler(newVal) {
const pieData = this.projectList
// .filter(p => p.totalPrice > 0)
.filter(p => {
// 过滤 totalPrice 非数字/小于等于0的项
const price = Number(p.totalPrice);
return !isNaN(price) && price > 0;
})
.map(p => ({
name: p.projectName,
value: p.totalPrice,
value: parseInt(p.totalPrice)
}));
this.chartData = {
// this.chartData = {
// series: [{
// data: pieData
// }]
// }
let res = {
series: [{
data: pieData
}]
}
};
this.chartData = JSON.parse(JSON.stringify(res));
},
immediate: true,
},
}
}
</script>

View File

@@ -12,11 +12,11 @@
:key="index"
>
<view class="card-title">{{ card.title }}</view>
<view class="card-value">人民币{{ card.valueCNY }}</view>
<view class="card-value">美元{{ card.valueUSD }}</view>
<view class="card-value">人民币{{ formatNumber(card.valueCNY) }}</view>
<view class="card-value">美元{{ formatNumber(card.valueUSD) }}</view>
</view>
</view>
</scroll-view>
</scroll-view>
</view>
</template>
@@ -37,9 +37,9 @@ export default {
}
},
methods: {
// 格式化数字,添加千位分隔符
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if (isNaN(num)) return '0'; // 兜底处理
return num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 保留2位小数+千位分隔符
}
}
};

View File

@@ -1,9 +1,5 @@
<template>
<view class="recent-records-container">
<view class="table-header">
<text class="title">最近记录</text>
</view>
<scroll-view
scroll-x="true"
class="table-scroll"
@@ -12,12 +8,12 @@
<view class="table-wrapper">
<!-- 表头 -->
<view class="table-row header-row">
<view class="table-cell">物料名称</view>
<view class="table-cell">型号</view>
<view class="table-cell">当前库存</view>
<view class="table-cell">安全库存</view>
<view class="table-cell">在途数量</view>
<view class="table-cell">库存状态</view>
<view class="table-cell">项目名</view>
<view class="table-cell">人力花费</view>
<view class="table-cell">人天计算</view>
<view class="table-cell">物料花费</view>
<view class="table-cell">综合成本</view>
<view class="table-cell">项目总款</view>
</view>
<!-- 表体 -->
@@ -26,20 +22,16 @@
v-for="(item, index) in tableData"
:key="index"
:class="{ 'odd-row': index % 2 === 1 }"
@click="rowClick(item)"
>
<view class="table-cell">{{ item.name }}</view>
<view class="table-cell">{{ item.model }}</view>
<view class="table-cell">{{ item.inventory }}</view>
<view class="table-cell">{{ item.threshold }}</view>
<view class="table-cell">{{ item.taskInventory }}</view>
<view class="table-cell">
<view
class="status-tag"
:class="calcInventoryStatus(item.inventory, item.taskInventory, item.threshold).type"
>
{{ calcInventoryStatus(item.inventory, item.taskInventory, item.threshold).status }}
</view>
</view>
<view class="table-cell">{{ item.projectName }}</view>
<view class="table-cell">{{ item.userCost }}</view>
<view class="table-cell">{{ item.peopleDay }}</view>
<view class="table-cell">{{ item.materialCost }}</view>
<view class="table-cell">{{ item.totalCost }}</view>
<view class="table-cell">
{{ item.funds }}
</view>
</view>
<!-- 空状态 -->
@@ -62,21 +54,30 @@ export default {
default: () => []
}
},
computed: {
renderData() {
return this.tableData.map(item => {
return {
projectName: item.projectName,
userCost: this.formatNumberToWan(item.userCost),
peopleDay: item.peopleDay,
materialCost: this.formatNumberToWan(item.materialCost),
totalCost: this.formatNumberToWan(item.materialCost + item.userCost + item.claimCost),
funds: this.formatNumberToWan(item.funds)
}
})
}
},
methods: {
//
calcInventoryStatus(inventory, inTransit, threshold) {
const total = inventory + inTransit;
if (total > threshold + 10) {
return { status: '库存积压', type: 'warning' };
} else if (total > threshold) {
return { status: '正常', type: 'success' };
} else if (total <= threshold && total > threshold - 2) {
return { status: '库存预警', type: 'danger' };
} else {
return { status: '库存不足', type: 'info' };
}
}
formatNumberToWan(num) {
if (num === 0) return 0;
const wanNum = num / 10000;
//
return Math.round(wanNum * 100) / 100;
},
rowClick(item) {
this.$emit('row', item);
}
}
};
</script>
@@ -87,7 +88,9 @@ export default {
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
overflow-y: scroll;
max-height: 800rpx;
overflow-x: hidden;
}
.table-header {