<template>
|
<div class="app-container">
|
<el-row :gutter="16" class="content-row">
|
<!-- 左侧台账 + 顶部筛选 -->
|
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8" class="left-col">
|
<div class="left-panel">
|
<div class="left-header">
|
<el-form :model="searchForm" inline>
|
<el-form-item prop="dateType">
|
<el-radio-group v-model="searchForm.dateType" size="small" @change="handleDateTypeChange">
|
<el-radio-button label="day">日</el-radio-button>
|
<el-radio-button label="month">月</el-radio-button>
|
</el-radio-group>
|
</el-form-item>
|
|
<el-form-item label="日期:" prop="dateRange">
|
<el-date-picker
|
v-model="searchForm.dateRange"
|
:type="searchForm.dateType === 'day' ? 'date' : 'daterange'"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
style="width: 200px"
|
@change="handleDateRangeChange"
|
/>
|
</el-form-item>
|
</el-form>
|
</div>
|
<PIMTable
|
rowKey="id"
|
:column="leftTableColumn"
|
:tableData="leftTableData"
|
:tableLoading="tableLoading"
|
:page="page"
|
@row-click="handleLeftRowClick"
|
@pagination="pagination"
|
>
|
<template #workDuration="{ row }">
|
<el-button v-if="Number(row.totalWorkMinutes) > 0"
|
type="primary"
|
text
|
@click.stop="openWorkDurationDialog(row)">
|
{{ row.totalWorkMinutes }}
|
</el-button>
|
<span v-else>-</span>
|
</template>
|
</PIMTable>
|
</div>
|
</el-col>
|
|
<!-- 右侧明细 -->
|
<el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16" class="right-col">
|
<div class="right-panel">
|
|
<el-form inline>
|
<el-form-item>
|
<el-button type="primary" @click="handleOut">导出</el-button>
|
</el-form-item>
|
</el-form>
|
<PIMTable
|
rowKey="id"
|
:column="tableColumn"
|
:tableData="tableData"
|
:page="page1"
|
:tableLoading="tableLoading1"
|
style="margin-right: 20px;"
|
@pagination="pagination1"
|
></PIMTable>
|
</div>
|
</el-col>
|
</el-row>
|
<el-dialog v-model="workDurationDialogVisible"
|
:title="`工时明细${currentSchedulingUserName ? ` - ${currentSchedulingUserName}` : ''}`"
|
width="600px">
|
<el-table :data="workDurationDetailList"
|
border
|
style="width: 100%; margin-bottom: 16px;">
|
<el-table-column type="index"
|
label="序号"
|
width="80"/>
|
<el-table-column prop="deviceName"
|
label="机台"
|
min-width="220"/>
|
<el-table-column prop="workMinutes"
|
label="工作时间(分钟)"
|
min-width="160"/>
|
</el-table>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import {onMounted, ref} from "vue";
|
import {ElMessageBox} from "element-plus";
|
import dayjs from "dayjs";
|
import {
|
salesLedgerProductionAccountingListProductionDetails,
|
salesLedgerProductionAccountingList
|
} from "@/api/productionManagement/productionCosting.js";
|
|
const {proxy} = getCurrentInstance();
|
|
const tableColumn = ref([
|
{
|
label: "生产日期",
|
prop: "schedulingDate",
|
minWidth: 100,
|
},
|
{
|
label: "生产人",
|
prop: "schedulingUserName",
|
minWidth: 100,
|
},
|
{
|
label: "合同号",
|
prop: "salesContractNo",
|
minWidth: 100,
|
},
|
{
|
label: "客户名称",
|
prop: "customerName",
|
minWidth: 100,
|
},
|
{
|
label: "产品大类",
|
prop: "productName",
|
minWidth: 100,
|
},
|
{
|
label: "规格型号",
|
prop: "productModelName",
|
minWidth: 100,
|
},
|
{
|
label: "单位",
|
prop: "unit",
|
minWidth: 100,
|
},
|
{
|
label: "工序",
|
prop: "process",
|
minWidth: 100,
|
},
|
{
|
label: "生产数量",
|
prop: "quantity",
|
minWidth: 100,
|
},
|
{
|
label: "工时定额",
|
prop: "workHours",
|
minWidth: 100,
|
},
|
{
|
label: "工资",
|
prop: "wages",
|
minWidth: 100,
|
},
|
{
|
label: "机台",
|
prop: "deviceName",
|
minWidth: 100,
|
},
|
{
|
label: "工时(分钟)",
|
prop: "workMinutes",
|
width: 110,
|
},
|
]);
|
|
// 左侧汇总台账列(生产人、产量、工资、合格率)
|
const leftTableColumn = ref([
|
{
|
label: "生产人",
|
prop: "schedulingUserName",
|
minWidth: 100,
|
},
|
{
|
label: "产量",
|
prop: "outputNum",
|
minWidth: 100,
|
|
},
|
{
|
label: "工资",
|
prop: "wages",
|
minWidth: 100,
|
|
},
|
{
|
label: "合格率",
|
prop: "outputRate",
|
minWidth: 100,
|
formatData: (val) => {
|
if (val == null || val === '') return '-'
|
return parseFloat(val).toFixed(2)
|
},
|
},
|
{
|
label: "工时(分钟)",
|
prop: "totalWorkMinutes",
|
minWidth: 120,
|
dataType: "slot",
|
slot: "workDuration",
|
},
|
]);
|
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const tableLoading1 = ref(false);
|
const leftTableData = ref([]);
|
const workDurationDialogVisible = ref(false);
|
const workDurationDetailList = ref([]);
|
const currentSchedulingUserName = ref("");
|
// 日 / 月 切换(默认按日)
|
const page = reactive({
|
current: 1,
|
size: 100,
|
total: 0,
|
});
|
|
const page1 = reactive({
|
current: 1,
|
size: 100,
|
total: 0,
|
});
|
|
const data = reactive({
|
searchForm: {
|
schedulingUserName: "",
|
salesContractNo: "",
|
dateType: "day",
|
dateRange: dayjs().format("YYYY-MM-DD"),
|
entryDate: dayjs().format("YYYY-MM-DD"),
|
entryDateStart: undefined,
|
entryDateEnd: undefined,
|
},
|
});
|
const {searchForm} = toRefs(data);
|
|
const pagination = (obj) => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
|
const pagination1 = (obj) => {
|
page1.current = obj.page;
|
page1.size = obj.limit;
|
getList1();
|
};
|
|
const handleDateRangeChange = (value) => {
|
if (value) {
|
if (searchForm.value.dateType === "day") {
|
searchForm.value.entryDate = value;
|
} else {
|
searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
|
searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
|
}
|
|
} else {
|
searchForm.value.entryDate = undefined;
|
searchForm.value.entryDateStart = undefined;
|
searchForm.value.entryDateEnd = undefined;
|
}
|
reloadData()
|
};
|
|
const getList = () => {
|
tableLoading.value = true;
|
const params = {...searchForm.value, ...page};
|
|
salesLedgerProductionAccountingList(params).then((res) => {
|
const records = res.data.records || [];
|
leftTableData.value = records.map(item => {
|
const workDurationDetailListValue = buildWorkDurationDetailList(item.deviceWorkInfoPairList);
|
const totalWorkMinutes = workDurationDetailListValue.reduce(
|
(sum, detail) => sum + Number(detail.workMinutes || 0),
|
0
|
);
|
return {
|
...item,
|
workDurationDetailList: workDurationDetailListValue,
|
totalWorkMinutes,
|
};
|
});
|
page.total = res.data.total || 0;
|
}).finally(() => {
|
tableLoading.value = false;
|
})
|
|
|
};
|
|
const getList1 = () => {
|
tableLoading1.value = true;
|
const params = {...page1, ...searchForm.value};
|
salesLedgerProductionAccountingListProductionDetails(params).then((res) => {
|
tableData.value = res.data.records || [];
|
;
|
page1.total = res.data.total || 0;
|
}).finally(() => {
|
tableLoading1.value = false;
|
})
|
};
|
|
// 构建左侧汇总台账(按生产人汇总产量、工资等)
|
const buildLeftTableData = (records) => {
|
const map = {};
|
records.forEach((item) => {
|
const key = item.schedulingUserName || "未知";
|
if (!map[key]) {
|
map[key] = {
|
id: key,
|
schedulingUserName: key,
|
finishedNum: 0,
|
wages: 0,
|
qualifiedRate: item.qualifiedRate ?? null,
|
};
|
}
|
map[key].finishedNum += Number(item.finishedNum || 0);
|
map[key].wages += Number(item.wages || 0);
|
if (item.qualifiedRate != null) {
|
map[key].qualifiedRate = item.qualifiedRate;
|
}
|
});
|
leftTableData.value = Object.values(map);
|
};
|
|
// 左侧日/月切换
|
const handleDateTypeChange = (value) => {
|
// 这里只作为筛选条件的一部分,直接重新查询列表
|
if (value === "day") {
|
searchForm.value.entryDate = dayjs().format("YYYY-MM-DD");
|
searchForm.value.dateRange = searchForm.value.entryDate
|
} else {
|
searchForm.value.entryDateStart = dayjs().startOf("month").format("YYYY-MM-DD");
|
searchForm.value.entryDateEnd = dayjs().endOf("month").format("YYYY-MM-DD");
|
searchForm.value.dateRange = [searchForm.value.entryDateStart, searchForm.value.entryDateEnd]
|
}
|
|
reloadData()
|
};
|
|
const reloadData = () => {
|
page.current = 1;
|
page1.current = 1;
|
getList();
|
tableData.value = []
|
}
|
|
const buildWorkDurationDetailList = (deviceWorkInfoPairList) => {
|
if (!deviceWorkInfoPairList) {
|
return [];
|
}
|
let listData = deviceWorkInfoPairList;
|
if (typeof deviceWorkInfoPairList === "string") {
|
try {
|
listData = JSON.parse(deviceWorkInfoPairList);
|
} catch {
|
return [];
|
}
|
}
|
if (!Array.isArray(listData)) {
|
return [];
|
}
|
const details = [];
|
listData.forEach(item => {
|
if (!item || typeof item !== "object") {
|
return;
|
}
|
Object.entries(item).forEach(([deviceName, workMinutes]) => {
|
const numericMinutes = Number(workMinutes);
|
if (deviceName) {
|
details.push({
|
deviceName,
|
workMinutes: Number.isFinite(numericMinutes) ? numericMinutes : 0,
|
});
|
}
|
});
|
});
|
return details;
|
};
|
|
const openWorkDurationDialog = row => {
|
currentSchedulingUserName.value = row?.schedulingUserName || "";
|
const details = Array.isArray(row?.workDurationDetailList)
|
? row.workDurationDetailList
|
: buildWorkDurationDetailList(row?.deviceWorkInfoPairList);
|
workDurationDetailList.value = details;
|
workDurationDialogVisible.value = true;
|
};
|
|
// 点击左侧行,刷右侧明细(按生产人过滤)
|
const handleLeftRowClick = (row) => {
|
searchForm.value.schedulingUserName = row.schedulingUserName || "";
|
handleQuery();
|
};
|
|
// 查询列表
|
/** 搜索按钮操作 */
|
const handleQuery = () => {
|
page1.current = 1;
|
getList1();
|
};
|
|
|
// 导出
|
const handleOut = () => {
|
ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
proxy.download("/salesLedger/productionAccounting/export", {}, "生产核算.xlsx");
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.content-row {
|
width: 100%;
|
}
|
|
.content-row .left-col,
|
.content-row .right-col {
|
margin-bottom: 16px;
|
}
|
|
.left-panel,
|
.right-panel {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
min-width: 0;
|
}
|
|
.left-header {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
}
|
|
.left-title {
|
font-size: 16px;
|
color: #ffffff;
|
}
|
|
.header-filters {
|
display: flex;
|
align-items: center;
|
flex: 1;
|
justify-content: flex-end;
|
gap: 8px;
|
}
|
|
.search_title {
|
color: #ffffff;
|
}
|
|
.ml10 {
|
margin-left: 10px;
|
}
|
</style>
|