| | |
| | | </el-form> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="leftTableColumn" |
| | | :tableData="leftTableData" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | @row-click="handleLeftRowClick" |
| | | @pagination="pagination" |
| | | ></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> |
| | | 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 {ElMessageBox} from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import {salesLedgerProductionAccountingListProductionDetails, salesLedgerProductionAccountingList} from "@/api/productionManagement/productionCosting.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | salesLedgerProductionAccountingListProductionDetails, |
| | | salesLedgerProductionAccountingList |
| | | } from "@/api/productionManagement/productionCosting.js"; |
| | | |
| | | const {proxy} = getCurrentInstance(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "生产日期", |
| | | prop: "schedulingDate", |
| | | { |
| | | label: "生产日期", |
| | | prop: "schedulingDate", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "生产人", |
| | | prop: "schedulingUserName", |
| | | }, |
| | | { |
| | | label: "生产人", |
| | | prop: "schedulingUserName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "合同号", |
| | | prop: "salesContractNo", |
| | | }, |
| | | { |
| | | label: "合同号", |
| | | prop: "salesContractNo", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "客户名称", |
| | | prop: "customerName", |
| | | }, |
| | | { |
| | | label: "客户名称", |
| | | prop: "customerName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "产品大类", |
| | | prop: "productName", |
| | | }, |
| | | { |
| | | label: "产品大类", |
| | | prop: "productName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "规格型号", |
| | | prop: "productModelName", |
| | | }, |
| | | { |
| | | label: "规格型号", |
| | | prop: "productModelName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "工序", |
| | | prop: "process", |
| | | }, |
| | | { |
| | | label: "工序", |
| | | prop: "process", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "生产数量", |
| | | prop: "quantity", |
| | | }, |
| | | { |
| | | label: "生产数量", |
| | | prop: "quantity", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "工时定额", |
| | | prop: "workHours", |
| | | }, |
| | | { |
| | | label: "工时定额", |
| | | prop: "workHours", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "工资", |
| | | prop: "wages", |
| | | }, |
| | | { |
| | | label: "工资", |
| | | prop: "wages", |
| | | minWidth: 100, |
| | | }, |
| | | }, |
| | | { |
| | | label: "机台", |
| | | prop: "deviceName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "工时(分钟)", |
| | | prop: "workMinutes", |
| | | width: 110, |
| | | }, |
| | | ]); |
| | | |
| | | // 左侧汇总台账列(生产人、产量、工资、合格率) |
| | | const leftTableColumn = ref([ |
| | | { |
| | | label: "生产人", |
| | | prop: "schedulingUserName", |
| | | { |
| | | label: "生产人", |
| | | prop: "schedulingUserName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "产量", |
| | | prop: "outputNum", |
| | | }, |
| | | { |
| | | label: "产量", |
| | | prop: "outputNum", |
| | | minWidth: 100, |
| | | |
| | | }, |
| | | { |
| | | label: "工资", |
| | | prop: "wages", |
| | | { |
| | | label: "工资", |
| | | prop: "wages", |
| | | minWidth: 100, |
| | | |
| | | }, |
| | | { |
| | | label: "合格率", |
| | | prop: "outputRate", |
| | | }, |
| | | { |
| | | 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, |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | |
| | | const page1 = reactive({ |
| | |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | schedulingUserName: "", |
| | | salesContractNo: "", |
| | | searchForm: { |
| | | schedulingUserName: "", |
| | | salesContractNo: "", |
| | | dateType: "day", |
| | | dateRange: dayjs().format("YYYY-MM-DD"), |
| | | entryDate: dayjs().format("YYYY-MM-DD"), |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | entryDate: dayjs().format("YYYY-MM-DD"), |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const {searchForm} = toRefs(data); |
| | | |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const pagination1 = (obj) => { |
| | | page1.current = obj.page; |
| | | page1.size = obj.limit; |
| | | getList1(); |
| | | getList1(); |
| | | }; |
| | | |
| | | const handleDateRangeChange = (value) => { |
| | | if (value) { |
| | | if (value) { |
| | | if (searchForm.value.dateType === "day") { |
| | | searchForm.value.entryDate = value; |
| | | } else { |
| | |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | |
| | | } else { |
| | | searchForm.value.entryDate = undefined; |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | } |
| | | } else { |
| | | searchForm.value.entryDate = undefined; |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | } |
| | | reloadData() |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | tableLoading.value = true; |
| | | const params = {...searchForm.value, ...page}; |
| | | |
| | | salesLedgerProductionAccountingList(params).then((res) => { |
| | | const records = res.data.records || []; |
| | | leftTableData.value = records; |
| | | page.total = res.data.total || 0; |
| | | }).finally(() => { |
| | | 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 }; |
| | | const params = {...page1, ...searchForm.value}; |
| | | salesLedgerProductionAccountingListProductionDetails(params).then((res) => { |
| | | tableData.value = res.data.records || [];; |
| | | 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 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 |
| | |
| | | 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(); |
| | | searchForm.value.schedulingUserName = row.schedulingUserName || ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 查询列表 |
| | |
| | | |
| | | // 导出 |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/salesLedger/productionAccounting/export", {}, "生产核算.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/salesLedger/productionAccounting/export", {}, "生产核算.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |