天津-潜宇塑胶
1.设备、生产、BOM、质量、产品、仓储物流模块需求更改
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | const fs = require('fs'); |
| | | const path = require('path'); |
| | | const { Document, Packer, Paragraph, TextRun, HeadingLevel } = require('docx'); |
| | | |
| | | const ROOT = path.join(__dirname, '..', '..'); |
| | | const OUT = path.join(ROOT, 'ç³»ç»æ¨¡åæºä»£ç _纯页é¢.docx'); |
| | | |
| | | const viewDirs = [ |
| | | 'src/views/basicData', |
| | | 'src/views/salesManagement', |
| | | 'src/views/procurementManagement', |
| | | 'src/views/personnelManagement', |
| | | 'src/views/inventoryManagement', |
| | | 'src/views/equipmentManagement', |
| | | ]; |
| | | |
| | | const CODE_FONT = { ascii: 'Consolas', hAnsi: 'Consolas', eastAsia: '微软é
é»' }; |
| | | const PATH_FONT = { ascii: 'Consolas', hAnsi: 'Consolas', eastAsia: '微软é
é»' }; |
| | | |
| | | function collectVue(relDir) { |
| | | const abs = path.join(ROOT, relDir); |
| | | const result = []; |
| | | (function walk(d) { |
| | | for (const e of fs.readdirSync(d, { withFileTypes: true })) { |
| | | const full = path.join(d, e.name); |
| | | if (e.isDirectory()) walk(full); |
| | | else if (e.name.endsWith('.vue')) result.push(full); |
| | | } |
| | | })(abs); |
| | | return result; |
| | | } |
| | | |
| | | // 廿 <style> ... </style> æ ·å¼å |
| | | function stripStyle(code) { |
| | | return code.replace(/<style[\s\S]*?<\/style\s*>/g, ''); |
| | | } |
| | | |
| | | function codeParagraph(code) { |
| | | const lines = code.split(/\r?\n/); |
| | | while (lines.length && lines[lines.length - 1] === '') lines.pop(); |
| | | const runs = lines.map((line, i) => { |
| | | const last = i === lines.length - 1; |
| | | return new TextRun({ text: line, font: CODE_FONT, size: 16, break: last ? undefined : 1 }); |
| | | }); |
| | | return new Paragraph({ children: runs, spacing: { before: 40, after: 200 } }); |
| | | } |
| | | |
| | | async function main() { |
| | | const files = []; |
| | | for (const d of viewDirs) { |
| | | for (const abs of collectVue(d)) { |
| | | files.push({ abs, rel: path.relative(ROOT, abs).replace(/\\/g, '/') }); |
| | | } |
| | | } |
| | | files.sort((a, b) => a.rel.localeCompare(b.rel)); |
| | | |
| | | const children = []; |
| | | for (const f of files) { |
| | | children.push(new Paragraph({ |
| | | heading: HeadingLevel.HEADING_1, |
| | | children: [new TextRun({ text: f.rel, font: PATH_FONT })], |
| | | })); |
| | | children.push(codeParagraph(stripStyle(fs.readFileSync(f.abs, 'utf8')))); |
| | | } |
| | | |
| | | const doc = new Document({ |
| | | styles: { default: { document: { run: { font: CODE_FONT, size: 16 } } } }, |
| | | sections: [{ properties: {}, children }], |
| | | }); |
| | | const buf = await Packer.toBuffer(doc); |
| | | fs.writeFileSync(OUT, buf); |
| | | console.log('å·²çæ:', OUT); |
| | | console.log('æä»¶æ°:', files.length); |
| | | console.log('大å°:', (buf.length / 1024).toFixed(0), 'KB'); |
| | | } |
| | | |
| | | main().catch((e) => { console.error(e); process.exit(1); }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åºåçç¹æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // åèµ·çç¹ |
| | | export function start(data) { |
| | | return request({ |
| | | url: "/stockCheck/start", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // çç¹åå页 |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/stockCheck/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // çç¹å详æ
|
| | | export function getById(id) { |
| | | return request({ |
| | | url: "/stockCheck/" + id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // çç¹æç»å页 |
| | | export function detailPage(query) { |
| | | return request({ |
| | | url: "/stockCheck/detailPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å½å
¥å®ç |
| | | export function entry(data) { |
| | | return request({ |
| | | url: "/stockCheck/entry", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // 宿çç¹ |
| | | export function finish(data) { |
| | | return request({ |
| | | url: "/stockCheck/finish", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // åæ¶çç¹ |
| | | export function cancel(data) { |
| | | return request({ |
| | | url: "/stockCheck/cancel", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤çç¹åï¼body 为 id æ°ç»ï¼å个å é¤ä¼ [id]ï¼ |
| | | export function remove(ids) { |
| | | return request({ |
| | | url: "/stockCheck/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // å·®å¼åæå表 |
| | | export function diffList(query) { |
| | | return request({ |
| | | url: "/stockCheck/diffList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·®å¼åæå¯¼åº |
| | | export function diffListExport(query) { |
| | | return request({ |
| | | url: "/stockCheck/diffList/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // çç¹æç»å¯¼åº |
| | | export function detailExport(query) { |
| | | return request({ |
| | | url: "/stockCheck/detailExport", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // çç¹åå¯¼åº |
| | | export function exportList(query) { |
| | | return request({ |
| | | url: "/stockCheck/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | // åºåé¢è¦å页 |
| | | export const warnPage = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/warnPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // åºåé¢è¦å¯¼åº |
| | | export const warnExport = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/warnExport", |
| | | method: "get", |
| | | params, |
| | | responseType: "blob", |
| | | }); |
| | | }; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // éè´éæ±æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/procurementDemand/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¹éç¶æåæ´ |
| | | export function changeStatus(ids, status) { |
| | | return request({ |
| | | url: "/procurementDemand/changeStatus", |
| | | method: "put", |
| | | params: { |
| | | ids: Array.isArray(ids) ? ids.join(",") : ids, |
| | | status, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | // å é¤ |
| | | export function del(ids) { |
| | | return request({ |
| | | url: "/procurementDemand/del", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // å¯¼åº |
| | | export function exportDemand(query) { |
| | | return request({ |
| | | url: "/procurementDemand/export", |
| | | method: "post", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ææ¬æ ¸ç®åææ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // 1. ææ¬æ ¸ç®æç» |
| | | export function costStat(query) { |
| | | return request({ |
| | | url: "/costAccounting/stat", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 2. ææ¬æææ±æ» |
| | | export function costSummary(query) { |
| | | return request({ |
| | | url: "/costAccounting/summary", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 3. ç¼ºä»·ç©ææ¸
å |
| | | export function missingPrice(query) { |
| | | return request({ |
| | | url: "/costAccounting/missingPrice", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 导åºï¼å为 GET + blobï¼ |
| | | export function costStatExport(query) { |
| | | return request({ |
| | | url: "/costAccounting/stat/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | export function costSummaryExport(query) { |
| | | return request({ |
| | | url: "/costAccounting/summary/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | export function missingPriceExport(query) { |
| | | return request({ |
| | | url: "/costAccounting/missingPrice/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // æææ¶èæ ¸ç®æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ ¸ç®å表 |
| | | export function stat(query) { |
| | | return request({ |
| | | url: "/materialConsumption/stat", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ ¸ç®å¯¼åº |
| | | export function statExport(query) { |
| | | return request({ |
| | | url: "/materialConsumption/stat/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // 计件åä»·é
ç½®æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/pieceRateConfig/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢ |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/pieceRateConfig/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾ |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/pieceRateConfig/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å¯ç¨/åç¨ |
| | | export function changeStatus(data) { |
| | | return request({ |
| | | url: "/pieceRateConfig/changeStatus", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¹éå é¤ |
| | | export function batchDelete(ids) { |
| | | return request({ |
| | | url: "/pieceRateConfig/batchDelete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // å¯¼åº |
| | | export function exportConfig(query) { |
| | | return request({ |
| | | url: "/pieceRateConfig/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // BOMå¯ç¨/åç¨ |
| | | export function changeStatus(data) { |
| | | return request({ |
| | | url: "/technologyBom/changeStatus", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // BOMé½å¥åæ |
| | | export function kitCheck(data) { |
| | | return request({ |
| | | url: "/technologyBom/kitCheck", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // é½å¥åæå¯¼åº |
| | | export function kitCheckExport(data) { |
| | | return request({ |
| | | url: "/technologyBom/kitCheck/export", |
| | | method: "post", |
| | | data: data, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // 缺å£çæéè´éæ± |
| | | export function generateDemand(data) { |
| | | return request({ |
| | | url: "/technologyBom/kitCheck/generateDemand", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ç产ç»è®¡æ¥å£ï¼è®¡ä»¶å·¥èµæ±æ» + 产éç»è®¡ï¼ |
| | | import request from "@/utils/request"; |
| | | |
| | | // è®¡ä»¶å·¥èµæ±æ» |
| | | export function wageSummary(query) { |
| | | return request({ |
| | | url: "/productionProductMain/wageSummary", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // è®¡ä»¶å·¥èµæ±æ»å¯¼åº |
| | | export function wageSummaryExport(query) { |
| | | return request({ |
| | | url: "/productionProductMain/wageSummary/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // 产éç»è®¡ |
| | | export function outputStat(query) { |
| | | return request({ |
| | | url: "/productionProductMain/outputStat", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 产éç»è®¡å¯¼åº |
| | | export function outputStatExport(query) { |
| | | return request({ |
| | | url: "/productionProductMain/outputStat/export", |
| | | method: "get", |
| | | params: query, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // æ£åè¿½æº¯ï¼æåæ¹å· -> å
¨é¾è·¯ |
| | | export function forwardTrace(batchNo) { |
| | | return request({ |
| | | url: '/quality/trace/forward', |
| | | method: 'get', |
| | | params: { batchNo }, |
| | | }) |
| | | } |
| | | |
| | | // åå追溯ï¼åææ¹å· -> åå½±åæå |
| | | export function reverseTrace(batchNo) { |
| | | return request({ |
| | | url: '/quality/trace/reverse', |
| | | method: 'get', |
| | | params: { batchNo }, |
| | | }) |
| | | } |
| | | |
| | | // æ¹å·åéï¼æç´¢ä¸æï¼ï¼type: finished | material |
| | | export function batchOptions(type, keyword, limit = 50) { |
| | | return request({ |
| | | url: '/quality/trace/batchOptions', |
| | | method: 'get', |
| | | params: { type, keyword, limit }, |
| | | }) |
| | | } |
| | | |
| | | // æ£åè¿½æº¯å¯¼åº |
| | | export function forwardExport(batchNo) { |
| | | return request({ |
| | | url: '/quality/trace/forward/export', |
| | | method: 'get', |
| | | params: { batchNo }, |
| | | responseType: 'blob', |
| | | }) |
| | | } |
| | | |
| | | // ååè¿½æº¯å¯¼åº |
| | | export function reverseExport(batchNo) { |
| | | return request({ |
| | | url: '/quality/trace/reverse/export', |
| | | method: 'get', |
| | | params: { batchNo }, |
| | | responseType: 'blob', |
| | | }) |
| | | } |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¡å½¢ç ï¼" |
| | | prop="barcode"> |
| | | <el-input v-model="modelForm.barcode" |
| | | placeholder="请è¾å
¥æ¡å½¢ç ï¼éå¡«ï¼" |
| | | clearable |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "æ¡å½¢ç ", |
| | | prop: "barcode", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | |
| | | model: "", |
| | | unit: "", |
| | | productCode: "", |
| | | barcode: "", |
| | | }, |
| | | modelRules: { |
| | | model: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | modelForm.value.model = ""; |
| | | modelForm.value.unit = ""; |
| | | modelForm.value.productCode = ""; |
| | | modelForm.value.barcode = ""; |
| | | modelForm.value.id = ""; |
| | | if (type === "edit") { |
| | | modelForm.value = { ...data }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="check-detail"> |
| | | <div class="detail-header"> |
| | | <span class="detail-title">çç¹è¯¦æ
</span> |
| | | <el-button link |
| | | type="primary" |
| | | @click="handleBack">è¿åå表</el-button> |
| | | </div> |
| | | |
| | | <el-descriptions :column="3" |
| | | border |
| | | style="margin-bottom: 16px"> |
| | | <el-descriptions-item label="çç¹åå·"> |
| | | {{ main.checkNo || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="çç¹åç§°"> |
| | | {{ main.checkName || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="çç¹æ¥æ"> |
| | | {{ main.checkDate || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="çç¹äºº"> |
| | | {{ main.checker || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ"> |
| | | <el-tag :type="statusTypeMap[mainStatus] || 'info'"> |
| | | {{ statusTextMap[mainStatus] || "æªç¥" }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç§ç±»æ°/差弿°"> |
| | | {{ main.totalKinds ?? 0 }} / {{ main.diffKinds ?? 0 }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" |
| | | :span="3"> |
| | | {{ main.remark || "-" }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="detail-toolbar"> |
| | | <div> |
| | | <span style="margin-right: 8px">åªçå·®å¼</span> |
| | | <el-switch v-model="onlyDiff" |
| | | @change="handleQuery" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" |
| | | :disabled="readOnly" |
| | | @click="saveEntry">ä¿åå½å
¥</el-button> |
| | | <el-button type="success" |
| | | :disabled="readOnly" |
| | | @click="finishCheck">宿çç¹</el-button> |
| | | <el-button @click="openDiffDialog">å·®å¼åæ</el-button> |
| | | <el-button @click="handleExportDetail">å¯¼åºæç»</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable rowKey="id" |
| | | :column="detailColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"> |
| | | <template #actualQty="{ row }"> |
| | | <el-input-number v-if="!readOnly" |
| | | v-model="row.actualQty" |
| | | :min="0" |
| | | :precision="4" |
| | | :controls="false" |
| | | size="small" |
| | | style="width: 100%" /> |
| | | <span v-else>{{ row.actualQty ?? "-" }}</span> |
| | | </template> |
| | | <template #diffQty="{ row }"> |
| | | <span :style="{ color: diffColor(row) }">{{ diffText(row) }}</span> |
| | | </template> |
| | | <template #adjusted="{ row }"> |
| | | <el-tag :type="row.adjusted == 1 ? 'success' : 'info'"> |
| | | {{ row.adjusted == 1 ? "å·²è°è´¦" : "æªè°è´¦" }} |
| | | </el-tag> |
| | | </template> |
| | | <template #remark="{ row }"> |
| | | <el-input v-if="!readOnly" |
| | | v-model="row.remark" |
| | | size="small" |
| | | placeholder="夿³¨" /> |
| | | <span v-else>{{ row.remark || "-" }}</span> |
| | | </template> |
| | | </PIMTable> |
| | | |
| | | <el-dialog v-model="diffDialogVisible" |
| | | title="å·®å¼åæ" |
| | | width="90%"> |
| | | <div style="text-align: right; margin-bottom: 10px"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="diffExportLoading" |
| | | @click="handleExportDiff">导åºå·®å¼</el-button> |
| | | </div> |
| | | <el-table :data="diffData" |
| | | border |
| | | height="420"> |
| | | <el-table-column type="index" |
| | | label="åºå·" |
| | | width="60" |
| | | align="center" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="productModel" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | width="80" /> |
| | | <el-table-column label="æ¹å·" |
| | | prop="batchNo" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è´¦é¢æ°é" |
| | | prop="bookQty" |
| | | width="120" /> |
| | | <el-table-column label="å®çæ°é" |
| | | prop="actualQty" |
| | | width="120" /> |
| | | <el-table-column label="差弿°é" |
| | | prop="diffQty" |
| | | width="120" /> |
| | | <el-table-column label="å·²è°è´¦" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.adjusted == 1 ? 'success' : 'info'"> |
| | | {{ scope.row.adjusted == 1 ? "å·²è°è´¦" : "æªè°è´¦" }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" |
| | | prop="remark" |
| | | show-overflow-tooltip /> |
| | | </el-table> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | getById, |
| | | detailPage, |
| | | entry, |
| | | finish, |
| | | diffList, |
| | | diffListExport, |
| | | detailExport, |
| | | } from "@/api/inventoryManagement/stockCheck.js"; |
| | | |
| | | const props = defineProps({ |
| | | mainId: { |
| | | type: [Number, String], |
| | | default: "", |
| | | }, |
| | | }); |
| | | const emit = defineEmits(["back"]); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const statusTextMap = { 0: "çç¹ä¸", 1: "已宿", 2: "已忶" }; |
| | | const statusTypeMap = { 0: "warning", 1: "success", 2: "info" }; |
| | | |
| | | const main = ref({}); |
| | | const onlyDiff = ref(false); |
| | | |
| | | const mainStatus = computed(() => main.value.status ?? main.value.checkStatus); |
| | | const readOnly = computed(() => mainStatus.value != 0); |
| | | |
| | | const detailColumn = ref([ |
| | | { label: "产ååç§°", prop: "productName", minWidth: 140 }, |
| | | { label: "è§æ ¼åå·", prop: "productModel", minWidth: 140 }, |
| | | { label: "åä½", prop: "unit", width: 80 }, |
| | | { label: "æ¹å·", prop: "batchNo", minWidth: 120 }, |
| | | { label: "è´¦é¢æ°é", prop: "bookQty", width: 110 }, |
| | | { label: "å®çæ°é", prop: "actualQty", width: 140, dataType: "slot", slot: "actualQty" }, |
| | | { label: "å·®å¼", prop: "diffQty", width: 110, dataType: "slot", slot: "diffQty" }, |
| | | { label: "å·²è°è´¦", prop: "adjusted", width: 100, dataType: "slot", slot: "adjusted" }, |
| | | { label: "夿³¨", prop: "remark", minWidth: 140, dataType: "slot", slot: "remark" }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const diffDialogVisible = ref(false); |
| | | const diffData = ref([]); |
| | | const diffExportLoading = ref(false); |
| | | |
| | | const diffColor = row => { |
| | | const diff = diffValue(row); |
| | | if (diff === null) return ""; |
| | | if (diff > 0) return "#67C23A"; |
| | | if (diff < 0) return "#F56C6C"; |
| | | return ""; |
| | | }; |
| | | |
| | | const diffValue = row => { |
| | | const actual = row.actualQty; |
| | | const book = row.bookQty; |
| | | if (actual === null || actual === undefined || actual === "") return null; |
| | | return Number(actual) - Number(book ?? 0); |
| | | }; |
| | | |
| | | const diffText = row => { |
| | | const diff = diffValue(row); |
| | | return diff === null ? "-" : diff; |
| | | }; |
| | | |
| | | const loadMain = () => { |
| | | if (!props.mainId) return; |
| | | getById(props.mainId).then(res => { |
| | | main.value = res?.data || {}; |
| | | }); |
| | | }; |
| | | |
| | | const loadDetail = () => { |
| | | tableLoading.value = true; |
| | | detailPage({ |
| | | mainId: props.mainId, |
| | | onlyDiff: onlyDiff.value, |
| | | current: page.current, |
| | | size: page.size, |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | loadDetail(); |
| | | }; |
| | | |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | loadDetail(); |
| | | }; |
| | | |
| | | const saveEntry = () => { |
| | | const rows = tableData.value |
| | | .filter(item => item.actualQty !== null && item.actualQty !== undefined) |
| | | .map(item => ({ |
| | | id: item.id, |
| | | actualQty: item.actualQty, |
| | | remark: item.remark, |
| | | })); |
| | | if (rows.length === 0) { |
| | | proxy.$modal.msgWarning("请å
å½å
¥å®çæ°é"); |
| | | return; |
| | | } |
| | | entry(rows) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("ä¿åæå"); |
| | | loadMain(); |
| | | loadDetail(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | const finishCheck = () => { |
| | | ElMessageBox.confirm( |
| | | "å·®å¼å°çæè°è´¦åæ®ï¼å®¡æ ¸éè¿åè´¦é¢çæï¼ç¡®è®¤å®æçç¹ï¼", |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(() => { |
| | | finish({ id: props.mainId }) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess(res?.data || "æä½æå"); |
| | | loadMain(); |
| | | loadDetail(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | const openDiffDialog = () => { |
| | | diffDialogVisible.value = true; |
| | | diffList({ mainId: props.mainId }).then(res => { |
| | | diffData.value = res?.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const exportBlob = async (requestFn, query, filename) => { |
| | | const blobData = await requestFn(query); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, filename); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | const handleExportDetail = async () => { |
| | | try { |
| | | await exportBlob( |
| | | detailExport, |
| | | { mainId: props.mainId }, |
| | | `çç¹æç»_${main.value.checkNo || props.mainId}.xlsx` |
| | | ); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } |
| | | }; |
| | | |
| | | const handleExportDiff = async () => { |
| | | diffExportLoading.value = true; |
| | | try { |
| | | await exportBlob( |
| | | diffListExport, |
| | | { mainId: props.mainId }, |
| | | `çç¹å·®å¼_${main.value.checkNo || props.mainId}.xlsx` |
| | | ); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | diffExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleBack = () => { |
| | | emit("back"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | loadMain(); |
| | | loadDetail(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .detail-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | } |
| | | .detail-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | } |
| | | .detail-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <template v-if="viewMode === 'list'"> |
| | | <PageHeader content="åºåçç¹" /> |
| | | <div class="search_form"> |
| | | <el-form :model="queryParams" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="çç¹åå·ï¼"> |
| | | <el-input v-model="queryParams.checkNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="çç¹åç§°ï¼"> |
| | | <el-input v-model="queryParams.checkName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æï¼"> |
| | | <el-select v-model="queryParams.status" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | style="width: 160px" |
| | | @change="handleQuery"> |
| | | <el-option label="çç¹ä¸" |
| | | :value="0" /> |
| | | <el-option label="已宿" |
| | | :value="1" /> |
| | | <el-option label="已忶" |
| | | :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="çç¹æ¶é´ï¼"> |
| | | <el-date-picker v-model="dateRange" |
| | | type="datetimerange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¶é´" |
| | | end-placeholder="ç»ææ¶é´" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="primary" |
| | | @click="openStartDialog">åèµ·çç¹</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | :disabled="selectedRows.length === 0" |
| | | @click="handleBatchDelete">æ¹éå é¤</el-button> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | :tableLoading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="pagination"> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="statusTypeMap[getStatus(row)] || 'info'"> |
| | | {{ statusTextMap[getStatus(row)] || "æªç¥" }} |
| | | </el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </template> |
| | | |
| | | <CheckDetail v-else |
| | | :main-id="currentId" |
| | | @back="backToList" /> |
| | | |
| | | <el-dialog v-model="startDialogVisible" |
| | | title="åèµ·çç¹" |
| | | width="520px"> |
| | | <el-form ref="startFormRef" |
| | | :model="startForm" |
| | | :rules="startRules" |
| | | label-width="100px"> |
| | | <el-form-item label="çç¹åç§°ï¼" |
| | | prop="checkName"> |
| | | <el-input v-model="startForm.checkName" |
| | | placeholder="请è¾å
¥çç¹åç§°" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="çç¹æ¥æï¼"> |
| | | <el-date-picker v-model="startForm.checkDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="çç¹äººï¼"> |
| | | <el-input v-model="startForm.checker" |
| | | placeholder="请è¾å
¥çç¹äºº" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼èå´ï¼"> |
| | | <el-select v-model="startForm.productModelIds" |
| | | multiple |
| | | filterable |
| | | clearable |
| | | placeholder="ç空表示å
¨é¨åºå" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.productName ? item.productName + ' ' + item.model : item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openProductSelect" |
| | | style="margin-top: 6px"> |
| | | éæ©äº§åè§æ ¼ |
| | | </el-button> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input v-model="startForm.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitStart">确认</el-button> |
| | | <el-button @click="startDialogVisible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <ProductSelectDialog v-model="productSelectVisible" |
| | | @confirm="handleProductSelected" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | start, |
| | | listPage, |
| | | finish, |
| | | cancel, |
| | | remove, |
| | | exportList, |
| | | } from "@/api/inventoryManagement/stockCheck.js"; |
| | | import CheckDetail from "./components/CheckDetail.vue"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const statusTextMap = { 0: "çç¹ä¸", 1: "已宿", 2: "已忶" }; |
| | | const statusTypeMap = { 0: "warning", 1: "success", 2: "info" }; |
| | | |
| | | const getStatus = row => row.status ?? row.checkStatus; |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "çç¹åå·", prop: "checkNo", minWidth: 150 }, |
| | | { label: "çç¹åç§°", prop: "checkName", minWidth: 140 }, |
| | | { label: "çç¹æ¥æ", prop: "checkDate", width: 120 }, |
| | | { label: "çç¹äºº", prop: "checker", width: 100 }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "slot", |
| | | slot: "status", |
| | | width: 100, |
| | | }, |
| | | { label: "çç¹ç§ç±»æ°", prop: "totalKinds", width: 110 }, |
| | | { label: "å·®å¼ç§ç±»æ°", prop: "diffKinds", width: 110 }, |
| | | { label: "夿³¨", prop: "remark", minWidth: 140 }, |
| | | { label: "å建æ¶é´", prop: "createTime", width: 170 }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 300, |
| | | operation: [ |
| | | { |
| | | name: "ç»§ç»çç¹", |
| | | type: "text", |
| | | showHide: row => getStatus(row) == 0, |
| | | clickFun: row => { |
| | | toDetail(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "宿", |
| | | type: "text", |
| | | color: "#67C23A", |
| | | showHide: row => getStatus(row) == 0, |
| | | clickFun: row => { |
| | | handleFinish(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åæ¶", |
| | | type: "text", |
| | | color: "#909399", |
| | | showHide: row => getStatus(row) == 0, |
| | | clickFun: row => { |
| | | handleCancel(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | showHide: row => getStatus(row) != 0, |
| | | clickFun: row => { |
| | | toDetail(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "text", |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const queryParams = reactive({ |
| | | checkNo: "", |
| | | checkName: "", |
| | | status: "", |
| | | }); |
| | | const dateRange = ref([]); |
| | | |
| | | const viewMode = ref("list"); |
| | | const currentId = ref(""); |
| | | |
| | | const startDialogVisible = ref(false); |
| | | const startFormRef = ref(null); |
| | | const productSelectVisible = ref(false); |
| | | const modelOptions = ref([]); |
| | | const startForm = reactive({ |
| | | checkName: "", |
| | | checkDate: "", |
| | | checker: "", |
| | | remark: "", |
| | | productModelIds: [], |
| | | }); |
| | | const startRules = { |
| | | checkName: [{ required: true, message: "请è¾å
¥çç¹åç§°", trigger: "blur" }], |
| | | }; |
| | | |
| | | const buildQuery = () => ({ |
| | | checkNo: queryParams.checkNo || undefined, |
| | | checkName: queryParams.checkName || undefined, |
| | | status: |
| | | queryParams.status === "" || |
| | | queryParams.status === null || |
| | | queryParams.status === undefined |
| | | ? undefined |
| | | : queryParams.status, |
| | | beginTime: dateRange.value?.[0] || undefined, |
| | | endTime: dateRange.value?.[1] || undefined, |
| | | }); |
| | | |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listPage({ |
| | | pageNum: page.current, |
| | | pageSize: page.size, |
| | | ...buildQuery(), |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | selectedRows.value = []; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | queryParams.checkNo = ""; |
| | | queryParams.checkName = ""; |
| | | queryParams.status = ""; |
| | | dateRange.value = []; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const toDetail = row => { |
| | | currentId.value = row.id; |
| | | viewMode.value = "detail"; |
| | | }; |
| | | |
| | | const backToList = () => { |
| | | viewMode.value = "list"; |
| | | getList(); |
| | | }; |
| | | |
| | | // åèµ·çç¹ |
| | | const openStartDialog = () => { |
| | | startForm.checkName = ""; |
| | | startForm.checkDate = ""; |
| | | startForm.checker = ""; |
| | | startForm.remark = ""; |
| | | startForm.productModelIds = []; |
| | | modelOptions.value = []; |
| | | startDialogVisible.value = true; |
| | | }; |
| | | |
| | | const openProductSelect = () => { |
| | | productSelectVisible.value = true; |
| | | }; |
| | | |
| | | const handleProductSelected = rows => { |
| | | (rows || []).forEach(row => { |
| | | if (!startForm.productModelIds.includes(row.id) && !modelOptions.value.some(m => m.id === row.id)) { |
| | | modelOptions.value.push(row); |
| | | } |
| | | }); |
| | | const ids = (rows || []).map(row => row.id); |
| | | ids.forEach(id => { |
| | | if (!startForm.productModelIds.includes(id)) { |
| | | startForm.productModelIds.push(id); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const submitStart = () => { |
| | | startFormRef.value?.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | const submitData = { |
| | | checkName: startForm.checkName, |
| | | checkDate: startForm.checkDate || undefined, |
| | | checker: startForm.checker || undefined, |
| | | remark: startForm.remark || undefined, |
| | | productModelIds: |
| | | startForm.productModelIds.length > 0 |
| | | ? startForm.productModelIds |
| | | : undefined, |
| | | }; |
| | | start(submitData) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("åèµ·æå"); |
| | | startDialogVisible.value = false; |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }); |
| | | }; |
| | | |
| | | // 宿 |
| | | const handleFinish = row => { |
| | | ElMessageBox.confirm( |
| | | "å·®å¼å°çæè°è´¦åæ®ï¼å®¡æ ¸éè¿åè´¦é¢çæï¼ç¡®è®¤å®æçç¹ï¼", |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(() => { |
| | | finish({ id: row.id }) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess(res?.data || "æä½æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // åæ¶ |
| | | const handleCancel = row => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶è¯¥çç¹åï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | cancel({ id: row.id }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm( |
| | | `确认å é¤çç¹åã${row.checkNo || row.checkName || ""}ãï¼`, |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(() => { |
| | | remove([row.id]) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // æ¹éå é¤ |
| | | const handleBatchDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm( |
| | | `éä¸ç ${selectedRows.value.length} æ¡çç¹åå°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼`, |
| | | "å é¤æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(() => { |
| | | remove(selectedRows.value.map(item => item.id)) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleExport = async () => { |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await exportList(buildQuery()); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, "åºåçç¹.xlsx"); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="stock-warn"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="产å大类ï¼"> |
| | | <el-input v-model="searchForm.productName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 240px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·ï¼"> |
| | | <el-input v-model="searchForm.model" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¹å·ï¼"> |
| | | <el-input v-model="searchForm.batchNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | :rowClassName="tableRowClassName" |
| | | @pagination="pagination" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { warnPage, warnExport } from "@/api/inventoryManagement/stockInventory.js"; |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "产å大类", prop: "productName", minWidth: 140 }, |
| | | { label: "è§æ ¼åå·", prop: "model", minWidth: 140 }, |
| | | { label: "æ¹å·", prop: "batchNo", minWidth: 120 }, |
| | | { label: "åä½", prop: "unit", width: 80 }, |
| | | { label: "åºåæ°é", prop: "qualitity", width: 110 }, |
| | | { label: "å»ç»æ°é", prop: "lockedQuantity", width: 110 }, |
| | | { label: "å¯ç¨æ°é", prop: "unLockedQuantity", width: 110 }, |
| | | { label: "é¢è¦å¼", prop: "warnNum", width: 100 }, |
| | | { label: "夿³¨", prop: "remark", minWidth: 140 }, |
| | | { label: "æè¿æ´æ°æ¶é´", prop: "updateTime", width: 170 }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const searchForm = reactive({ |
| | | productName: "", |
| | | model: "", |
| | | batchNo: "", |
| | | }); |
| | | |
| | | const getUnlocked = row => row?.unLockedQuantity ?? row?.unLocked_quantity ?? 0; |
| | | |
| | | const buildQuery = () => ({ |
| | | productName: searchForm.productName || undefined, |
| | | model: searchForm.model || undefined, |
| | | batchNo: searchForm.batchNo || undefined, |
| | | }); |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | warnPage({ |
| | | // å
¼å®¹ current/size ä¸ pageNum/pageSize 两ç§å页忰 |
| | | current: page.current, |
| | | size: page.size, |
| | | pageNum: page.current, |
| | | pageSize: page.size, |
| | | ...buildQuery(), |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | searchForm.productName = ""; |
| | | searchForm.model = ""; |
| | | searchForm.batchNo = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | // å¯ç¨æ°é <= é¢è¦å¼ ä¸ é¢è¦å¼ > 0 æ¶é«äº® |
| | | const tableRowClassName = ({ row }) => { |
| | | const unlocked = Number(getUnlocked(row)); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(unlocked) || !Number.isFinite(warn)) { |
| | | return ""; |
| | | } |
| | | return warn > 0 && unlocked <= warn ? "row-low-stock" : ""; |
| | | }; |
| | | |
| | | const handleExport = async () => { |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await warnExport(buildQuery()); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, "åºåé¢è¦.xlsx"); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | :deep(.row-low-stock td) { |
| | | background-color: #fde2e2; |
| | | color: #c45656; |
| | | } |
| | | |
| | | :deep(.row-low-stock:hover > td) { |
| | | background-color: #fcd4d4; |
| | | } |
| | | </style> |
| | |
| | | :key="tab.id"> |
| | | <Record :product-id="tab.id" v-if="tab.id === activeTab" /> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="åºåé¢è¦" |
| | | name="__warn__"> |
| | | <StockWarn v-if="activeTab === '__warn__'" /> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | </div> |
| | |
| | | import { ref, onMounted } from 'vue'; |
| | | import { productTreeList } from "@/api/basicData/product.js"; |
| | | import Record from "@/views/inventoryManagement/stockManagement/Record.vue"; |
| | | import StockWarn from "@/views/inventoryManagement/stockManagement/StockWarn.vue"; |
| | | const products = ref([]) |
| | | const activeTab = ref(null) |
| | | const loading = ref(false) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="éè´éæ±" /> |
| | | <div class="search_form"> |
| | | <el-form :model="queryParams" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="产ååç§°ï¼"> |
| | | <el-input v-model="queryParams.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·ï¼"> |
| | | <el-input v-model="queryParams.productModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æï¼"> |
| | | <el-select v-model="queryParams.status" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | style="width: 160px" |
| | | @change="handleQuery"> |
| | | <el-option label="å¾
å¤ç" |
| | | :value="0" /> |
| | | <el-option label="已转éè´" |
| | | :value="1" /> |
| | | <el-option label="已忶" |
| | | :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | listPage, |
| | | changeStatus, |
| | | del, |
| | | exportDemand, |
| | | } from "@/api/procurementManagement/procurementDemand.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const statusTextMap = { 0: "å¾
å¤ç", 1: "已转éè´", 2: "已忶" }; |
| | | const statusTypeMap = { 0: "warning", 1: "success", 2: "info" }; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "产ååç§°", |
| | | prop: "productName", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "productModel", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "åä½", |
| | | prop: "unit", |
| | | width: 80, |
| | | }, |
| | | { |
| | | label: "缺å£éæ±é", |
| | | prop: "demandQuantity", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "çææ¶åºå", |
| | | prop: "stockQuantity", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ¥æºBOMç¼å·", |
| | | prop: "sourceBomNo", |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: v => statusTextMap[v] || "å¾
å¤ç", |
| | | formatType: v => statusTypeMap[v] || "warning", |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 170, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 240, |
| | | operation: [ |
| | | { |
| | | name: "æ 记已转éè´", |
| | | type: "text", |
| | | color: "#67C23A", |
| | | showHide: row => row.status == 0, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 1); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åæ¶", |
| | | type: "text", |
| | | color: "#E6A23C", |
| | | showHide: row => row.status == 0, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 2); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | link: true, |
| | | showHide: row => row.status == 0, |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const queryParams = reactive({ |
| | | productName: "", |
| | | productModel: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const buildQuery = () => ({ |
| | | productName: queryParams.productName || undefined, |
| | | productModel: queryParams.productModel || undefined, |
| | | status: |
| | | queryParams.status === "" || |
| | | queryParams.status === null || |
| | | queryParams.status === undefined |
| | | ? undefined |
| | | : queryParams.status, |
| | | }); |
| | | |
| | | // æ¥è¯¢å表 |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listPage({ |
| | | pageNum: page.current, |
| | | pageSize: page.size, |
| | | ...buildQuery(), |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | queryParams.productName = ""; |
| | | queryParams.productModel = ""; |
| | | queryParams.status = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // ç¶æåæ´ |
| | | const handleChangeStatus = (row, status) => { |
| | | const text = status == 1 ? "æ 记为已转éè´" : "åæ¶è¯¥éè´éæ±"; |
| | | ElMessageBox.confirm(`确认${text}ï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | changeStatus([row.id], status) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥éè´éæ±ï¼ä»
å¾
å¤çæ°æ®å¯å é¤ã", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | del([row.id]) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleExport = async () => { |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await exportDemand(buildQuery()); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, "éè´éæ±.xlsx"); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="ææ¬æ ¸ç®åæ" /> |
| | | <div class="search_form"> |
| | | <el-form :model="queryParams" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="è®¢åæ¥æï¼"> |
| | | <el-date-picker v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç产订åå·ï¼"> |
| | | <el-input v-model="queryParams.npsNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="æåï¼"> |
| | | <el-input v-model="queryParams.productName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <!-- æ ¸ç®æç» --> |
| | | <el-tab-pane label="æ ¸ç®æç»" |
| | | name="stat"> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="statExportLoading" |
| | | @click="handleStatExport">导åº</el-button> |
| | | </div> |
| | | <PIMTable rowKey="productionOrderId" |
| | | :column="statColumn" |
| | | :tableData="statData" |
| | | :isShowPagination="false" |
| | | :tableLoading="statLoading"> |
| | | <template #laborCost="{ row }"> |
| | | <el-tooltip v-if="row.noRateRows > 0" |
| | | content="å卿 å·¥èµæ åçæ¥å·¥è®°å½ï¼äººå·¥ææ¬å¯è½ä¸åç¡®ï¼è¯·è¡¥å½å·¥æ¶æç»´æ¤è®¡ä»¶åä»·" |
| | | placement="top"> |
| | | <span style="color: #e6a23c">{{ formatNum(row.laborCost) }}</span> |
| | | </el-tooltip> |
| | | <span v-else>{{ formatNum(row.laborCost) }}</span> |
| | | </template> |
| | | <template #grossProfitRate="{ row }"> |
| | | <span :style="{ color: isNegative(row.grossProfitRate) ? '#f56c6c' : '' }"> |
| | | {{ formatRate(row.grossProfitRate) }} |
| | | </span> |
| | | </template> |
| | | <template #missingPriceKinds="{ row }"> |
| | | <span :style="{ color: row.missingPriceKinds > 0 ? '#e6a23c' : '' }"> |
| | | {{ row.missingPriceKinds ?? 0 }} |
| | | </span> |
| | | </template> |
| | | </PIMTable> |
| | | </el-tab-pane> |
| | | |
| | | <!-- æææ±æ» --> |
| | | <el-tab-pane label="æææ±æ»" |
| | | name="summary"> |
| | | <div class="mb20" |
| | | style="display: flex; align-items: center; justify-content: space-between;"> |
| | | <el-radio-group v-model="groupBy" |
| | | @change="getSummary"> |
| | | <el-radio label="month">ææä»½</el-radio> |
| | | <el-radio label="product">ææå</el-radio> |
| | | </el-radio-group> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="summaryExportLoading" |
| | | @click="handleSummaryExport">导åº</el-button> |
| | | </div> |
| | | <PIMTable rowKey="groupKey" |
| | | :column="summaryColumn" |
| | | :tableData="summaryData" |
| | | :isShowPagination="false" |
| | | :tableLoading="summaryLoading" /> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ç¼ºä»·ç©æ --> |
| | | <el-tab-pane label="ç¼ºä»·ç©æ" |
| | | name="missing"> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="missingExportLoading" |
| | | @click="handleMissingExport">导åº</el-button> |
| | | </div> |
| | | <PIMTable rowKey="productModelId" |
| | | :column="missingColumn" |
| | | :tableData="missingData" |
| | | :isShowPagination="false" |
| | | :tableLoading="missingLoading" /> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | costStat, |
| | | costSummary, |
| | | missingPrice, |
| | | costStatExport, |
| | | costSummaryExport, |
| | | missingPriceExport, |
| | | } from "@/api/productionManagement/costAccounting.js"; |
| | | |
| | | // é»è®¤æ¥è¯¢å½åæ |
| | | const currentMonthRange = () => [ |
| | | dayjs().startOf("month").format("YYYY-MM-DD"), |
| | | dayjs().endOf("month").format("YYYY-MM-DD"), |
| | | ]; |
| | | |
| | | const activeTab = ref("stat"); |
| | | const dateRange = ref(currentMonthRange()); |
| | | const queryParams = reactive({ |
| | | npsNo: "", |
| | | productName: "", |
| | | }); |
| | | const groupBy = ref("month"); |
| | | |
| | | const statColumn = ref([ |
| | | { label: "ç产订åå·", prop: "npsNo", minWidth: 150 }, |
| | | { label: "æå", prop: "finishedProduct", minWidth: 140 }, |
| | | { label: "åä½", prop: "unit", width: 80 }, |
| | | { label: "è®¢åæ¥æ", prop: "orderDate", width: 120 }, |
| | | { label: "产é", prop: "outputQty", width: 100 }, |
| | | { label: "ææææ¬", prop: "materialCost", width: 110 }, |
| | | { label: "äººå·¥ææ¬", prop: "laborCost", width: 110, dataType: "slot", slot: "laborCost" }, |
| | | { label: "è½èææ¬", prop: "energyCost", width: 110 }, |
| | | { label: "æ»ææ¬", prop: "totalCost", width: 110 }, |
| | | { label: "å使æ¬", prop: "unitCost", width: 110 }, |
| | | { label: "éå®åä»·", prop: "salePrice", width: 110, formatData: v => formatNum(v) }, |
| | | { label: "æ¯å©", prop: "grossProfit", width: 110, formatData: v => formatNum(v) }, |
| | | { |
| | | label: "æ¯å©ç(%)", |
| | | prop: "grossProfitRate", |
| | | width: 110, |
| | | dataType: "slot", |
| | | slot: "grossProfitRate", |
| | | }, |
| | | { label: "ä»·æ ¼è¦çç(%)", prop: "priceCoverageRate", width: 120 }, |
| | | { |
| | | label: "ç¼ºä»·ç©ææ°", |
| | | prop: "missingPriceKinds", |
| | | width: 110, |
| | | dataType: "slot", |
| | | slot: "missingPriceKinds", |
| | | }, |
| | | { label: "æ å·¥èµæ åè®°å½", prop: "noRateRows", width: 130 }, |
| | | ]); |
| | | |
| | | const summaryColumn = ref([ |
| | | { label: "åç»", prop: "groupKey", minWidth: 160 }, |
| | | { label: "è®¢åæ°", prop: "orderCount", width: 100 }, |
| | | { label: "产é", prop: "outputQty", width: 110 }, |
| | | { label: "ææææ¬", prop: "materialCost", width: 110 }, |
| | | { label: "ææå æ¯(%)", prop: "materialRate", width: 120 }, |
| | | { label: "äººå·¥ææ¬", prop: "laborCost", width: 110 }, |
| | | { label: "äººå·¥å æ¯(%)", prop: "laborRate", width: 120 }, |
| | | { label: "è½èææ¬", prop: "energyCost", width: 110 }, |
| | | { label: "è½èå æ¯(%)", prop: "energyRate", width: 120 }, |
| | | { label: "æ»ææ¬", prop: "totalCost", width: 110 }, |
| | | { label: "å使æ¬", prop: "unitCost", width: 110 }, |
| | | { label: "æ¯å©", prop: "grossProfit", width: 110 }, |
| | | { label: "æ¯å©ç(%)", prop: "grossProfitRate", width: 110 }, |
| | | ]); |
| | | |
| | | const missingColumn = ref([ |
| | | { label: "ç©æåç§°", prop: "materialName", minWidth: 160 }, |
| | | { label: "è§æ ¼åå·", prop: "materialModel", minWidth: 160 }, |
| | | { label: "åä½", prop: "unit", width: 80 }, |
| | | { label: "æå
¥æ°é", prop: "inputQty", width: 120 }, |
| | | { label: "æ¶åè®¢åæ°", prop: "orderCount", width: 110 }, |
| | | { label: "æè¿æå
¥æ¶é´", prop: "lastInputTime", width: 170 }, |
| | | ]); |
| | | |
| | | const statData = ref([]); |
| | | const statLoading = ref(false); |
| | | const statExportLoading = ref(false); |
| | | const summaryData = ref([]); |
| | | const summaryLoading = ref(false); |
| | | const summaryExportLoading = ref(false); |
| | | const missingData = ref([]); |
| | | const missingLoading = ref(false); |
| | | const missingExportLoading = ref(false); |
| | | |
| | | const buildQuery = () => ({ |
| | | startDate: dateRange.value?.[0] || undefined, |
| | | endDate: dateRange.value?.[1] || undefined, |
| | | npsNo: queryParams.npsNo || undefined, |
| | | productName: queryParams.productName || undefined, |
| | | }); |
| | | |
| | | const buildSummaryQuery = () => ({ |
| | | groupBy: groupBy.value, |
| | | ...buildQuery(), |
| | | }); |
| | | |
| | | const formatNum = v => { |
| | | if (v === null || v === undefined || v === "") { |
| | | return "â"; |
| | | } |
| | | return v; |
| | | }; |
| | | |
| | | const isNegative = v => { |
| | | const num = Number(v); |
| | | return Number.isFinite(num) && num < 0; |
| | | }; |
| | | |
| | | const formatRate = v => { |
| | | if (v === null || v === undefined || v === "") { |
| | | return "â"; |
| | | } |
| | | return v; |
| | | }; |
| | | |
| | | const getStat = () => { |
| | | statLoading.value = true; |
| | | costStat(buildQuery()) |
| | | .then(res => { |
| | | statData.value = res?.data || []; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | statLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const getSummary = () => { |
| | | summaryLoading.value = true; |
| | | costSummary(buildSummaryQuery()) |
| | | .then(res => { |
| | | summaryData.value = res?.data || []; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | summaryLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const getMissing = () => { |
| | | missingLoading.value = true; |
| | | missingPrice(buildQuery()) |
| | | .then(res => { |
| | | missingData.value = res?.data || []; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | missingLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const loadCurrentTab = () => { |
| | | if (activeTab.value === "stat") { |
| | | getStat(); |
| | | } else if (activeTab.value === "summary") { |
| | | getSummary(); |
| | | } else { |
| | | getMissing(); |
| | | } |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | loadCurrentTab(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | dateRange.value = currentMonthRange(); |
| | | queryParams.npsNo = ""; |
| | | queryParams.productName = ""; |
| | | loadCurrentTab(); |
| | | }; |
| | | |
| | | const handleTabChange = () => { |
| | | loadCurrentTab(); |
| | | }; |
| | | |
| | | const exportBlob = async (requestFn, query, filename) => { |
| | | const blobData = await requestFn(query); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, filename); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | const handleStatExport = async () => { |
| | | statExportLoading.value = true; |
| | | try { |
| | | await exportBlob(costStatExport, buildQuery(), "ææ¬æ ¸ç®æç».xlsx"); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | statExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleSummaryExport = async () => { |
| | | summaryExportLoading.value = true; |
| | | try { |
| | | await exportBlob(costSummaryExport, buildSummaryQuery(), "ææ¬æææ±æ».xlsx"); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | summaryExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleMissingExport = async () => { |
| | | missingExportLoading.value = true; |
| | | try { |
| | | await exportBlob(missingPriceExport, buildQuery(), "ç¼ºä»·ç©ææ¸
å.xlsx"); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | missingExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getStat(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="æææ¶èæ ¸ç®" /> |
| | | <div class="search_form"> |
| | | <el-form :model="queryParams" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="è®¢åæ¥æï¼"> |
| | | <el-date-picker v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç产订åå·ï¼"> |
| | | <el-input v-model="queryParams.npsNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="æåï¼"> |
| | | <el-input v-model="queryParams.productName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="åªçå·®å¼ï¼"> |
| | | <el-switch v-model="queryParams.onlyDiff" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="rowKey" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :isShowPagination="false" |
| | | :tableLoading="tableLoading"> |
| | | <template #diffRate="{ row }"> |
| | | <span :style="{ color: diffRateColor(row.diffRate) }"> |
| | | {{ formatDiffRate(row.diffRate) }} |
| | | </span> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { stat, statExport } from "@/api/productionManagement/materialConsumption.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const tableColumn = ref([ |
| | | { label: "ç产订åå·", prop: "npsNo", minWidth: 150 }, |
| | | { label: "æå", prop: "finishedProduct", minWidth: 140 }, |
| | | { label: "ç©æåç§°", prop: "materialName", minWidth: 140 }, |
| | | { label: "è§æ ¼åå·", prop: "materialModel", minWidth: 140 }, |
| | | { label: "åä½", prop: "unit", width: 80 }, |
| | | { label: "BOMåè", prop: "unitQuantity", width: 110 }, |
| | | { label: "å®å·¥æ°é", prop: "completeQty", width: 110 }, |
| | | { label: "æ åæ¶è", prop: "standardQty", width: 110 }, |
| | | { label: "å®é
æ¶è", prop: "actualQty", width: 110 }, |
| | | { label: "差弿°é", prop: "diffQty", width: 110 }, |
| | | { |
| | | label: "å·®å¼ç(%)", |
| | | prop: "diffRate", |
| | | width: 110, |
| | | dataType: "slot", |
| | | slot: "diffRate", |
| | | }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const exportLoading = ref(false); |
| | | // é»è®¤æ¥è¯¢å½åæ |
| | | const currentMonthRange = () => [ |
| | | dayjs().startOf("month").format("YYYY-MM-DD"), |
| | | dayjs().endOf("month").format("YYYY-MM-DD"), |
| | | ]; |
| | | |
| | | const dateRange = ref(currentMonthRange()); |
| | | const queryParams = reactive({ |
| | | npsNo: "", |
| | | productName: "", |
| | | onlyDiff: false, |
| | | }); |
| | | |
| | | const buildQuery = () => ({ |
| | | startDate: dateRange.value?.[0] || undefined, |
| | | endDate: dateRange.value?.[1] || undefined, |
| | | npsNo: queryParams.npsNo || undefined, |
| | | productName: queryParams.productName || undefined, |
| | | onlyDiff: queryParams.onlyDiff ? true : undefined, |
| | | }); |
| | | |
| | | const formatDiffRate = rate => { |
| | | if (rate === null || rate === undefined || rate === "") { |
| | | return "-"; |
| | | } |
| | | return rate; |
| | | }; |
| | | |
| | | const diffRateColor = rate => { |
| | | const num = Number(rate); |
| | | if (rate === null || rate === undefined || rate === "" || !Number.isFinite(num)) { |
| | | return ""; |
| | | } |
| | | return Math.abs(num) > 10 ? "#F56C6C" : ""; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | stat(buildQuery()) |
| | | .then(res => { |
| | | const list = res?.data || []; |
| | | tableData.value = list.map((item, index) => ({ |
| | | ...item, |
| | | rowKey: `${item.productionOrderId}_${item.materialModelId}_${index}`, |
| | | })); |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | getList(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | dateRange.value = currentMonthRange(); |
| | | queryParams.npsNo = ""; |
| | | queryParams.productName = ""; |
| | | queryParams.onlyDiff = false; |
| | | getList(); |
| | | }; |
| | | |
| | | const handleExport = async () => { |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await statExport(buildQuery()); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, "æææ¶èæ ¸ç®.xlsx"); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="ç产ç»è®¡" /> |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <!-- è®¡ä»¶å·¥èµæ±æ» --> |
| | | <el-tab-pane label="è®¡ä»¶å·¥èµæ±æ»" |
| | | name="wage"> |
| | | <div class="search_form"> |
| | | <el-form :model="wageQuery" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="æ¥æèå´ï¼"> |
| | | <el-date-picker v-model="wageDateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleWageQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="人åå§åï¼"> |
| | | <el-input v-model="wageQuery.userName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleWageQuery" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleWageQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetWageQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="wageExportLoading" |
| | | @click="handleWageExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="userId" |
| | | :column="wageColumn" |
| | | :tableData="wageData" |
| | | :isShowPagination="false" |
| | | :tableLoading="wageLoading" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 产éç»è®¡ --> |
| | | <el-tab-pane label="产éç»è®¡" |
| | | name="output"> |
| | | <div class="search_form"> |
| | | <el-form :model="outputQuery" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="ç»è®¡ç»´åº¦ï¼"> |
| | | <el-radio-group v-model="outputQuery.dimension" |
| | | @change="handleDimensionChange"> |
| | | <el-radio label="device">设å¤</el-radio> |
| | | <el-radio label="operation">å·¥åº</el-radio> |
| | | <el-radio label="product">产å</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æèå´ï¼"> |
| | | <el-date-picker v-model="outputDateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleOutputQuery" /> |
| | | </el-form-item> |
| | | <el-form-item v-if="outputQuery.dimension === 'device'" |
| | | label="设å¤ï¼"> |
| | | <el-select v-model="outputQuery.deviceId" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | filterable |
| | | style="width: 200px" |
| | | @change="handleOutputQuery"> |
| | | <el-option v-for="item in deviceOptions" |
| | | :key="item.id" |
| | | :label="item.deviceName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleOutputQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetOutputQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="outputExportLoading" |
| | | @click="handleOutputExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="groupId" |
| | | :column="outputColumn" |
| | | :tableData="outputData" |
| | | :isShowPagination="false" |
| | | :tableLoading="outputLoading" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | wageSummary, |
| | | wageSummaryExport, |
| | | outputStat, |
| | | outputStatExport, |
| | | } from "@/api/productionManagement/productionStat.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const activeTab = ref("wage"); |
| | | const deviceOptions = ref([]); |
| | | |
| | | const wageColumn = ref([ |
| | | { |
| | | label: "ç产人", |
| | | prop: "userName", |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | label: "æ¥å·¥åæ°", |
| | | prop: "workCount", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "ç产æ°é", |
| | | prop: "quantity", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ»å·¥æ¶", |
| | | prop: "workHour", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "计件工èµ", |
| | | prop: "pieceWage", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "åºåå计", |
| | | prop: "wages", |
| | | width: 120, |
| | | }, |
| | | ]); |
| | | |
| | | const outputColumn = ref([ |
| | | { |
| | | label: "åç»åç§°", |
| | | prop: "groupName", |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | label: "æ¥å·¥åæ°", |
| | | prop: "workCount", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "产é", |
| | | prop: "quantity", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ¥åºæ°é", |
| | | prop: "scrapQty", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "å·¥æ¶", |
| | | prop: "workHour", |
| | | width: 110, |
| | | }, |
| | | ]); |
| | | |
| | | const wageData = ref([]); |
| | | const wageLoading = ref(false); |
| | | const wageExportLoading = ref(false); |
| | | const wageDateRange = ref([]); |
| | | const wageQuery = reactive({ |
| | | userName: "", |
| | | }); |
| | | |
| | | const outputData = ref([]); |
| | | const outputLoading = ref(false); |
| | | const outputExportLoading = ref(false); |
| | | const outputDateRange = ref([]); |
| | | const outputQuery = reactive({ |
| | | dimension: "device", |
| | | deviceId: "", |
| | | }); |
| | | |
| | | const buildWageQuery = () => ({ |
| | | startDate: wageDateRange.value?.[0] || undefined, |
| | | endDate: wageDateRange.value?.[1] || undefined, |
| | | userName: wageQuery.userName || undefined, |
| | | }); |
| | | |
| | | const buildOutputQuery = () => ({ |
| | | dimension: outputQuery.dimension, |
| | | startDate: outputDateRange.value?.[0] || undefined, |
| | | endDate: outputDateRange.value?.[1] || undefined, |
| | | deviceId: |
| | | outputQuery.dimension === "device" && outputQuery.deviceId |
| | | ? outputQuery.deviceId |
| | | : undefined, |
| | | }); |
| | | |
| | | // è®¡ä»¶å·¥èµæ±æ» |
| | | const getWageList = () => { |
| | | wageLoading.value = true; |
| | | wageSummary(buildWageQuery()) |
| | | .then(res => { |
| | | wageData.value = res?.data || []; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | wageLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleWageQuery = () => { |
| | | getWageList(); |
| | | }; |
| | | |
| | | const resetWageQuery = () => { |
| | | wageQuery.userName = ""; |
| | | wageDateRange.value = []; |
| | | getWageList(); |
| | | }; |
| | | |
| | | // 产éç»è®¡ |
| | | const getOutputList = () => { |
| | | outputLoading.value = true; |
| | | outputStat(buildOutputQuery()) |
| | | .then(res => { |
| | | outputData.value = res?.data || []; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | outputLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleOutputQuery = () => { |
| | | getOutputList(); |
| | | }; |
| | | |
| | | const resetOutputQuery = () => { |
| | | outputQuery.dimension = "device"; |
| | | outputQuery.deviceId = ""; |
| | | outputDateRange.value = []; |
| | | getOutputList(); |
| | | }; |
| | | |
| | | const handleDimensionChange = () => { |
| | | outputQuery.deviceId = ""; |
| | | getOutputList(); |
| | | }; |
| | | |
| | | const handleTabChange = name => { |
| | | if (name === "wage") { |
| | | getWageList(); |
| | | } else { |
| | | getOutputList(); |
| | | } |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const exportBlob = async (requestFn, query, filename) => { |
| | | const blobData = await requestFn(query); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, filename); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | const handleWageExport = async () => { |
| | | wageExportLoading.value = true; |
| | | try { |
| | | await exportBlob(wageSummaryExport, buildWageQuery(), "è®¡ä»¶å·¥èµæ±æ».xlsx"); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | wageExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleOutputExport = async () => { |
| | | outputExportLoading.value = true; |
| | | try { |
| | | await exportBlob(outputStatExport, buildOutputQuery(), "产éç»è®¡.xlsx"); |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | outputExportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getWageList(); |
| | | getDeviceLedger() |
| | | .then(res => { |
| | | deviceOptions.value = res.data || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("è·å设å¤å表失败", err); |
| | | deviceOptions.value = []; |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="计件åä»·é
ç½®" /> |
| | | <div class="search_form"> |
| | | <el-form :model="queryParams" |
| | | inline |
| | | style="margin-bottom: 0;"> |
| | | <el-form-item label="è§æ ¼åå·ï¼"> |
| | | <el-input v-model="queryParams.productModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="å·¥åºåç§°ï¼"> |
| | | <el-input v-model="queryParams.operationName" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | clearable |
| | | style="width: 200px" |
| | | @keyup.enter="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æï¼"> |
| | | <el-select v-model="queryParams.status" |
| | | placeholder="å
¨é¨" |
| | | clearable |
| | | style="width: 160px" |
| | | @change="handleQuery"> |
| | | <el-option label="å¯ç¨" |
| | | :value="1" /> |
| | | <el-option label="åç¨" |
| | | :value="0" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button @click="resetQuery">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <div class="mb20" |
| | | style="text-align: right;"> |
| | | <el-button type="primary" |
| | | @click="openDialog('add')">æ°å¢</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleBatchDelete">å é¤</el-button> |
| | | <el-button type="warning" |
| | | plain |
| | | icon="Download" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" /> |
| | | </div> |
| | | |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢è®¡ä»¶åä»·' : 'ç¼è¾è®¡ä»¶åä»·'" |
| | | width="480px" |
| | | @close="closeDialog"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100px"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" |
| | | prop="productModel"> |
| | | <el-input v-model="form.productModel" |
| | | placeholder="è¯·éæ©è§æ ¼åå·" |
| | | readonly |
| | | @click="openProductSelect"> |
| | | <template #append> |
| | | <el-button icon="Search" |
| | | @click="openProductSelect" /> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å·¥åºåç§°ï¼" |
| | | prop="operationName"> |
| | | <el-input v-model="form.operationName" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="计件åä»·ï¼" |
| | | prop="unitPrice"> |
| | | <el-input-number v-model="form.unitPrice" |
| | | :min="0.0001" |
| | | :precision="4" |
| | | :controls="false" |
| | | placeholder="请è¾å
¥è®¡ä»¶åä»·" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <ProductSelectDialog v-model="productSelectVisible" |
| | | single |
| | | @confirm="handleProductSelected" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, getCurrentInstance, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | listPage, |
| | | add, |
| | | update, |
| | | changeStatus, |
| | | batchDelete, |
| | | exportConfig, |
| | | } from "@/api/productionManagement/pieceRateConfig.js"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "è§æ ¼åå·", |
| | | prop: "productModel", |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | label: "å·¥åºåç§°", |
| | | prop: "operationName", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "计件åä»·", |
| | | prop: "unitPrice", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | width: 100, |
| | | formatData: v => (v == 1 ? "å¯ç¨" : "åç¨"), |
| | | formatType: v => (v == 1 ? "success" : "info"), |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | width: 170, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDialog("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å¯ç¨", |
| | | type: "text", |
| | | color: "#67C23A", |
| | | showHide: row => row.status != 1, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 1); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åç¨", |
| | | type: "text", |
| | | color: "#909399", |
| | | showHide: row => row.status == 1, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 0); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å é¤", |
| | | type: "danger", |
| | | link: true, |
| | | clickFun: row => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const queryParams = reactive({ |
| | | productModel: "", |
| | | operationName: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const dialogVisible = ref(false); |
| | | const operationType = ref("add"); |
| | | const formRef = ref(null); |
| | | const productSelectVisible = ref(false); |
| | | const form = reactive({ |
| | | id: "", |
| | | productModelId: "", |
| | | productModel: "", |
| | | operationName: "", |
| | | unitPrice: undefined, |
| | | remark: "", |
| | | }); |
| | | const rules = { |
| | | productModel: [{ required: true, message: "è¯·éæ©è§æ ¼åå·", trigger: "change" }], |
| | | operationName: [{ required: true, message: "请è¾å
¥å·¥åºåç§°", trigger: "blur" }], |
| | | unitPrice: [{ required: true, message: "请è¾å
¥è®¡ä»¶åä»·", trigger: "blur" }], |
| | | }; |
| | | |
| | | const buildQuery = () => ({ |
| | | productModel: queryParams.productModel || undefined, |
| | | operationName: queryParams.operationName || undefined, |
| | | status: |
| | | queryParams.status === "" || |
| | | queryParams.status === null || |
| | | queryParams.status === undefined |
| | | ? undefined |
| | | : queryParams.status, |
| | | }); |
| | | |
| | | // æ¥è¯¢å表 |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listPage({ |
| | | pageNum: page.current, |
| | | pageSize: page.size, |
| | | ...buildQuery(), |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | page.total = res?.data?.total || 0; |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const resetQuery = () => { |
| | | queryParams.productModel = ""; |
| | | queryParams.operationName = ""; |
| | | queryParams.status = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | if (type === "edit" && row) { |
| | | form.id = row.id; |
| | | form.productModelId = row.productModelId; |
| | | form.productModel = row.productModel; |
| | | form.operationName = row.operationName; |
| | | form.unitPrice = row.unitPrice; |
| | | form.remark = row.remark; |
| | | } else { |
| | | form.id = ""; |
| | | form.productModelId = ""; |
| | | form.productModel = ""; |
| | | form.operationName = ""; |
| | | form.unitPrice = undefined; |
| | | form.remark = ""; |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | formRef.value?.resetFields(); |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | const openProductSelect = () => { |
| | | productSelectVisible.value = true; |
| | | }; |
| | | |
| | | const handleProductSelected = rows => { |
| | | const row = rows && rows[0]; |
| | | if (row) { |
| | | form.productModelId = row.id; |
| | | form.productModel = row.model; |
| | | } |
| | | }; |
| | | |
| | | // æäº¤ |
| | | const submitForm = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | const submitData = { |
| | | id: form.id, |
| | | productModelId: form.productModelId, |
| | | operationName: form.operationName, |
| | | unitPrice: form.unitPrice, |
| | | remark: form.remark, |
| | | }; |
| | | const request = operationType.value === "edit" ? update : add; |
| | | request(submitData) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDialog(); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }); |
| | | }; |
| | | |
| | | // å¯ç¨/åç¨ |
| | | const handleChangeStatus = (row, status) => { |
| | | const text = status == 1 ? "å¯ç¨" : "åç¨"; |
| | | ElMessageBox.confirm(`确认${text}该计件åä»·ï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | changeStatus({ id: row.id, status }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = row => { |
| | | removeRows([row.id]); |
| | | }; |
| | | |
| | | const handleBatchDelete = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | removeRows(selectedRows.value.map(item => item.id)); |
| | | }; |
| | | |
| | | const removeRows = ids => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchDelete(ids) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleExport = async () => { |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await exportConfig(buildQuery()); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, "计件åä»·é
ç½®.xlsx"); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"></style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="visible" |
| | | title="BOMé½å¥åæ" |
| | | width="900px" |
| | | @close="handleClose"> |
| | | <div class="bom-info"> |
| | | <span>BOMç¼å·ï¼{{ bom.bomNo || '-' }}</span> |
| | | <span>产ååç§°ï¼{{ bom.productName || '-' }}</span> |
| | | <span>è§æ ¼åå·ï¼{{ bom.productModelName || '-' }}</span> |
| | | </div> |
| | | |
| | | <el-form :inline="true" |
| | | @submit.prevent> |
| | | <el-form-item label="éæ±æ°é"> |
| | | <el-input-number v-model="demandQty" |
| | | :min="0" |
| | | :precision="4" |
| | | :step="1" |
| | | :controls="false" |
| | | placeholder="请è¾å
¥éæ±æ°é" |
| | | style="width: 180px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | :loading="loading" |
| | | @click="handleAnalyze">å¼å§åæ</el-button> |
| | | <el-button :disabled="!resultList.length" |
| | | :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | <el-button type="success" |
| | | :disabled="!resultList.length" |
| | | :loading="genLoading" |
| | | @click="handleGenerate">çæéè´éæ±</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <el-table v-loading="loading" |
| | | :data="resultList" |
| | | border |
| | | size="small" |
| | | max-height="420" |
| | | style="width: 100%"> |
| | | <el-table-column type="index" |
| | | label="åºå·" |
| | | width="60" |
| | | align="center" /> |
| | | <el-table-column label="ç©æåç§°" |
| | | prop="productName" |
| | | min-width="140" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | min-width="140" /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | width="80" |
| | | align="center" /> |
| | | <el-table-column label="éæ±æ°é" |
| | | prop="requiredQty" |
| | | width="110" |
| | | align="right" /> |
| | | <el-table-column label="å¯ç¨åºå" |
| | | prop="availableQty" |
| | | width="110" |
| | | align="right" /> |
| | | <el-table-column label="ç¼ºå£æ°é" |
| | | prop="shortageQty" |
| | | width="110" |
| | | align="right"> |
| | | <template #default="{ row }"> |
| | | <span :class="{ shortage: Number(row.shortageQty) > 0 }">{{ row.shortageQty }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance } from "vue"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import { |
| | | kitCheck, |
| | | kitCheckExport, |
| | | generateDemand, |
| | | } from "@/api/productionManagement/productBom.js"; |
| | | |
| | | const props = defineProps({ |
| | | showModel: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | bom: { |
| | | type: Object, |
| | | default: () => ({}), |
| | | }, |
| | | }); |
| | | |
| | | const emits = defineEmits(["update:showModel"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const visible = computed({ |
| | | get() { |
| | | return props.showModel; |
| | | }, |
| | | set(val) { |
| | | emits("update:showModel", val); |
| | | }, |
| | | }); |
| | | |
| | | const demandQty = ref(undefined); |
| | | const resultList = ref([]); |
| | | const loading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const genLoading = ref(false); |
| | | |
| | | // æ ¡éªå¹¶ç»è£
请æ±åæ° |
| | | const buildParams = () => { |
| | | if (!props.bom?.id) { |
| | | proxy.$modal.msgWarning("æªè·åå°BOMä¿¡æ¯ï¼è¯·éè¯"); |
| | | return null; |
| | | } |
| | | const qty = Number(demandQty.value); |
| | | if (!qty || qty <= 0) { |
| | | proxy.$modal.msgWarning("请è¾å
¥å¤§äº0çéæ±æ°é"); |
| | | return null; |
| | | } |
| | | return { bomId: props.bom.id, demandQty: qty }; |
| | | }; |
| | | |
| | | // å¼å§åæ |
| | | const handleAnalyze = () => { |
| | | const params = buildParams(); |
| | | if (!params) return; |
| | | loading.value = true; |
| | | kitCheck(params) |
| | | .then(res => { |
| | | resultList.value = res?.data || []; |
| | | if (!resultList.value.length) { |
| | | proxy.$modal.msgWarning("该BOM䏿 ç©æèç¹"); |
| | | } |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleExport = async () => { |
| | | const params = buildParams(); |
| | | if (!params) return; |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = await kitCheckExport(params); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | proxy.$modal.msgWarning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | saveAs(blob, `BOMé½å¥åæ_${props.bom.bomNo || props.bom.id}.xlsx`); |
| | | proxy.$modal.msgSuccess("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | proxy.$modal.msgError(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | proxy.$modal.msgError("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 缺å£çæéè´éæ± |
| | | const handleGenerate = () => { |
| | | const params = buildParams(); |
| | | if (!params) return; |
| | | genLoading.value = true; |
| | | generateDemand(params) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess(res?.data || "çæå®æ"); |
| | | }) |
| | | .catch(() => {}) |
| | | .finally(() => { |
| | | genLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | demandQty.value = undefined; |
| | | resultList.value = []; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .bom-info { |
| | | display: flex; |
| | | gap: 24px; |
| | | margin-bottom: 16px; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .shortage { |
| | | color: #d93025; |
| | | font-weight: 600; |
| | | } |
| | | </style> |
| | |
| | | <StructureEdit v-if="showEdit" |
| | | v-model:show-model="showEdit" |
| | | :record="currentRow" /> |
| | | <!-- é½å¥åæå¼¹çª --> |
| | | <KitCheckDialog v-model:show-model="kitCheckVisible" |
| | | :bom="kitCheckRow" /> |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢BOM' : 'ç¼è¾BOM'" |
| | |
| | | batchDelete, |
| | | exportBom, |
| | | downloadTemplate, |
| | | changeStatus, |
| | | } from "@/api/productionManagement/productBom.js"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import ImportDialog from "@/components/Dialog/ImportDialog.vue"; |
| | | import KitCheckDialog from "@/views/productionManagement/productStructure/KitCheckDialog.vue"; |
| | | |
| | | const router = useRouter(); |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "status", |
| | | dataType: "tag", |
| | | width: 90, |
| | | formatData: v => (v == 1 ? "å¯ç¨" : "åç¨"), |
| | | formatType: v => (v == 1 ? "success" : "info"), |
| | | }, |
| | | { |
| | | label: "夿³¨", |
| | | prop: "remark", |
| | | minWidth: 160, |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 250, |
| | | width: 330, |
| | | operation: [ |
| | | { |
| | | name: "å¤å¶", |
| | |
| | | type: "text", |
| | | clickFun: row => { |
| | | handleEdit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "é½å¥åæ", |
| | | type: "text", |
| | | clickFun: row => { |
| | | handleKitCheck(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å¯ç¨", |
| | | type: "text", |
| | | color: "#67C23A", |
| | | showHide: row => row.status != 1, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 1); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åç¨", |
| | | type: "text", |
| | | color: "#909399", |
| | | showHide: row => row.status == 1, |
| | | clickFun: row => { |
| | | handleChangeStatus(row, 0); |
| | | }, |
| | | }, |
| | | { |
| | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const showEdit = ref(false); |
| | | const kitCheckVisible = ref(false); |
| | | const kitCheckRow = ref({}); |
| | | const selectedRows = ref([]); |
| | | const currentRow = ref({}); |
| | | const dialogVisible = ref(false); |
| | |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // å¯ç¨/åç¨ |
| | | const handleChangeStatus = (row, status) => { |
| | | const tip = |
| | | status == 1 |
| | | ? "确认å¯ç¨è¯¥BOMçæ¬ï¼å产åè§æ ¼çå
¶ä»å¯ç¨çæ¬å°èªå¨åç¨ã" |
| | | : "确认åç¨è¯¥BOMçæ¬ï¼"; |
| | | ElMessageBox.confirm(tip, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | changeStatus({ id: row.id, status }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess(status == 1 ? "å¯ç¨æå" : "åç¨æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // é½å¥åæ |
| | | const handleKitCheck = row => { |
| | | kitCheckRow.value = row; |
| | | kitCheckVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const handleEdit = row => { |
| | | operationType.value = "edit"; |
| | |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "使ç¨è®¾å¤", |
| | | prop: "deviceName", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "å·¥æ¶ï¼hï¼", |
| | | prop: "workHour", |
| | | minWidth: 100, |
| | |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "è®¡èªæ¹å¼", |
| | | prop: "wageMode", |
| | | minWidth: 100, |
| | | dataType: "tag", |
| | | formatData: v => (v === "计件" ? "计件" : "计æ¶"), |
| | | formatType: v => (v === "计件" ? "success" : "info"), |
| | | }, |
| | | { |
| | | label: "å·¥èµ", |
| | | prop: "wages", |
| | | minWidth: 100, |
| | |
| | | style="width: 200px;" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="使ç¨è®¾å¤:"> |
| | | <el-select v-model="searchForm.deviceId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | style="width: 200px;" |
| | | @change="handleQuery"> |
| | | <el-option v-for="item in deviceOptions" |
| | | :key="item.id" |
| | | :label="item.deviceName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æç´¢</el-button> |
| | |
| | | } from "@/api/productionManagement/productionReporting.js"; |
| | | import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"; |
| | | import InputModal from "@/views/productionManagement/productionReporting/Input.vue"; |
| | | |
| | | const data = reactive({ |
| | |
| | | nickName: "", |
| | | workOrderNo: "", |
| | | workOrderStatus: "", |
| | | deviceId: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const expandedRowKeys = ref([]); |
| | | const expandData = ref([]); |
| | | const userList = ref([]); |
| | | const deviceOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æ¥å·¥åå·", |
| | |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "使ç¨è®¾å¤", |
| | | prop: "deviceName", |
| | | width: 140, |
| | | }, |
| | | { |
| | | label: "å·¥åç¼å·", |
| | |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | getDeviceLedger() |
| | | .then(res => { |
| | | deviceOptions.value = res.data || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("è·å设å¤å表失败", err); |
| | | deviceOptions.value = []; |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | <el-table-column label="å建人" |
| | | prop="userName" |
| | | align="center" /> |
| | | <el-table-column label="使ç¨è®¾å¤" |
| | | prop="deviceName" |
| | | align="center" /> |
| | | <el-table-column label="å建æ¶é´" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | |
| | | :value="user.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="使ç¨è®¾å¤"> |
| | | <el-select v-model="reportForm.deviceId" |
| | | style="width: 300px" |
| | | placeholder="è¯·éæ©ä½¿ç¨è®¾å¤" |
| | | clearable |
| | | filterable> |
| | | <el-option v-for="item in deviceOptions" |
| | | :key="item.id" |
| | | :label="item.deviceName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | |
| | | downProductWorkOrder, |
| | | } from "@/api/productionManagement/workOrder.js"; |
| | | import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"; |
| | | import QRCode from "qrcode"; |
| | | import { getCurrentInstance, reactive, toRefs } from "vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | |
| | | const workOrderFilesRef = ref(null); |
| | | const reportFormRef = ref(null); |
| | | const userOptions = ref([]); |
| | | const deviceOptions = ref([]); |
| | | const reportForm = reactive({ |
| | | planQuantity: 0, |
| | | quantity: null, |
| | |
| | | reportWork: "", |
| | | productProcessRouteItemId: "", |
| | | userId: "", |
| | | deviceId: "", |
| | | productMainId: null, |
| | | }); |
| | | |
| | |
| | | |
| | | const showReportDialog = row => { |
| | | currentReportRowData.value = row; |
| | | reportForm.deviceId = ""; |
| | | reportForm.planQuantity = row.planQuantity - row.completeQuantity; |
| | | reportForm.quantity = |
| | | row.quantity !== undefined && row.quantity !== null ? row.quantity : null; |
| | |
| | | } |
| | | }; |
| | | |
| | | // è·å设å¤ä¸æ |
| | | const getDeviceOptions = () => { |
| | | getDeviceLedger() |
| | | .then(res => { |
| | | deviceOptions.value = res.data || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("è·å设å¤å表失败", err); |
| | | deviceOptions.value = []; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | getUserList(); |
| | | getDeviceOptions(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | :value="user.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="使ç¨è®¾å¤"> |
| | | <el-select v-model="reportForm.deviceId" |
| | | style="width: 300px" |
| | | placeholder="è¯·éæ©ä½¿ç¨è®¾å¤" |
| | | clearable |
| | | filterable> |
| | | <el-option v-for="item in deviceOptions" |
| | | :key="item.id" |
| | | :label="item.deviceName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <!-- å·¥æ¶ --> |
| | | <el-form-item label="å·¥æ¶" |
| | | v-if="currentReportRowData?.type == 0" |
| | |
| | | import { listMaterialPickingDetail } from "@/api/productionManagement/productionOrder.js"; |
| | | import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js"; |
| | | import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"; |
| | | import { getDicts } from "@/api/system/dict/data"; |
| | | import QRCode from "qrcode"; |
| | | import { getCurrentInstance, reactive, toRefs } from "vue"; |
| | |
| | | const currentWorkOrderId = ref(null); |
| | | const reportFormRef = ref(null); |
| | | const userOptions = ref([]); |
| | | const deviceOptions = ref([]); |
| | | const reportForm = reactive({ |
| | | planQuantity: 0, |
| | | quantity: null, |
| | |
| | | reportWork: "", |
| | | productProcessRouteItemId: "", |
| | | userId: "", |
| | | deviceId: "", |
| | | productMainId: null, |
| | | productionOrderRoutingOperationId: "", |
| | | productionOrderId: "", |
| | |
| | | toQuantity(planQuantity - completeQuantity) |
| | | ); |
| | | reportForm.planQuantity = remainingQuantity; |
| | | reportForm.deviceId = ""; |
| | | reportForm.quantity = remainingQuantity; |
| | | reportForm.productProcessRouteItemId = row.productProcessRouteItemId; |
| | | reportForm.workOrderId = row.id; |
| | |
| | | scrapQty: isNaN(scrapQty) ? 0 : scrapQty, |
| | | userId: reportForm.userId, |
| | | userName: reportForm.userName, |
| | | deviceId: reportForm.deviceId || undefined, |
| | | productionOperationTaskId: reportForm.workOrderId, |
| | | productProcessRouteItemId: reportForm.productProcessRouteItemId, |
| | | reportWork: reportForm.reportWork, |
| | |
| | | userOptions.value = res.data; |
| | | } |
| | | }); |
| | | // è·å设å¤å表 |
| | | getDeviceLedger() |
| | | .then(res => { |
| | | deviceOptions.value = res.data || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("è·å设å¤å表失败", err); |
| | | deviceOptions.value = []; |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | // å·²æäº¤çæ£éªå强å¶åªè¯» |
| | | if (row?.inspectState === 1) { |
| | | operationType.value = "view"; |
| | | } |
| | | dialogFormVisible.value = true; |
| | | // å
æ¸
空表åéªè¯ç¶æï¼é¿å
éªç |
| | | await nextTick(); |
| | |
| | | |
| | | // æä»· |
| | | const submit = async id => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | ElMessageBox.confirm( |
| | | "æäº¤åå°çæå
¥åºè®°å½ä¸ä¸å¯ä¿®æ¹ï¼ç¡®è®¤æäº¤ï¼", |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(async () => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess( |
| | | "æäº¤æåï¼åæ ¼é¨åå·²çæå
¥åºè®°å½ï¼å¾
å®¡æ ¸ï¼ï¼ä¸åæ ¼é¨åå·²çæå¤çå" |
| | | ); |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchSubmit = () => { |
| | |
| | | clearable> |
| | | <el-option :label="item.label" |
| | | :value="item.value" |
| | | v-for="item in rejection_handling" |
| | | v-for="item in filteredRejectionHandling" |
| | | :key="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="isReworkResult"> |
| | | <el-col :span="24"> |
| | | <el-alert title="æäº¤åå°èªå¨å建è¿å·¥ç产订åï¼FGåå·ï¼" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | style="margin-bottom: 12px" /> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); // æ£éªå/å¤çäººä¸æå表 |
| | | |
| | | // åæç±»ï¼inspectType=0ï¼ä¸åæ ¼åªå
许"æ¥åº/è®©æ¥æ¾è¡" |
| | | const filteredRejectionHandling = computed(() => { |
| | | const data = rejection_handling.value || []; |
| | | if (form.value.inspectType === 0 || form.value.inspectType === "0") { |
| | | return data.filter( |
| | | item => item && (item.label === "æ¥åº" || item.label === "è®©æ¥æ¾è¡") |
| | | ); |
| | | } |
| | | return data; |
| | | }); |
| | | |
| | | // éä¸"è¿å·¥/è¿ä¿®"æ¶çæç¤º |
| | | const isReworkResult = computed(() => { |
| | | const item = (rejection_handling.value || []).find( |
| | | i => i.value === form.value.dealResult |
| | | ); |
| | | const label = item ? item.label : form.value.dealResult; |
| | | return label === "è¿å·¥" || label === "è¿ä¿®"; |
| | | }); |
| | | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="isReworkResult"> |
| | | <el-col :span="24"> |
| | | <el-alert |
| | | title="æäº¤åå°èªå¨å建è¿å·¥ç产订åï¼FGåå·ï¼" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | style="margin-bottom: 12px" |
| | | /> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¤ç人ï¼" prop="dealName"> |
| | |
| | | const userList = ref([]); // å¤çäººä¸æå表 |
| | | |
| | | const filteredRejectionHandling = computed(() => { |
| | | const data = rejection_handling.value; |
| | | let data = rejection_handling.value; |
| | | // åæç±»ä¸åæ ¼åªå
许"æ¥åº/è®©æ¥æ¾è¡" |
| | | if (form.value.inspectType === 0 || form.value.inspectType === "0") { |
| | | data = data.filter(item => item && (item.label === 'æ¥åº' || item.label === 'è®©æ¥æ¾è¡')); |
| | | } |
| | | if (form.value.method) { |
| | | return data.filter(item => item && item.label && item.label !== 'è¿å·¥' && item.label !== 'è¿ä¿®') |
| | | } |
| | | return data |
| | | }) |
| | | |
| | | // éä¸"è¿å·¥/è¿ä¿®"æ¶çæç¤º |
| | | const isReworkResult = computed(() => { |
| | | const item = (rejection_handling.value || []).find(i => i.value === form.value.dealResult); |
| | | const label = item ? item.label : form.value.dealResult; |
| | | return label === 'è¿å·¥' || label === 'è¿ä¿®'; |
| | | }) |
| | | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | // å·²æäº¤çæ£éªå强å¶åªè¯» |
| | | if (row?.inspectState === 1) { |
| | | operationType.value = "view"; |
| | | } |
| | | getOptions().then(res => { |
| | | supplierList.value = res.data; |
| | | }); |
| | |
| | | }; |
| | | // æä»· |
| | | const submit = async id => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | ElMessageBox.confirm( |
| | | "æäº¤åå°çæå
¥åºè®°å½ä¸ä¸å¯ä¿®æ¹ï¼ç¡®è®¤æäº¤ï¼", |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(async () => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess( |
| | | "æäº¤æåï¼åæ ¼é¨åå·²çæå
¥åºè®°å½ï¼å¾
å®¡æ ¸ï¼ï¼ä¸åæ ¼é¨åå·²çæå¤çå" |
| | | ); |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchSubmit = () => { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div v-loading="loading" |
| | | class="forward-result"> |
| | | <!-- 订åå¡ --> |
| | | <div v-for="chain in data.chains" |
| | | :key="chain.productionOrderId" |
| | | class="order-card"> |
| | | <div class="order-head"> |
| | | <div class="order-title"> |
| | | <el-link type="primary" |
| | | @click="$emit('goto-order', chain.npsNo)">{{ chain.npsNo }}</el-link> |
| | | <span class="product-name">{{ chain.finishedProduct || '-' }}</span> |
| | | </div> |
| | | <el-tag :type="statusType(chain.orderStatus)">{{ statusText(chain.orderStatus) }}</el-tag> |
| | | </div> |
| | | |
| | | <div class="order-meta"> |
| | | <span>è®¡åæ°éï¼{{ chain.planQty ?? '-' }} {{ chain.unit || '' }}</span> |
| | | <span>å®å·¥æ°éï¼{{ chain.completeQty ?? '-' }} {{ chain.unit || '' }}</span> |
| | | <span>ä¸åæ¶é´ï¼{{ chain.orderCreateTime || '-' }}</span> |
| | | </div> |
| | | |
| | | <div v-if="chain.batches && chain.batches.length" |
| | | class="hit-batches"> |
| | | <span class="hit-label">å½ä¸å
¥åºæ¹æ¬¡ï¼</span> |
| | | <el-tag v-for="batch in chain.batches" |
| | | :key="batch.batchNo" |
| | | type="success" |
| | | effect="plain"> |
| | | {{ batch.batchNo }}ï¼{{ batch.qty }} {{ chain.unit || '' }}ï¼{{ batch.inTime }}ï¼{{ recordTypeLabel(batch.recordType) }}ï¼ |
| | | </el-tag> |
| | | </div> |
| | | |
| | | <el-timeline class="order-timeline"> |
| | | <!-- å·¥åºæ¥å·¥ --> |
| | | <el-timeline-item v-for="report in sortReports(chain.reports)" |
| | | :key="report.productMainId + '_' + report.workOrderNo" |
| | | :timestamp="report.reportTime" |
| | | type="primary" |
| | | placement="top"> |
| | | <div class="node-title">å·¥åºæ¥å·¥ï¼{{ report.operationName || '-' }}</div> |
| | | <el-descriptions :column="3" |
| | | border |
| | | size="small"> |
| | | <el-descriptions-item label="å·¥åç¼å·">{{ report.workOrderNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥å·¥åå·">{{ report.productNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥å·¥äºº">{{ report.userName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="设å¤">{{ report.deviceName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="äº§åºæ°é">{{ report.outputQty ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ¥åºæ°é">{{ report.scrapQty ?? '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <el-collapse class="input-collapse" |
| | | :model-value="expandedInputs[reportKey(chain, report)] || []" |
| | | @update:model-value="val => (expandedInputs[reportKey(chain, report)] = val)"> |
| | | <el-collapse-item name="inputs" |
| | | :title="`æå
¥ç©æï¼${inputsFor(chain, report).length}ï¼`"> |
| | | <el-table :data="inputsFor(chain, report)" |
| | | border |
| | | size="small"> |
| | | <el-table-column label="ç©æåç§°" |
| | | prop="materialName" |
| | | align="center" /> |
| | | <el-table-column label="ç©æè§æ ¼" |
| | | prop="materialModel" |
| | | align="center" /> |
| | | <el-table-column label="æå
¥æ°é" |
| | | prop="inputQty" |
| | | align="center" /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | align="center" /> |
| | | </el-table> |
| | | </el-collapse-item> |
| | | </el-collapse> |
| | | </el-timeline-item> |
| | | |
| | | <!-- é¢ææ¹æ¬¡ï¼è®¢åçº§ï¼ --> |
| | | <el-timeline-item v-if="chain.picks && chain.picks.length" |
| | | type="warning" |
| | | placement="top"> |
| | | <div class="node-title">é¢ææ¹æ¬¡</div> |
| | | <el-table :data="chain.picks" |
| | | border |
| | | size="small"> |
| | | <el-table-column label="åææ¹å·" |
| | | align="center" |
| | | min-width="200"> |
| | | <template #default="{ row }"> |
| | | <el-link type="primary" |
| | | @click="$emit('pick-batch', row.batchNo)">{{ row.batchNo }}</el-link> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç©æ" |
| | | prop="materialName" |
| | | align="center" /> |
| | | <el-table-column label="ç©æè§æ ¼" |
| | | prop="materialModel" |
| | | align="center" /> |
| | | <el-table-column label="颿æ°é" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.quantity }} {{ row.unit || '' }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å·¥åº" |
| | | prop="operationName" |
| | | align="center" /> |
| | | <el-table-column label="颿æ¶é´" |
| | | prop="pickTime" |
| | | align="center" /> |
| | | <el-table-column label="ä¾åºå" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.supplierName || '-' }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="éè´ååå·" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.contractNo || '-' }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="åææ£éªç»è®º" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.checkResult || '-' }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-timeline-item> |
| | | |
| | | <!-- æ£éªå --> |
| | | <el-timeline-item v-if="chain.inspects && chain.inspects.length" |
| | | type="success" |
| | | placement="top"> |
| | | <div class="node-title">æ£éªå</div> |
| | | <el-table :data="chain.inspects" |
| | | border |
| | | size="small"> |
| | | <el-table-column label="æ£éªç±»å" |
| | | prop="inspectTypeName" |
| | | align="center" /> |
| | | <el-table-column label="å·¥åº" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.process || '-' }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æ£éªå" |
| | | prop="checkName" |
| | | align="center" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" |
| | | align="center" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | align="center" /> |
| | | <el-table-column label="æ°é" |
| | | prop="quantity" |
| | | align="center" /> |
| | | <el-table-column label="åæ ¼æ°é" |
| | | prop="qualifiedQuantity" |
| | | align="center" /> |
| | | <el-table-column label="ä¸åæ ¼æ°é" |
| | | prop="unqualifiedQuantity" |
| | | align="center" /> |
| | | <el-table-column label="æ£æµç»æ" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.checkResult === 'åæ ¼' ? 'success' : 'danger'"> |
| | | {{ row.checkResult || 'å¾
æ£æµ' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ£æµæ¥æ" |
| | | prop="checkTime" |
| | | align="center" /> |
| | | <el-table-column label="æä½" |
| | | align="center" |
| | | width="100"> |
| | | <template #default="{ row }"> |
| | | <el-link type="primary" |
| | | @click="openInspect(chain, row)">æ¥ç详æ
</el-link> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | |
| | | <!-- åºåºæµåï¼é¡¶å±ï¼ææ¹å·èåï¼ --> |
| | | <div v-if="data.outbounds && data.outbounds.length" |
| | | class="order-card"> |
| | | <div class="order-head"> |
| | | <div class="order-title"> |
| | | <span class="product-name">åºåºæµå</span> |
| | | </div> |
| | | </div> |
| | | <el-table :data="data.outbounds" |
| | | border |
| | | size="small"> |
| | | <el-table-column label="æ¹å·" |
| | | prop="batchNo" |
| | | align="center" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" |
| | | align="center" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="productModel" |
| | | align="center" /> |
| | | <el-table-column label="åºåºæ°é" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.qty }} {{ row.unit || '' }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="åºåºç±»å" |
| | | align="center"> |
| | | <template #default="{ row }">{{ recordTypeLabel(row.recordType) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="åºåºæ¶é´" |
| | | prop="outTime" |
| | | align="center" /> |
| | | <el-table-column label="夿³¨" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.remark || '-' }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ£éªå详æ
--> |
| | | <el-dialog v-model="inspectVisible" |
| | | title="æ£éªå详æ
" |
| | | width="900px"> |
| | | <div v-if="currentInspect" |
| | | class="inspect-detail"> |
| | | <el-descriptions :column="3" |
| | | border |
| | | size="small"> |
| | | <el-descriptions-item label="æ£éªç±»å">{{ currentInspect.inspectTypeName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥åº">{{ currentInspect.process || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ£éªå">{{ currentInspect.checkName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="产ååç§°">{{ currentInspect.productName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·">{{ currentInspect.model || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åä½">{{ currentInspect.unit || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ°é">{{ currentInspect.quantity ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ ¼æ°é">{{ currentInspect.qualifiedQuantity ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸åæ ¼æ°é">{{ currentInspect.unqualifiedQuantity ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ£æµç»æ"> |
| | | <el-tag :type="currentInspect.checkResult === 'åæ ¼' ? 'success' : 'danger'"> |
| | | {{ currentInspect.checkResult || 'å¾
æ£æµ' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ£æµæ¥æ">{{ currentInspect.checkTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸åæ ¼ç°è±¡">{{ currentInspect.defectivePhenomena || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <h4 class="sub-title">ä¸åæ ¼å¤ç</h4> |
| | | <el-table :data="currentUnqualified" |
| | | border |
| | | size="small"> |
| | | <el-table-column label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | align="center" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" |
| | | align="center" /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | align="center" /> |
| | | <el-table-column label="æ°é" |
| | | prop="quantity" |
| | | align="center" /> |
| | | <el-table-column label="ä¸åæ ¼ç°è±¡" |
| | | prop="defectivePhenomena" |
| | | align="center" /> |
| | | <el-table-column label="å¤çç»æ" |
| | | prop="dealResult" |
| | | align="center" /> |
| | | <el-table-column label="å¤ç人" |
| | | prop="dealName" |
| | | align="center" /> |
| | | <el-table-column label="å¤çæ¥æ" |
| | | prop="dealTime" |
| | | align="center" /> |
| | | </el-table> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="inspectVisible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | |
| | | defineProps({ |
| | | data: { |
| | | type: Object, |
| | | required: true, |
| | | }, |
| | | loading: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | }); |
| | | |
| | | defineEmits(["goto-order", "pick-batch"]); |
| | | |
| | | const statusTypeMap = { 1: "primary", 2: "warning", 3: "success", 5: "danger" }; |
| | | const statusTextMap = { 1: "å¾
å¼å§", 2: "è¿è¡ä¸", 3: "已宿", 5: "å·²ç»æ" }; |
| | | const statusType = status => statusTypeMap[status] || "info"; |
| | | const statusText = status => statusTextMap[status] || "已忶"; |
| | | |
| | | const inTypeMap = { |
| | | 0: "èªå®ä¹å
¥åº", |
| | | 2: "ç产æ¥å·¥å
¥åº", |
| | | 6: "è´¨æ£åæ ¼å
¥åº", |
| | | 7: "éè´å
¥åº", |
| | | 10: "éè´å
¥åº", |
| | | }; |
| | | const outTypeMap = { |
| | | 1: "èªå®ä¹åºåº", |
| | | 3: "ç产æ¥å·¥åºåº", |
| | | 8: "éå®åºåº", |
| | | 13: "éå®åè´§åºåº", |
| | | }; |
| | | const recordTypeLabel = type => { |
| | | const key = String(type ?? ""); |
| | | return inTypeMap[key] || outTypeMap[key] || key || "-"; |
| | | }; |
| | | |
| | | const sortReports = reports => |
| | | [...(reports || [])].sort((a, b) => |
| | | String(a.reportTime || "").localeCompare(String(b.reportTime || "")) |
| | | ); |
| | | |
| | | const inputsFor = (chain, report) => |
| | | (chain.inputs || []).filter( |
| | | item => String(item.productMainId) === String(report.productMainId) |
| | | ); |
| | | |
| | | const reportKey = (chain, report) => |
| | | `${chain.productionOrderId}_${report.productMainId}_${report.workOrderNo}`; |
| | | const expandedInputs = reactive({}); |
| | | |
| | | // æ£éªå详æ
|
| | | const inspectVisible = ref(false); |
| | | const currentInspect = ref(null); |
| | | const currentUnqualified = ref([]); |
| | | const openInspect = (chain, inspect) => { |
| | | currentInspect.value = inspect; |
| | | const own = inspect.unqualifiedList || []; |
| | | currentUnqualified.value = own.length |
| | | ? own |
| | | : (chain.unqualifiedList || []).filter( |
| | | item => String(item.inspectId) === String(inspect.inspectId) |
| | | ); |
| | | inspectVisible.value = true; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .forward-result { |
| | | min-height: 200px; |
| | | } |
| | | |
| | | .order-card { |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | padding: 20px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .order-head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .order-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .product-name { |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | } |
| | | |
| | | .order-meta { |
| | | display: flex; |
| | | gap: 28px; |
| | | padding: 12px 0; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .hit-batches { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 8px 12px; |
| | | background-color: #f0f9eb; |
| | | border-radius: 6px; |
| | | |
| | | .hit-label { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | } |
| | | } |
| | | |
| | | .order-timeline { |
| | | margin-top: 20px; |
| | | padding-left: 4px; |
| | | } |
| | | |
| | | .node-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .input-collapse { |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .sub-title { |
| | | margin: 20px 0 12px; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div v-loading="loading" |
| | | class="reverse-result"> |
| | | <!-- åææ¥æº --> |
| | | <div class="material-card"> |
| | | <h3 class="section-title">åææ¥æº</h3> |
| | | <el-descriptions v-if="data.material" |
| | | :column="4" |
| | | border |
| | | size="small"> |
| | | <el-descriptions-item label="åææ¹å·">{{ data.material.batchNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç©æ">{{ data.material.materialName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç©æè§æ ¼">{{ data.material.materialModel || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
¥åºç±»å">{{ recordTypeLabel(data.material.recordType) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
¥åºæ°é">{{ data.material.qty ?? '-' }} {{ data.material.unit || '' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
¥åºæ¶é´">{{ data.material.inTime || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¾åºå">{{ data.material.supplierName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="éè´ååå·">{{ data.material.contractNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="éè´æ¥æ">{{ data.material.entryDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ£éªæ°é">{{ data.material.inspectQty ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ ¼æ°é">{{ data.material.qualifiedQty ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸åæ ¼æ°é">{{ data.material.unqualifiedQty ?? '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ£éªç»è®º">{{ data.material.checkResult || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ£éªæ¥æ">{{ data.material.checkTime || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-alert v-else |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | description="该æ¹å·æ å
¥åº/éè´æ¥æºè®°å½" /> |
| | | </div> |
| | | |
| | | <!-- åå½±å订å --> |
| | | <div class="order-card"> |
| | | <h3 class="section-title">åå½±åç产订åï¼{{ data.orders.length }}ï¼</h3> |
| | | <PIMTable rowKey="productionOrderId" |
| | | :column="tableColumn" |
| | | :tableData="data.orders" |
| | | :page="{ total: data.orders.length }" |
| | | :isShowPagination="false" |
| | | height="520" |
| | | :tableLoading="loading"> |
| | | <template #npsNo="{ row }"> |
| | | <el-link type="primary" |
| | | @click="$emit('goto-order', row.npsNo)">{{ row.npsNo }}</el-link> |
| | | </template> |
| | | <template #orderStatus="{ row }"> |
| | | <el-tag :type="statusType(row.orderStatus)">{{ statusText(row.orderStatus) }}</el-tag> |
| | | </template> |
| | | <template #finishedBatches="{ row }"> |
| | | <template v-if="row.finishedBatches && row.finishedBatches.length"> |
| | | <el-tag v-for="batch in row.finishedBatches" |
| | | :key="batch.batchNo" |
| | | class="batch-tag" |
| | | @click="$emit('goto-forward', batch.batchNo)">{{ batch.batchNo }}</el-tag> |
| | | </template> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | |
| | | defineProps({ |
| | | data: { |
| | | type: Object, |
| | | required: true, |
| | | }, |
| | | loading: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | }); |
| | | |
| | | defineEmits(["goto-order", "goto-forward"]); |
| | | |
| | | const tableColumn = [ |
| | | { |
| | | label: "ç产订åå·", |
| | | prop: "npsNo", |
| | | dataType: "slot", |
| | | slot: "npsNo", |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | label: "æå", |
| | | prop: "finishedProduct", |
| | | minWidth: 220, |
| | | }, |
| | | { |
| | | label: "è®¡åæ°é", |
| | | prop: "planQty", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å®å·¥æ°é", |
| | | prop: "completeQty", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "订åç¶æ", |
| | | prop: "orderStatus", |
| | | dataType: "slot", |
| | | slot: "orderStatus", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "颿æ°é", |
| | | prop: "pickedQty", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "äº§åºæåæ¹å·", |
| | | prop: "finishedBatches", |
| | | dataType: "slot", |
| | | slot: "finishedBatches", |
| | | minWidth: 260, |
| | | }, |
| | | ]; |
| | | |
| | | const statusTypeMap = { 1: "primary", 2: "warning", 3: "success", 5: "danger" }; |
| | | const statusTextMap = { 1: "å¾
å¼å§", 2: "è¿è¡ä¸", 3: "已宿", 5: "å·²ç»æ" }; |
| | | const statusType = status => statusTypeMap[status] || "info"; |
| | | const statusText = status => statusTextMap[status] || "已忶"; |
| | | |
| | | const inTypeMap = { |
| | | 0: "èªå®ä¹å
¥åº", |
| | | 2: "ç产æ¥å·¥å
¥åº", |
| | | 6: "è´¨æ£åæ ¼å
¥åº", |
| | | 7: "éè´å
¥åº", |
| | | 10: "éè´å
¥åº", |
| | | }; |
| | | const outTypeMap = { |
| | | 1: "èªå®ä¹åºåº", |
| | | 3: "ç产æ¥å·¥åºåº", |
| | | 8: "éå®åºåº", |
| | | 13: "éå®åè´§åºåº", |
| | | }; |
| | | const recordTypeLabel = type => { |
| | | const key = String(type ?? ""); |
| | | return inTypeMap[key] || outTypeMap[key] || key || "-"; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .material-card, |
| | | .order-card { |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | padding: 20px; |
| | | margin-bottom: 20px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | margin-bottom: 16px; |
| | | color: #1a1a1a; |
| | | border-bottom: 2px solid #409eff; |
| | | padding-bottom: 10px; |
| | | } |
| | | |
| | | .batch-tag { |
| | | margin: 2px 6px 2px 0; |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="è´¨é追溯" /> |
| | | <el-card class="trace-card"> |
| | | <el-tabs v-model="activeTab" |
| | | @tab-change="handleTabChange"> |
| | | <el-tab-pane label="æ£åè¿½æº¯ï¼æå â åæï¼" |
| | | name="forward" /> |
| | | <el-tab-pane label="åå追溯ï¼åæ â æåï¼" |
| | | name="reverse" /> |
| | | </el-tabs> |
| | | |
| | | <div class="search-bar"> |
| | | <el-select v-model="batchNo" |
| | | filterable |
| | | remote |
| | | reserve-keyword |
| | | clearable |
| | | :placeholder="placeholder" |
| | | :loading="batchLoading" |
| | | :remote-method="handleBatchSearch" |
| | | style="width: 460px;" |
| | | @change="handleBatchChange"> |
| | | <el-option v-for="option in batchOpts" |
| | | :key="option.batchNo" |
| | | :value="option.batchNo" |
| | | :label="optionLabel(option)" /> |
| | | <template #footer> |
| | | <div v-if="batchHasMore" |
| | | class="select-load-more" |
| | | @click="loadMoreBatch"> |
| | | <span v-if="batchLoading">å è½½ä¸...</span> |
| | | <span v-else>ç¹å»å è½½æ´å¤</span> |
| | | </div> |
| | | <div v-else-if="batchOpts.length > 0" |
| | | class="select-load-more no-more"> |
| | | æ²¡ææ´å¤äº |
| | | </div> |
| | | </template> |
| | | </el-select> |
| | | <el-button type="primary" |
| | | :loading="loading" |
| | | @click="handleQuery">æ¥è¯¢</el-button> |
| | | <el-button :loading="exportLoading" |
| | | @click="handleExport">导åº</el-button> |
| | | </div> |
| | | |
| | | <el-empty v-if="!queried" |
| | | description="请è¾å
¥æéæ©æ¹å·åç¹å»æ¥è¯¢" /> |
| | | <el-empty v-else-if="isEmptyResult" |
| | | description="æªæ¥è¯¢å°è¯¥æ¹å·ç追溯记å½" /> |
| | | <ForwardResult v-else-if="isForward" |
| | | :data="forwardData" |
| | | :loading="loading" |
| | | @goto-order="gotoOrder" |
| | | @pick-batch="goReverse" /> |
| | | <ReverseTable v-else |
| | | :data="reverseData" |
| | | :loading="loading" |
| | | @goto-order="gotoOrder" |
| | | @goto-forward="goForward" /> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRouter } from "vue-router"; |
| | | import { saveAs } from "file-saver"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | import ForwardResult from "./components/ForwardResult.vue"; |
| | | import ReverseTable from "./components/ReverseTable.vue"; |
| | | import { |
| | | forwardTrace, |
| | | reverseTrace, |
| | | batchOptions, |
| | | forwardExport, |
| | | reverseExport, |
| | | } from "@/api/qualityManagement/qualityTrace"; |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const BATCH_PAGE = 50; |
| | | const BATCH_MAX = 200; |
| | | |
| | | const activeTab = ref("forward"); |
| | | const isForward = computed(() => activeTab.value === "forward"); |
| | | const batchType = computed(() => (isForward.value ? "finished" : "material")); |
| | | const placeholder = computed(() => |
| | | isForward.value ? "请è¾å
¥æåæ¹å·æç´¢" : "请è¾å
¥åææ¹å·æç´¢" |
| | | ); |
| | | |
| | | // æ¹å·ä¸æ |
| | | const batchNo = ref(""); |
| | | const batchOpts = ref([]); |
| | | const batchLoading = ref(false); |
| | | const batchKeyword = ref(""); |
| | | const batchLimit = ref(BATCH_PAGE); |
| | | const batchHasMore = computed( |
| | | () => batchOpts.value.length >= batchLimit.value && batchLimit.value < BATCH_MAX |
| | | ); |
| | | |
| | | const loading = ref(false); |
| | | const exportLoading = ref(false); |
| | | const queried = ref(false); |
| | | |
| | | const forwardData = reactive({ batchNo: "", chains: [], outbounds: [] }); |
| | | const reverseData = reactive({ batchNo: "", material: null, orders: [] }); |
| | | |
| | | const isEmptyResult = computed(() => { |
| | | if (!queried.value) return false; |
| | | return isForward.value |
| | | ? forwardData.chains.length === 0 && forwardData.outbounds.length === 0 |
| | | : !reverseData.material && reverseData.orders.length === 0; |
| | | }); |
| | | |
| | | const optionLabel = option => |
| | | [option.batchNo, option.productName, option.productModel] |
| | | .filter(Boolean) |
| | | .join(" | "); |
| | | |
| | | // æ¥å£æ offsetï¼åªè½æ¾å¤§ limit åæ´ä½éå |
| | | const fetchBatchOptions = async () => { |
| | | batchLoading.value = true; |
| | | try { |
| | | const res = await batchOptions( |
| | | batchType.value, |
| | | batchKeyword.value, |
| | | batchLimit.value |
| | | ); |
| | | batchOpts.value = res.code === 200 ? res.data || [] : []; |
| | | } catch (error) { |
| | | batchOpts.value = []; |
| | | } finally { |
| | | batchLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleBatchSearch = keyword => { |
| | | batchKeyword.value = keyword || ""; |
| | | batchLimit.value = BATCH_PAGE; |
| | | fetchBatchOptions(); |
| | | }; |
| | | |
| | | const loadMoreBatch = () => { |
| | | if (batchLoading.value || !batchHasMore.value) return; |
| | | batchLimit.value = Math.min(batchLimit.value + BATCH_PAGE, BATCH_MAX); |
| | | fetchBatchOptions(); |
| | | }; |
| | | |
| | | const handleBatchChange = value => { |
| | | if (value) handleQuery(); |
| | | }; |
| | | |
| | | const resetResult = () => { |
| | | forwardData.batchNo = ""; |
| | | forwardData.chains = []; |
| | | forwardData.outbounds = []; |
| | | reverseData.batchNo = ""; |
| | | reverseData.material = null; |
| | | reverseData.orders = []; |
| | | }; |
| | | |
| | | const handleTabChange = () => { |
| | | batchNo.value = ""; |
| | | batchOpts.value = []; |
| | | queried.value = false; |
| | | resetResult(); |
| | | handleBatchSearch(""); |
| | | }; |
| | | |
| | | const handleQuery = async () => { |
| | | const keyword = (batchNo.value || "").trim(); |
| | | if (!keyword) { |
| | | ElMessage.warning("请è¾å
¥æéæ©æ¹å·"); |
| | | return; |
| | | } |
| | | queried.value = true; |
| | | loading.value = true; |
| | | try { |
| | | if (isForward.value) { |
| | | const res = await forwardTrace(keyword); |
| | | if (res.code === 200) { |
| | | forwardData.batchNo = res.data?.batchNo || keyword; |
| | | forwardData.chains = res.data?.chains || []; |
| | | forwardData.outbounds = res.data?.outbounds || []; |
| | | } |
| | | } else { |
| | | const res = await reverseTrace(keyword); |
| | | if (res.code === 200) { |
| | | reverseData.batchNo = res.data?.batchNo || keyword; |
| | | reverseData.material = res.data?.material || null; |
| | | reverseData.orders = res.data?.orders || []; |
| | | } |
| | | } |
| | | } catch (error) { |
| | | // é 200ï¼å¦"æ¹å·ä¸è½ä¸ºç©º"ï¼å·²ç±ååºæ¦æªå¨æç¤º |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleExport = async () => { |
| | | const keyword = (batchNo.value || "").trim(); |
| | | if (!keyword) { |
| | | ElMessage.warning("请å
éæ©æè¾å
¥æ¹å·"); |
| | | return; |
| | | } |
| | | exportLoading.value = true; |
| | | try { |
| | | const blobData = isForward.value |
| | | ? await forwardExport(keyword) |
| | | : await reverseExport(keyword); |
| | | if (blobValidate(blobData)) { |
| | | if (blobData.size === 0) { |
| | | ElMessage.warning("ææ æ°æ®å¯å¯¼åº"); |
| | | return; |
| | | } |
| | | const blob = new Blob([blobData], { |
| | | type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| | | }); |
| | | const name = isForward.value ? "æåæ¹æ¬¡è¿½æº¯" : "åææ¹æ¬¡æµå追溯"; |
| | | saveAs(blob, `${name}_${keyword}.xlsx`); |
| | | ElMessage.success("å¯¼åºæå"); |
| | | } else { |
| | | const resText = await blobData.text(); |
| | | let rspObj = {}; |
| | | try { |
| | | rspObj = JSON.parse(resText); |
| | | } catch (error) { |
| | | rspObj = {}; |
| | | } |
| | | ElMessage.error(rspObj.msg || "导åºå¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error("导åºå¤±è´¥ï¼è¯·ç¨åéè¯"); |
| | | } finally { |
| | | exportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const gotoOrder = npsNo => { |
| | | if (!npsNo) return; |
| | | router.push({ |
| | | path: "/productionManagement/productionTraceability", |
| | | query: { npsNo }, |
| | | }); |
| | | }; |
| | | |
| | | const goReverse = batch => { |
| | | if (!batch) return; |
| | | activeTab.value = "reverse"; |
| | | handleTabChange(); |
| | | batchNo.value = batch; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const goForward = batch => { |
| | | if (!batch) return; |
| | | activeTab.value = "forward"; |
| | | handleTabChange(); |
| | | batchNo.value = batch; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | handleBatchSearch(""); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .trace-card { |
| | | min-height: 82vh; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin: 8px 0 20px; |
| | | } |
| | | |
| | | .select-load-more { |
| | | text-align: center; |
| | | padding: 8px 0; |
| | | font-size: 13px; |
| | | color: #409eff; |
| | | cursor: pointer; |
| | | border-top: 1px solid #ebeef5; |
| | | |
| | | &:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | &.no-more { |
| | | color: #c0c4cc; |
| | | cursor: default; |
| | | |
| | | &:hover { |
| | | background-color: transparent; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | // å·²æäº¤çæ£éªå强å¶åªè¯» |
| | | if (row?.inspectState === 1) { |
| | | operationType.value = "view"; |
| | | } |
| | | getOptions().then(res => { |
| | | supplierList.value = res.data; |
| | | }); |
| | |
| | | |
| | | // æä»· |
| | | const submit = async id => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | getList(); |
| | | } |
| | | ElMessageBox.confirm( |
| | | "æäº¤åå°çæå
¥åºè®°å½ä¸ä¸å¯ä¿®æ¹ï¼ç¡®è®¤æäº¤ï¼", |
| | | "æç¤º", |
| | | { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning" } |
| | | ) |
| | | .then(async () => { |
| | | const res = await submitQualityInspect({ id: id }); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess( |
| | | "æäº¤æåï¼åæ ¼é¨åå·²çæå
¥åºè®°å½ï¼å¾
å®¡æ ¸ï¼ï¼ä¸åæ ¼é¨åå·²çæå¤çå" |
| | | ); |
| | | getList(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | |
| | | const { VITE_APP_ENV } = env; |
| | | const baseUrl = |
| | | env.VITE_APP_ENV === "development" |
| | | ? "http://localhost:7005" |
| | | ? "http://192.168.0.10:7005" |
| | | : env.VITE_BASE_API; |
| | | const javaUrl = |
| | | env.VITE_APP_ENV === "development" |