| | |
| | | type: Array, |
| | | default: () => ['png', 'jpg', 'jpeg', 'webp'], |
| | | }, |
| | | watermarkText: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | buttonText: { |
| | | type: String, |
| | | default: 'ä¸ä¼ å¾ç', |
| | |
| | | return true |
| | | } |
| | | |
| | | async function addWatermarkToFile(rawFile) { |
| | | if (!props.watermarkText) { |
| | | return rawFile |
| | | } |
| | | |
| | | const imageUrl = await readFileAsDataURL(rawFile) |
| | | const image = await loadImage(imageUrl) |
| | | const canvas = document.createElement('canvas') |
| | | const ctx = canvas.getContext('2d') |
| | | |
| | | if (!ctx) { |
| | | return rawFile |
| | | } |
| | | |
| | | canvas.width = image.width |
| | | canvas.height = image.height |
| | | ctx.drawImage(image, 0, 0, canvas.width, canvas.height) |
| | | |
| | | const padding = Math.max(16, Math.round(canvas.width * 0.02)) |
| | | const fontSize = Math.max(24, Math.round(canvas.width * 0.03)) |
| | | const lineHeight = Math.round(fontSize * 1.35) |
| | | const lines = String(props.watermarkText).split('\n').filter(Boolean) |
| | | |
| | | ctx.fillStyle = 'rgba(0, 0, 0, 0.32)' |
| | | const blockHeight = lines.length * lineHeight + padding * 2 |
| | | ctx.fillRect(0, canvas.height - blockHeight, canvas.width, blockHeight) |
| | | ctx.fillStyle = 'rgba(255, 255, 255, 0.96)' |
| | | ctx.font = `${fontSize}px sans-serif` |
| | | ctx.textBaseline = 'bottom' |
| | | |
| | | lines.forEach((line, index) => { |
| | | ctx.fillText(line, padding, canvas.height - padding - (lines.length - 1 - index) * lineHeight) |
| | | }) |
| | | |
| | | const mimeType = rawFile.type || 'image/jpeg' |
| | | const blob = await new Promise((resolve) => canvas.toBlob(resolve, mimeType, 0.92)) |
| | | if (!blob) { |
| | | return rawFile |
| | | } |
| | | |
| | | return new File([blob], rawFile.name, { type: mimeType, lastModified: Date.now() }) |
| | | } |
| | | |
| | | function readFileAsDataURL(file) { |
| | | return new Promise((resolve, reject) => { |
| | | const reader = new FileReader() |
| | | reader.onload = () => resolve(reader.result) |
| | | reader.onerror = reject |
| | | reader.readAsDataURL(file) |
| | | }) |
| | | } |
| | | |
| | | function loadImage(src) { |
| | | return new Promise((resolve, reject) => { |
| | | const img = new Image() |
| | | img.onload = () => resolve(img) |
| | | img.onerror = reject |
| | | img.src = src |
| | | }) |
| | | } |
| | | |
| | | function scheduleUpload(uploadFiles) { |
| | | clearTimeout(uploadQueueTimer.value) |
| | | uploadQueueTimer.value = setTimeout(() => { |
| | |
| | | } |
| | | |
| | | const formData = new FormData() |
| | | validFiles.forEach((file) => { |
| | | formData.append(props.uploadFieldName, file.raw) |
| | | }) |
| | | for (const file of validFiles) { |
| | | const uploadFileObj = await addWatermarkToFile(file.raw) |
| | | formData.append(props.uploadFieldName, uploadFileObj) |
| | | } |
| | | |
| | | uploading.value = true |
| | | proxy.$modal.loading('å¾çä¸ä¼ ä¸ï¼è¯·ç¨å...') |
| | |
| | | { value: "quotation", label: "æ¥ä»·å®¡æ¹", cellBg: "#f4ecfc", cellColor: "#9b59b6" }, |
| | | { value: "shipment", label: "å货审æ¹", cellBg: "#e8faf6", cellColor: "#1abc9c" }, |
| | | { value: "enterprise_news", label: "ä¼ä¸æ°é»", cellBg: "#ecf5ff", cellColor: "#409eff" }, |
| | | { value: "production_report", label: "æ¥å·¥å®¡æ¹", cellBg: "#eef5ff", cellColor: "#1d4ed8" }, |
| | | ]; |
| | | |
| | | /** å表æ¥è¯¢ï¼å®¡æ¹ç¶æï¼ä¸å端 status æä¸¾ä¸è´ï¼ */ |
| | |
| | | <div class="detail-block"> |
| | | <div class="detail-block-title">å¡«æ¥å
容</div> |
| | | <!-- é»è®¤è¡¨åå±ç¤º --> |
| | | <FormPayloadFields v-if="!isSpecialApprovalType" |
| | | <FormPayloadFields v-if="!isSpecialApprovalType && row.businessType !== 19" |
| | | :fields="formResolved.fields" |
| | | :form-payload="formResolved.formPayload" |
| | | readonly /> |
| | |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- æ¥å·¥å®¡æ¹è¯¦æ
--> |
| | | <template v-else-if="row.businessType === 19"> |
| | | <div v-if="formResolved.fields.length" class="report-detail"> |
| | | <el-divider content-position="left">æ¥å·¥å¡«æ¥å
容</el-divider> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item |
| | | v-for="field in formResolved.fields" |
| | | :key="field.key" |
| | | :label="field.label || field.key" |
| | | > |
| | | {{ formatFieldDisplayValue(field, formResolved.formPayload?.[field.key]) }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | <el-empty v-else description="ææ æ¥å·¥å¡«æ¥å
容" /> |
| | | </template> |
| | | |
| | | <!-- éè´å®¡æ¹è¯¦æ
--> |
| | |
| | | approvalStatusLabel, |
| | | approvalStatusTagType, |
| | | resolveInstanceFormFields, |
| | | formatFieldDisplayValue, |
| | | } from "../approveListConstants.js"; |
| | | import FormPayloadFields from "./FormPayloadFields.vue"; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // æ¥å·¥å®¡æ¹ï¼ç´æ¥å±ç¤ºå®¡æ¹å¡«æ¥å
容 |
| | | else if (row.businessType === 19) { |
| | | detailData.value = {}; |
| | | } |
| | | |
| | | // å
¶ä»å®¡æ¹ç±»å |
| | | else { |
| | | detailData.value = {}; |
| | |
| | | } |
| | | } |
| | | |
| | | // æ¥å·¥å®¡æ¹ï¼ç´æ¥å±ç¤ºå®¡æ¹å¡«æ¥å
容 |
| | | else if (row.businessType === 19) { |
| | | detailData.value = {}; |
| | | } |
| | | |
| | | // å
¶ä»å®¡æ¹ç±»å |
| | | else { |
| | | detailData.value = {}; |
| | |
| | | TRAVEL_REIMBURSE: "travel_reimburse", |
| | | COST_REIMBURSE: "cost_reimburse", |
| | | ENTERPRISE_NEWS: "enterprise_news", |
| | | PRODUCTION_REPORT: "production_report", |
| | | }; |
| | | |
| | | /** 审æ¹å®ä¾ listPage / ä¿å 使ç¨ç businessType æä¸¾ */ |
| | |
| | | [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16, |
| | | [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17, |
| | | [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18, |
| | | [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: 19, |
| | | }; |
| | | |
| | | /** @type {Record<string, import('./approvalModuleRegistry.js').ApprovalModuleConfig>} */ |
| | |
| | | businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS], |
| | | typeLabels: ["ä¼ä¸æ°é»", "æ°é»", "æ°é»åå¸"], |
| | | }, |
| | | [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: { |
| | | label: "æ¥å·¥å®¡æ¹", |
| | | approvalType: "production_report", |
| | | businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.PRODUCTION_REPORT], |
| | | typeLabels: ["æ¥å·¥", "ç产æ¥å·¥", "æ¥å·¥å®¡æ¹"], |
| | | }, |
| | | }; |
| | | |
| | | /** |
| | |
| | | :xl="8" |
| | | class="left-col"> |
| | | <div class="left-panel"> |
| | | <el-alert |
| | | title="å·¥èµæ ¸ç®å£å¾ï¼ä»
ç»è®¡å®¡æ ¸éè¿çåæ ¼æ¥å·¥ï¼è½»å¾®è¿å·¥ã严éè¿å·¥ãæ¥åºåä¸è®¡èªã" |
| | | type="info" |
| | | :closable="false" |
| | | show-icon |
| | | /> |
| | | <div class="left-header"> |
| | | <el-form :model="searchForm" |
| | | inline> |
| | | <el-form-item label="æ¥å·¥äººåï¼"> |
| | | <el-input v-model="searchForm.schedulingUserName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px" |
| | | @change="reloadData" /> |
| | | </el-form-item> |
| | | <el-form-item label="å·¥åç¼å·ï¼"> |
| | | <el-input v-model="searchForm.workOrderNo" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 160px" |
| | | @change="reloadData" /> |
| | | </el-form-item> |
| | | <el-form-item prop="dateType"> |
| | | <el-radio-group v-model="searchForm.dateType" |
| | | size="small" |
| | |
| | | :xl="16" |
| | | class="right-col"> |
| | | <div class="right-panel"> |
| | | <el-form inline> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleOut">导åº</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="right-toolbar"> |
| | | <el-button type="primary" @click="handleBatchAccounting">æ¹éæ ¸ç®</el-button> |
| | | <el-button type="warning" @click="handleSingleAdjust">å个修æ£</el-button> |
| | | <el-button @click="handleOut">导åºå·¥èµæ¡</el-button> |
| | | </div> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page1" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading1" |
| | | style="margin-right: 20px;" |
| | | @pagination="pagination1"></PIMTable> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <el-dialog v-model="adjustDialogVisible" |
| | | title="å个修æ£" |
| | | width="520px"> |
| | | <el-form :model="adjustForm" label-width="120px"> |
| | | <el-form-item label="æ¥å·¥äººå"> |
| | | <el-input v-model="adjustForm.schedulingUserName" disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="å·¥èµ"> |
| | | <el-input-number v-model="adjustForm.wages" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¿®æ£è¯´æ"> |
| | | <el-input v-model="adjustForm.remark" type="textarea" :rows="3" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="adjustDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="confirmAdjust">确认</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from "vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | |
| | | } from "@/api/productionManagement/productionCosting.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const formatTag = (val, map) => { |
| | | const item = map[String(val)]; |
| | | if (!item) return "-"; |
| | | return item.label; |
| | | }; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "æ¥å·¥åå·", |
| | | prop: "reportNo", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "çäº§æ¥æ", |
| | | prop: "schedulingDate", |
| | |
| | | prop: "schedulingUserName", |
| | | minWidth: 100, |
| | | }, |
| | | // { |
| | | // label: "ååå·", |
| | | // prop: "salesContractNo", |
| | | // minWidth: 100, |
| | | // }, |
| | | // { |
| | | // label: "客æ·åç§°", |
| | | // prop: "customerName", |
| | | // minWidth: 100, |
| | | // }, |
| | | { |
| | | label: "å®¡æ ¸ç¶æ", |
| | | prop: "auditStatusName", |
| | | minWidth: 100, |
| | | dataType: "tag", |
| | | formatData: val => formatTag(val, { |
| | | 0: { label: "å¾
å®¡æ ¸" }, |
| | | 1: { label: "å®¡æ ¸éè¿" }, |
| | | 2: { label: "å®¡æ ¸ä¸éè¿" }, |
| | | }), |
| | | formatType: val => { |
| | | const typeMap = { |
| | | 0: "warning", |
| | | 1: "success", |
| | | 2: "danger", |
| | | }; |
| | | return typeMap[String(val)] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "æ¥å·¥ç±»å", |
| | | prop: "reportTypeName", |
| | | minWidth: 100, |
| | | dataType: "tag", |
| | | formatData: val => formatTag(val, { |
| | | 0: { label: "åæ ¼" }, |
| | | 1: { label: "轻微è¿å·¥" }, |
| | | 2: { label: "严éè¿å·¥" }, |
| | | 3: { label: "æ¥åº" }, |
| | | }), |
| | | formatType: val => { |
| | | const typeMap = { |
| | | 0: "success", |
| | | 1: "warning", |
| | | 2: "danger", |
| | | 3: "info", |
| | | }; |
| | | return typeMap[String(val)] || "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "éå®ååå·", |
| | | prop: "salesContractNo", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "产å大类", |
| | | prop: "productName", |
| | |
| | | label: "å·¥åº", |
| | | prop: "process", |
| | | minWidth: 100, |
| | | }, |
| | | { |
| | | label: "å·¥åç¼å·", |
| | | prop: "workOrderNo", |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | label: "å·¥æ¶ï¼hï¼", |
| | |
| | | const tableLoading = ref(false); |
| | | const tableLoading1 = ref(false); |
| | | const leftTableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const adjustDialogVisible = ref(false); |
| | | const adjustForm = reactive({ |
| | | id: null, |
| | | schedulingUserName: "", |
| | | wages: 0, |
| | | remark: "", |
| | | }); |
| | | // æ¥ / æ 忢ï¼é»è®¤ææ¥ï¼ |
| | | const page = reactive({ |
| | | current: 1, |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | schedulingUserName: "", |
| | | workOrderNo: "", |
| | | salesContractNo: "", |
| | | dateType: "day", |
| | | dateRange: dayjs().format("YYYY-MM-DD"), |
| | |
| | | page1.current = obj.page; |
| | | page1.size = obj.limit; |
| | | getList1(); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection || []; |
| | | }; |
| | | |
| | | const handleDateRangeChange = value => { |
| | |
| | | .then(() => { |
| | | proxy.download( |
| | | "/salesLedger/productionAccounting/export", |
| | | {}, |
| | | { ...searchForm.value }, |
| | | "çäº§æ ¸ç®.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchAccounting = () => { |
| | | proxy.$modal.msgWarning("å½å页é¢å·²æå®¡æ ¸éè¿çåæ ¼æ¥å·¥æ±æ»å·¥èµï¼æ¹éæ ¸ç®æ¥å£åç»å¯ç´æ¥æ¥å
¥ã"); |
| | | }; |
| | | |
| | | const handleSingleAdjust = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("请å
å¾é䏿¡è®°å½"); |
| | | return; |
| | | } |
| | | if (selectedRows.value.length > 1) { |
| | | proxy.$modal.msgWarning("å个修æ£åªè½éæ©ä¸æ¡è®°å½"); |
| | | return; |
| | | } |
| | | const row = selectedRows.value[0]; |
| | | adjustForm.id = row.id; |
| | | adjustForm.schedulingUserName = row.schedulingUserName || ""; |
| | | adjustForm.wages = Number(row.wages || 0); |
| | | adjustForm.remark = ""; |
| | | adjustDialogVisible.value = true; |
| | | }; |
| | | |
| | | const confirmAdjust = () => { |
| | | proxy.$modal.msgSuccess("å·²ä¿åä¿®æ£è¯´æï¼åç»å¯æ¥å
¥å·¥èµä¿®æ£æ¥å£ã"); |
| | | adjustDialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleRowAdjust = row => { |
| | | selectedRows.value = [row]; |
| | | handleSingleAdjust(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | min-width: 0; |
| | | } |
| | | |
| | | .right-toolbar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .left-header { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | <el-input v-model="form.totalPrice" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥å·¥ç±»åï¼" prop="reportType"> |
| | | <el-select v-model="form.reportType" style="width: 100%;" placeholder="è¯·éæ©"> |
| | | <el-option label="åæ ¼" :value="0" /> |
| | | <el-option label="轻微è¿å·¥" :value="1" /> |
| | | <el-option label="严éè¿å·¥" :value="2" /> |
| | | <el-option label="æ¥åº" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import { getCurrentInstance, reactive, ref, toRefs } from "vue"; |
| | | // import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js"; |
| | |
| | | schedulingDate: "", |
| | | unitPrice: "", |
| | | totalPrice: "", |
| | | reportType: 0, |
| | | }, |
| | | rules: { |
| | | schedulingNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="æ¥å·¥å°è´¦" /> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | |
| | | style="width: 200px;" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥å·¥ç±»å:"> |
| | | <el-select v-model="searchForm.reportType" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 200px;" |
| | | @change="handleQuery"> |
| | | <el-option label="åæ ¼" :value="0" /> |
| | | <el-option label="轻微è¿å·¥" :value="1" /> |
| | | <el-option label="严éè¿å·¥" :value="2" /> |
| | | <el-option label="æ¥åº" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æç´¢</el-button> |
| | |
| | | <div class="table_list"> |
| | | <div style="text-align: right" |
| | | class="mb10"> |
| | | <!-- <el-button type="primary" |
| | | @click="openForm('add')">ç产æ¥å·¥</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </div> |
| | | <PIMTable rowKey="id" |
| | |
| | | nickName: "", |
| | | workOrderNo: "", |
| | | workOrderStatus: "", |
| | | reportType: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | |
| | | label: "æ¥å·¥äººå", |
| | | prop: "nickName", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ¥å·¥ç±»å", |
| | | prop: "reportTypeName", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å®¡æ ¸ç¶æ", |
| | | prop: "auditStatusName", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å·¥æ¶ï¼hï¼", |
| | |
| | | }); |
| | | }); |
| | | }; |
| | | const showInput = row => { |
| | | isShowInput.value = true; |
| | | isShowingId.value = row.id; |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records.map(item => ({ |
| | | ...item, |
| | | reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType), |
| | | auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus), |
| | | pendingFinishNum: |
| | | (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0), |
| | | })); |
| | |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, ["finishedNum"]); |
| | | }; |
| | | const resolveReportTypeName = value => { |
| | | const map = { |
| | | 0: "åæ ¼", |
| | | 1: "轻微è¿å·¥", |
| | | 2: "严éè¿å·¥", |
| | | 3: "æ¥åº", |
| | | }; |
| | | return map[Number(value)] || "â"; |
| | | }; |
| | | const resolveAuditStatusName = value => { |
| | | const map = { |
| | | 0: "å¾
å®¡æ ¸", |
| | | 1: "å®¡æ ¸éè¿", |
| | | 2: "å®¡æ ¸ä¸éè¿", |
| | | }; |
| | | return map[Number(value)] || "â"; |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | if (selectedRows.value.length !== 1) { |
| | |
| | | // æå¼æå
¥æ¨¡ææ¡ |
| | | const isShowInput = ref(false); |
| | | const isShowingId = ref(0); |
| | | const showInput = row => { |
| | | isShowInput.value = true; |
| | | isShowingId.value = row.id; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/productionProductMain/export", {}, "ç产æ¥å·¥.xlsx"); |
| | | const params = { ...searchForm.value }; |
| | | params.entryDate = undefined; |
| | | proxy.download("/productionProductMain/export", params, "ç产æ¥å·¥.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <PageHeader content="ç产æ¥å·¥" /> |
| | | <div class="search_form mb20"> |
| | | <div class="search-row"> |
| | | <div class="search-item"> |
| | | <span class="search_title">æ¥å·¥åå·ï¼</span> |
| | | <el-input v-model="searchForm.productNo" |
| | | style="width: 220px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <span class="search_title">å·¥åç¼å·ï¼</span> |
| | | <el-input v-model="searchForm.workOrderNo" |
| | | style="width: 220px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <span class="search_title">æ¥å·¥äººåï¼</span> |
| | | <el-input v-model="searchForm.nickName" |
| | | style="width: 220px" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <span class="search_title">æ¥å·¥ç±»åï¼</span> |
| | | <el-select v-model="searchForm.reportType" |
| | | style="width: 180px" |
| | | clearable |
| | | placeholder="è¯·éæ©" |
| | | @change="handleQuery"> |
| | | <el-option label="åæ ¼" :value="0" /> |
| | | <el-option label="轻微è¿å·¥" :value="1" /> |
| | | <el-option label="严éè¿å·¥" :value="2" /> |
| | | <el-option label="æ¥åº" :value="3" /> |
| | | </el-select> |
| | | </div> |
| | | <div class="search-item"> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" /> |
| | | </div> |
| | | |
| | | <input-modal v-if="isShowInput" |
| | | v-model:visible="isShowInput" |
| | | :production-product-main-id="isShowingId" /> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, reactive, ref, toRefs } from "vue"; |
| | | import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js"; |
| | | import InputModal from "@/views/productionManagement/productionReporting/Input.vue"; |
| | | |
| | | const tableLoading = ref(false); |
| | | const tableData = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productNo: "", |
| | | workOrderNo: "", |
| | | nickName: "", |
| | | reportType: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const tableColumn = [ |
| | | { label: "æ¥å·¥åå·", prop: "productNo", width: 140 }, |
| | | { label: "æ¥å·¥äººå", prop: "nickName", width: 120 }, |
| | | { label: "å·¥åº", prop: "process", width: 140 }, |
| | | { label: "å·¥åç¼å·", prop: "workOrderNo", width: 140 }, |
| | | { label: "äº§åºæ°é", prop: "quantity", width: 120 }, |
| | | { label: "æ¥åºæ°é", prop: "scrapQty", width: 120 }, |
| | | { label: "å·¥æ¶ï¼hï¼", prop: "workHour", width: 100 }, |
| | | { label: "æ¥å·¥ç»æ", prop: "reportTypeName", width: 100 }, |
| | | { label: "å®¡æ ¸ç¶æ", prop: "auditStatusName", width: 100 }, |
| | | { label: "å建æ¶é´", prop: "createTime", width: 160 }, |
| | | { |
| | | label: "æä½", |
| | | dataType: "action", |
| | | width: 120, |
| | | fixed: "right", |
| | | operation: [ |
| | | { |
| | | name: "æ¥çæå
¥", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | showInput(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | const isShowInput = ref(false); |
| | | const isShowingId = ref(0); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | productionProductMainListPage({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableData.value = (res.data.records || []).map((item) => ({ |
| | | ...item, |
| | | reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType), |
| | | auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus), |
| | | })); |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resolveReportTypeName = (val) => { |
| | | const map = { |
| | | 0: "åæ ¼", |
| | | 1: "轻微è¿å·¥", |
| | | 2: "严éè¿å·¥", |
| | | 3: "æ¥åº", |
| | | }; |
| | | return map[Number(val)] || "â"; |
| | | }; |
| | | |
| | | const resolveAuditStatusName = (val) => { |
| | | const map = { |
| | | 0: "å¾
å®¡æ ¸", |
| | | 1: "å®¡æ ¸éè¿", |
| | | 2: "å®¡æ ¸ä¸éè¿", |
| | | }; |
| | | return map[Number(val)] || "â"; |
| | | }; |
| | | |
| | | const showInput = (row) => { |
| | | isShowingId.value = row.id; |
| | | isShowInput.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 12px 16px; |
| | | } |
| | | |
| | | .search-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | </style> |
| | |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <!-- æµè½¬å¡å¼¹çª --> |
| | | <!-- äºç»´ç å¼¹çª --> |
| | | <el-dialog v-model="transferCardVisible" |
| | | title="æµè½¬å¡" |
| | | width="1000px"> |
| | | <div class="transfer-card-title">å·¥åæµè½¬å¡</div> |
| | | <div class="transfer-card-container"> |
| | | <div class="transfer-card-info"> |
| | | <div class="info-group"> |
| | | <div class="info-item"> |
| | | <span class="info-label">å·¥åç¼å·</span> |
| | | <span class="info-value">{{ transferCardRowData.workOrderNo }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">产ååç§°</span> |
| | | <span class="info-value">{{ transferCardRowData.productName }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">è§æ ¼åå·</span> |
| | | <span class="info-value">{{ transferCardRowData.model }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">计åå¼å§æ¶é´</span> |
| | | <span class="info-value">{{ transferCardRowData.planStartTime }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">计åç»ææ¶é´</span> |
| | | <span class="info-value">{{ transferCardRowData.planEndTime }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">夿³¨</span> |
| | | <span class="info-value">{{ transferCardRowData.remark }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-group"> |
| | | <div class="info-item"> |
| | | <span class="info-label">éæ±æ°é</span> |
| | | <span class="info-value">{{ transferCardRowData.planQuantity }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-value">{{ transferCardRowData.completeQuantity }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">è¯åæ°é</span> |
| | | <span class="info-value">0</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">ä¸è¯åæ°</span> |
| | | <span class="info-value">0</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">å®é
å¼å§æ¶é´</span> |
| | | <span class="info-value">{{ transferCardRowData.actualStartTime }}</span> |
| | | </div> |
| | | <div class="info-item"> |
| | | <span class="info-label">å®é
ç»ææ¶é´</span> |
| | | <span class="info-value">{{ transferCardRowData.actualEndTime }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="transfer-card-qr"> |
| | | <div class="qr-container"> |
| | | <img :src="transferCardQrUrl" |
| | | alt="æµè½¬å¡äºç»´ç " |
| | | style="width: 200px; height: 200px;" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="print-button-container" |
| | | style=" text-align: center; |
| | | margin-bottom: 40px;"> |
| | | <el-button type="primary" |
| | | style="margin-top: 20px;" |
| | | @click="printTransferCard">æå°æµè½¬å¡</el-button> |
| | | title="äºç»´ç " |
| | | width="420px" |
| | | align-center> |
| | | <div class="qr-only-dialog"> |
| | | <canvas ref="transferCardQrCanvas" |
| | | width="280" |
| | | height="280" |
| | | class="qr-canvas" /> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- æ¥å·¥å¼¹çª --> |
| | |
| | | style="width: 300px" |
| | | placeholder="请è¾å
¥æ¥åºæ°é" |
| | | @input="handleScrapQtyInput" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥å·¥ç±»å" |
| | | prop="reportType"> |
| | | <el-select v-model="reportForm.reportType" |
| | | style="width: 300px" |
| | | placeholder="è¯·éæ©æ¥å·¥ç±»å"> |
| | | <el-option label="åæ ¼" :value="0" /> |
| | | <el-option label="轻微è¿å·¥" :value="1" /> |
| | | <el-option label="严éè¿å·¥" :value="2" /> |
| | | <el-option label="æ¥åº" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="çç»ä¿¡æ¯"> |
| | | <el-select v-model="reportForm.userId" |
| | |
| | | }, |
| | | { |
| | | label: "æä½", |
| | | width: "260", |
| | | width: "320", |
| | | align: "center", |
| | | dataType: "action", |
| | | fixed: "right", |
| | |
| | | name: "æµè½¬å¡", |
| | | clickFun: row => { |
| | | downloadAndPrintWorkOrder(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "äºç»´ç ", |
| | | clickFun: row => { |
| | | showTransferCard(row); |
| | | }, |
| | | }, |
| | | { |
| | |
| | | const tableLoading = ref(false); |
| | | const transferCardVisible = ref(false); |
| | | const transferCardData = ref([]); |
| | | const transferCardQrUrl = ref(""); |
| | | const transferCardQrCanvas = ref(null); |
| | | const transferCardRowData = ref(null); |
| | | const reportDialogVisible = ref(false); |
| | | const fileDialogVisible = ref(false); |
| | |
| | | planQuantity: 0, |
| | | quantity: null, |
| | | scrapQty: null, |
| | | reportType: 0, |
| | | userName: "", |
| | | workOrderId: "", |
| | | reportWork: "", |
| | |
| | | |
| | | const showTransferCard = async row => { |
| | | transferCardRowData.value = row; |
| | | const qrContent = String(row.id); |
| | | |
| | | transferCardQrUrl.value = await QRCode.toDataURL(qrContent); |
| | | transferCardVisible.value = true; |
| | | await nextTick(); |
| | | const qrContent = String(row.id); |
| | | if (transferCardQrCanvas.value) { |
| | | const ctx = transferCardQrCanvas.value.getContext("2d"); |
| | | ctx?.clearRect(0, 0, 280, 280); |
| | | await QRCode.toCanvas(transferCardQrCanvas.value, qrContent, { |
| | | width: 280, |
| | | margin: 1, |
| | | errorCorrectionLevel: "M", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const printTransferCard = () => { |
| | | window.print(); |
| | | }; |
| | | const currentWorkOrderRow = ref(null); |
| | | |
| | | const openWorkOrderFiles = row => { |
| | |
| | | reportForm.planQuantity = remainingQuantity; |
| | | reportForm.quantity = |
| | | row.quantity !== undefined && row.quantity !== null ? row.quantity : null; |
| | | reportForm.reportType = row.reportType ?? 0; |
| | | reportForm.productProcessRouteItemId = row.productProcessRouteItemId; |
| | | reportForm.workOrderId = row.id; |
| | | reportForm.reportWork = row.reportWork; |
| | |
| | | const submitParams = { |
| | | quantity: quantity, |
| | | scrapQty: isNaN(scrapQty) ? 0 : scrapQty, |
| | | reportType: reportForm.reportType, |
| | | userId: reportForm.userId, |
| | | userName: reportForm.userName, |
| | | productionOperationTaskId: reportForm.workOrderId, |
| | |
| | | text-align: center; |
| | | margin-top: 20px; |
| | | } |
| | | .qr-only-dialog { |
| | | padding: 12px 0 28px; |
| | | } |
| | | .qr-canvas { |
| | | display: block; |
| | | margin: 0 auto; |
| | | } |
| | | .param-grid { |
| | | margin-top: 10px; |
| | | border-top: 1px solid #ebe9f3; |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸åæ ¼ç±»åï¼" |
| | | prop="dealType"> |
| | | <el-select v-model="form.dealType" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 100%"> |
| | | <el-option label="轻微è¿å·¥" :value="0" /> |
| | | <el-option label="严éè¿å·¥" :value="1" /> |
| | | <el-option label="æ¥åº" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¿®çå·¥æ¶ï¼" |
| | | prop="repairWorkHour"> |
| | | <el-input-number v-model="form.repairWorkHour" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | :disabled="form.dealType !== 0" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éæ°çææ°åï¼" |
| | | prop="regenerateNewOrder"> |
| | | <el-select v-model="form.regenerateNewOrder" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 100%"> |
| | | <el-option label="å¦" :value="0" /> |
| | | <el-option label="æ¯" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¤ç人ï¼" |
| | | prop="dealName"> |
| | | <el-select v-model="form.dealName" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ä¸åæ ¼å¾çï¼" |
| | | prop="photoUrls"> |
| | | <AttachmentUploadImage |
| | | v-model:fileList="form.photoUrls" |
| | | :limit="6" |
| | | button-text="æç
§/ä¸ä¼ å¾ç" |
| | | :watermark-text="watermarkText" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs } from "vue"; |
| | | import { computed, getCurrentInstance, ref, reactive, toRefs, watch } from "vue"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import AttachmentUploadImage from "@/components/AttachmentUpload/image/index.vue"; |
| | | import { |
| | | getQualityUnqualifiedInfo, |
| | | qualityUnqualifiedAdd, |
| | |
| | | inspectType: "", |
| | | defectivePhenomena: "", |
| | | dealResult: "", |
| | | dealType: 0, |
| | | repairWorkHour: 0, |
| | | regenerateNewOrder: 0, |
| | | dealName: "", |
| | | dealTime: "", |
| | | productModelId: undefined, |
| | |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const userList = ref([]); // æ£éªå/å¤çäººä¸æå表 |
| | | const watermarkText = computed(() => { |
| | | const userName = userStore.nickName || userStore.user?.nickName || "æªç¥äººå"; |
| | | const now = new Date(); |
| | | const pad = (n) => String(n).padStart(2, "0"); |
| | | return `ä¸åæ ¼å¤ç\n${userName}\n${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; |
| | | }); |
| | | watch(() => form.value.dealType, (val) => { |
| | | if (val !== 0) { |
| | | form.value.repairWorkHour = 0; |
| | | } |
| | | }); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | |
| | | dealName: "", |
| | | dealTime: "", |
| | | dealResult: "", |
| | | dealType: 0, |
| | | repairWorkHour: 0, |
| | | regenerateNewOrder: 0, |
| | | photoUrls: [], |
| | | defectivePhenomena: "", |
| | | inspectType: "", |
| | | checkTime: "", |
| | |
| | | if (operationType.value === "edit") { |
| | | getQualityUnqualifiedInfo(row.id).then(res => { |
| | | const { inspectState, ...rest } = res.data || {}; |
| | | form.value = { ...rest }; |
| | | form.value = { |
| | | ...rest, |
| | | photoUrls: rest.photoUrls |
| | | ? String(rest.photoUrls).split(",").filter(Boolean).map((url, index) => ({ |
| | | name: `image-${index + 1}`, |
| | | url, |
| | | })) |
| | | : [], |
| | | }; |
| | | }); |
| | | } |
| | | }; |
| | |
| | | if (valid) { |
| | | // ç¶æåæ®µä¸å¨è¡¨åå¡«åï¼ä¹ä¸ä¼ ç»å端 |
| | | const { inspectState, ...payload } = form.value || {}; |
| | | if (payload.dealType === 0 && (!payload.repairWorkHour || payload.repairWorkHour <= 0)) { |
| | | proxy.$modal.msgError("轻微è¿å·¥å¿
须填å大äº0çä¿®çå·¥æ¶"); |
| | | return; |
| | | } |
| | | payload.photoUrls = Array.isArray(payload.photoUrls) |
| | | ? payload.photoUrls.map(item => item.url || item.previewURL || item.previewUrl || item).filter(Boolean).join(",") |
| | | : payload.photoUrls; |
| | | if (operationType.value === "add") { |
| | | qualityUnqualifiedAdd(payload).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ä¸åæ ¼ç±»å", |
| | | prop: "dealType", |
| | | width: 120, |
| | | formatData: val => { |
| | | if (val === 0) return "轻微è¿å·¥"; |
| | | if (val === 1) return "严éè¿å·¥"; |
| | | if (val === 2) return "æ¥åº"; |
| | | return "-"; |
| | | } |
| | | }, |
| | | { |
| | | label: "ä¿®çå·¥æ¶", |
| | | prop: "repairWorkHour", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "éçæ°å", |
| | | prop: "regenerateNewOrder", |
| | | width: 100, |
| | | formatData: val => (val === 1 ? "æ¯" : "å¦") |
| | | }, |
| | | { |
| | | label: "å¤ç人", |
| | | prop: "dealName", |
| | | width: 120 |