From a52ebf49d0a304697ec7af4f4c3e6b6e13f269e7 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 15 七月 2026 15:28:40 +0800
Subject: [PATCH] 修改生产核算,不合格管理,报表管理
---
src/views/qualityManagement/nonconformingManagement/components/formDia.vue | 105 ++++++---
src/views/productionManagement/workOrder/components/filesDia.vue | 66 ++++++
src/views/reportAnalysis/reportManagement/index.vue | 86 ++++++++
src/views/productionManagement/workOrderManagement/components/filesDia.vue | 67 ++++++
src/api/productionManagement/productionProductMain.js | 1
src/views/productionManagement/productionCosting/index.vue | 14 +
src/api/reportAnalysis/qualityReport.js | 8
src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue | 96 ++++++++-
src/views/qualityManagement/nonconformingManagement/index.vue | 157 ++++++++++++++-
9 files changed, 529 insertions(+), 71 deletions(-)
diff --git a/src/api/productionManagement/productionProductMain.js b/src/api/productionManagement/productionProductMain.js
index 0493f8b..902d3e9 100644
--- a/src/api/productionManagement/productionProductMain.js
+++ b/src/api/productionManagement/productionProductMain.js
@@ -9,3 +9,4 @@
params: query,
});
}
+
diff --git a/src/api/reportAnalysis/qualityReport.js b/src/api/reportAnalysis/qualityReport.js
index 66a7540..add1e3a 100644
--- a/src/api/reportAnalysis/qualityReport.js
+++ b/src/api/reportAnalysis/qualityReport.js
@@ -50,3 +50,11 @@
params: { modelType }
})
}
+
+// 鑾峰彇鎸変汉鍛�/鐝粍缁熻璐ㄩ噺鏁版嵁
+export function getPersonGroupRateStatistics() {
+ return request({
+ url: '/qualityReport/getPersonGroupRateStatistics',
+ method: 'get'
+ })
+}
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index be247ce..53dfcf7 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -226,6 +226,11 @@
minWidth: 100,
},
{
+ label: "琛ヤ慨鐞嗗伐鏃�",
+ prop: "repairWorkHour",
+ minWidth: 120,
+ },
+ {
label: "鐢熶骇鏁伴噺",
prop: "quantity",
minWidth: 100,
@@ -236,8 +241,13 @@
minWidth: 100,
},
{
- label: "宸ヨ祫",
- prop: "wages",
+ label: "鍘熷伐璧�",
+ prop: "originalWages",
+ minWidth: 100,
+ },
+ {
+ label: "淇宸ヨ祫",
+ prop: "adjustAmount",
minWidth: 100,
},
{
diff --git a/src/views/productionManagement/workOrder/components/filesDia.vue b/src/views/productionManagement/workOrder/components/filesDia.vue
index 1336861..80e9fe6 100644
--- a/src/views/productionManagement/workOrder/components/filesDia.vue
+++ b/src/views/productionManagement/workOrder/components/filesDia.vue
@@ -5,7 +5,7 @@
<el-upload
v-model:file-list="fileList"
class="upload-demo"
- :action="uploadUrl"
+ :http-request="uploadFile"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:before-upload="beforeUpload"
@@ -46,6 +46,7 @@
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
+import axios from "axios";
import { getToken } from "@/utils/auth.js";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import filePreview from "@/components/filePreview/index.vue";
@@ -104,6 +105,7 @@
Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
+const currentWorkOrderNo = ref("");
const beforeUpload = file => {
const isImage = file?.type?.startsWith("image/");
@@ -116,6 +118,7 @@
const openDialog = row => {
dialogVisible.value = true;
currentWorkOrderId.value = row.id;
+ currentWorkOrderNo.value = row.workOrderNo || row.work_order_no || "";
page.current = 1;
getList();
};
@@ -171,6 +174,66 @@
proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
}
+const createWatermarkImage = async file => {
+ const sourceImage = await loadImage(URL.createObjectURL(file));
+ const canvas = document.createElement("canvas");
+ const width = sourceImage.naturalWidth || sourceImage.width;
+ const height = sourceImage.naturalHeight || sourceImage.height;
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext("2d");
+ ctx.drawImage(sourceImage, 0, 0, width, height);
+
+ const watermarkText = [
+ "澶╂触甯傜幃鑻撲箰鍣ㄦ湁闄愬叕鍙�",
+ `${new Date().toLocaleString()}`,
+ ];
+
+ ctx.save();
+ ctx.globalAlpha = 0.28;
+ ctx.fillStyle = "#ffffff";
+ ctx.font = `bold ${Math.max(18, Math.floor(width / 28))}px Arial`;
+ ctx.textAlign = "right";
+ ctx.textBaseline = "bottom";
+ const padding = Math.max(24, Math.floor(width / 40));
+ const lineHeight = Math.max(24, Math.floor(width / 20));
+ const x = width - padding;
+ const y = height - padding;
+ watermarkText.reverse().forEach((text, index) => {
+ ctx.fillText(text, x, y - index * lineHeight);
+ });
+ ctx.restore();
+
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, file.type || "image/jpeg", 0.92));
+ return new File([blob], file.name, { type: file.type || "image/jpeg" });
+};
+
+const loadImage = src =>
+ new Promise((resolve, reject) => {
+ const image = new Image();
+ image.crossOrigin = "anonymous";
+ image.onload = () => resolve(image);
+ image.onerror = reject;
+ image.src = src;
+ });
+
+const uploadFile = async options => {
+ try {
+ const watermarkedFile = await createWatermarkImage(options.file);
+ const formData = new FormData();
+ formData.append("file", watermarkedFile);
+ const response = await axios.post(uploadUrl.value, formData, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ Authorization: "Bearer " + getToken(),
+ },
+ });
+ options.onSuccess(response.data);
+ } catch (error) {
+ options.onError(error);
+ }
+};
+
const handleDelete = () => {
if (selectedRows.value.length === 0) {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
@@ -199,4 +262,3 @@
</script>
<style scoped></style>
-
diff --git a/src/views/productionManagement/workOrderManagement/components/filesDia.vue b/src/views/productionManagement/workOrderManagement/components/filesDia.vue
index 84d8bd4..80e9fe6 100644
--- a/src/views/productionManagement/workOrderManagement/components/filesDia.vue
+++ b/src/views/productionManagement/workOrderManagement/components/filesDia.vue
@@ -5,7 +5,7 @@
<el-upload
v-model:file-list="fileList"
class="upload-demo"
- :action="uploadUrl"
+ :http-request="uploadFile"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:before-upload="beforeUpload"
@@ -46,6 +46,7 @@
<script setup>
import { ref, reactive, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
+import axios from "axios";
import { getToken } from "@/utils/auth.js";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import filePreview from "@/components/filePreview/index.vue";
@@ -104,6 +105,7 @@
Authorization: "Bearer " + getToken(),
});
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload");
+const currentWorkOrderNo = ref("");
const beforeUpload = file => {
const isImage = file?.type?.startsWith("image/");
@@ -116,6 +118,7 @@
const openDialog = row => {
dialogVisible.value = true;
currentWorkOrderId.value = row.id;
+ currentWorkOrderNo.value = row.workOrderNo || row.work_order_no || "";
page.current = 1;
getList();
};
@@ -171,6 +174,66 @@
proxy.$modal.msgError("鏂囦欢涓婁紶澶辫触");
}
+const createWatermarkImage = async file => {
+ const sourceImage = await loadImage(URL.createObjectURL(file));
+ const canvas = document.createElement("canvas");
+ const width = sourceImage.naturalWidth || sourceImage.width;
+ const height = sourceImage.naturalHeight || sourceImage.height;
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext("2d");
+ ctx.drawImage(sourceImage, 0, 0, width, height);
+
+ const watermarkText = [
+ "澶╂触甯傜幃鑻撲箰鍣ㄦ湁闄愬叕鍙�",
+ `${new Date().toLocaleString()}`,
+ ];
+
+ ctx.save();
+ ctx.globalAlpha = 0.28;
+ ctx.fillStyle = "#ffffff";
+ ctx.font = `bold ${Math.max(18, Math.floor(width / 28))}px Arial`;
+ ctx.textAlign = "right";
+ ctx.textBaseline = "bottom";
+ const padding = Math.max(24, Math.floor(width / 40));
+ const lineHeight = Math.max(24, Math.floor(width / 20));
+ const x = width - padding;
+ const y = height - padding;
+ watermarkText.reverse().forEach((text, index) => {
+ ctx.fillText(text, x, y - index * lineHeight);
+ });
+ ctx.restore();
+
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, file.type || "image/jpeg", 0.92));
+ return new File([blob], file.name, { type: file.type || "image/jpeg" });
+};
+
+const loadImage = src =>
+ new Promise((resolve, reject) => {
+ const image = new Image();
+ image.crossOrigin = "anonymous";
+ image.onload = () => resolve(image);
+ image.onerror = reject;
+ image.src = src;
+ });
+
+const uploadFile = async options => {
+ try {
+ const watermarkedFile = await createWatermarkImage(options.file);
+ const formData = new FormData();
+ formData.append("file", watermarkedFile);
+ const response = await axios.post(uploadUrl.value, formData, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ Authorization: "Bearer " + getToken(),
+ },
+ });
+ options.onSuccess(response.data);
+ } catch (error) {
+ options.onError(error);
+ }
+};
+
const handleDelete = () => {
if (selectedRows.value.length === 0) {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
@@ -198,4 +261,4 @@
});
</script>
-<style scoped></style>
\ No newline at end of file
+<style scoped></style>
diff --git a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
index 9263626..bf380bb 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -122,10 +122,8 @@
<el-select v-model="form.dealResult"
placeholder="璇烽�夋嫨"
clearable>
- <el-option :label="item.label"
- :value="item.value"
- v-for="item in rejection_handling"
- :key="item.value" />
+ <el-option label="鎶ュ伐" value="鎶ュ伐" />
+ <el-option label="鎶ュ簾" value="鎶ュ簾" />
</el-select>
</el-form-item>
</el-col>
@@ -137,10 +135,12 @@
<el-select v-model="form.dealType"
placeholder="璇烽�夋嫨"
clearable
+ :disabled="!form.dealResult"
style="width: 100%">
- <el-option label="杞诲井杩斿伐" :value="0" />
- <el-option label="涓ラ噸杩斿伐" :value="1" />
- <el-option label="鎶ュ簾" :value="2" />
+ <el-option v-for="item in availableDealTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value" />
</el-select>
</el-form-item>
</el-col>
@@ -152,20 +152,6 @@
: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>
@@ -239,7 +225,6 @@
const dialogFormVisible = ref(false);
const operationType = ref("");
- const { rejection_handling } = proxy.useDict("rejection_handling");
const data = reactive({
form: {
checkTime: "",
@@ -255,7 +240,7 @@
inspectType: "",
defectivePhenomena: "",
dealResult: "",
- dealType: 0,
+ dealType: null,
repairWorkHour: 0,
regenerateNewOrder: 0,
dealName: "",
@@ -280,14 +265,43 @@
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())}`;
+ return `澶╂触甯傜幃鑻撲箰鍣ㄦ湁闄愬叕鍙竆n${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
});
- watch(() => form.value.dealType, (val) => {
+ const availableDealTypeOptions = computed(() => {
+ if (form.value.dealResult === "鎶ュ簾") {
+ return [{ label: "鎶ュ簾", value: 2 }];
+ }
+ if (form.value.dealResult === "鎶ュ伐") {
+ return [
+ { label: "杞诲井杩斿伐", value: 0 },
+ { label: "涓ラ噸杩斿伐", value: 1 },
+ ];
+ }
+ return [];
+ });
+
+ watch(() => form.value.dealResult, val => {
+ if (val === "鎶ュ簾") {
+ form.value.dealType = 2;
+ form.value.repairWorkHour = 0;
+ return;
+ }
+ if (val === "鎶ュ伐" && form.value.dealType === 2) {
+ form.value.dealType = null;
+ }
+ });
+
+ watch(() => form.value.dealType, val => {
if (val !== 0) {
form.value.repairWorkHour = 0;
+ }
+ if (val === 2) {
+ form.value.dealResult = "鎶ュ簾";
+ }
+ if ((val === 0 || val === 1) && !form.value.dealResult) {
+ form.value.dealResult = "鎶ュ伐";
}
});
@@ -308,7 +322,7 @@
dealName: "",
dealTime: "",
dealResult: "",
- dealType: 0,
+ dealType: null,
repairWorkHour: 0,
regenerateNewOrder: 0,
photoUrls: [],
@@ -327,19 +341,27 @@
}
getProductOptions();
if (operationType.value === "edit") {
- getQualityUnqualifiedInfo(row.id).then(res => {
- const { inspectState, ...rest } = res.data || {};
- form.value = {
- ...rest,
- photoUrls: rest.photoUrls
+ getQualityUnqualifiedInfo(row.id).then(res => {
+ const { inspectState, ...rest } = res.data || {};
+ form.value = {
+ ...rest,
+ photoUrls: rest.photoUrls
? String(rest.photoUrls).split(",").filter(Boolean).map((url, index) => ({
name: `image-${index + 1}`,
url,
- }))
+ }))
: [],
- };
- });
- }
+ };
+ if (Number(form.value.dealType) === 0) {
+ form.value.regenerateNewOrder = 0;
+ } else if (Number(form.value.dealType) === 1 || Number(form.value.dealType) === 2) {
+ form.value.regenerateNewOrder = 1;
+ }
+ if (!form.value.dealResult) {
+ form.value.dealResult = Number(form.value.dealType) === 2 ? "鎶ュ簾" : "鎶ュ伐";
+ }
+ });
+ }
};
const getProductOptions = () => {
productTreeList().then(res => {
@@ -392,6 +414,17 @@
if (valid) {
// 鐘舵�佸瓧娈典笉鍦ㄨ〃鍗曞~鍐欙紝涔熶笉浼犵粰鍚庣
const { inspectState, ...payload } = form.value || {};
+ if (!payload.dealResult) {
+ proxy.$modal.msgError("璇烽�夋嫨澶勭悊缁撴灉");
+ return;
+ }
+ if (payload.dealResult === "鎶ュ伐" && (payload.dealType === null || payload.dealType === undefined || payload.dealType === 2)) {
+ proxy.$modal.msgError("鎶ュ伐鏃惰閫夋嫨杞诲井杩斿伐鎴栦弗閲嶈繑宸�");
+ return;
+ }
+ if (payload.dealResult === "鎶ュ簾") {
+ payload.dealType = 2;
+ }
if (payload.dealType === 0 && (!payload.repairWorkHour || payload.repairWorkHour <= 0)) {
proxy.$modal.msgError("杞诲井杩斿伐蹇呴』濉啓澶т簬0鐨勪慨鐞嗗伐鏃�");
return;
diff --git a/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue b/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
index 8f4492a..d767cb2 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue
@@ -82,8 +82,26 @@
<el-col :span="12">
<el-form-item label="澶勭悊缁撴灉锛�" prop="dealResult">
<el-select v-model="form.dealResult" placeholder="璇烽�夋嫨" clearable>
- <el-option :label="item.label" :value="item.value" v-for="item in filteredRejectionHandling" :key="item.value" />
+ <el-option :label="item.label" :value="item.value" v-for="item in autoDealResultOptions" :key="item.value" />
</el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="涓嶅悎鏍肩被鍨嬶細" prop="dealType">
+ <el-input :model-value="dealTypeLabel(form.dealType)" disabled />
+ </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="Number(form.dealType) !== 0"
+ />
</el-form-item>
</el-col>
</el-row>
@@ -121,7 +139,7 @@
</template>
<script setup>
-import {ref, reactive, toRefs, computed} from "vue";
+import { ref, reactive, toRefs, computed, getCurrentInstance } from "vue";
import {productTreeList} from "@/api/basicData/product.js";
import {
getQualityUnqualifiedInfo,
@@ -131,7 +149,6 @@
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
-const { rejection_handling } = proxy.useDict("rejection_handling")
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
@@ -149,6 +166,9 @@
inspectType: '',
defectivePhenomena: '',
dealResult: '',
+ dealType: null,
+ repairWorkHour: 0,
+ regenerateNewOrder: 0,
dealName: '',
dealTime: '',
method: undefined
@@ -165,6 +185,7 @@
checkResult: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
defectivePhenomena: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
dealResult: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ repairWorkHour: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }],
dealName: [{ required: true, message: "璇烽�夋嫨澶勭悊浜�", trigger: "change" }],
dealTime: [{ required: true, message: "璇疯緭鍏�", trigger: "change" }],
},
@@ -173,13 +194,29 @@
const productOptions = ref([]);
const userList = ref([]); // 澶勭悊浜轰笅鎷夊垪琛�
-const filteredRejectionHandling = computed(() => {
- const data = rejection_handling.value;
- if (form.value.method) {
- return data.filter(item => item && item.label && item.label !== '杩斿伐' && item.label !== '杩斾慨')
+const autoDealResultOptions = computed(() => {
+ if (Number(form.value.dealType) === 2) {
+ return [{ label: "鎶ュ簾", value: "鎶ュ簾" }];
}
- return data
+ if (Number(form.value.dealType) === 0 || Number(form.value.dealType) === 1) {
+ return [{ label: "鎶ュ伐", value: "鎶ュ伐" }];
+ }
+ return [];
})
+
+const dealTypeLabel = (val) => {
+ const type = Number(val);
+ if (type === 0) {
+ return "杞诲井杩斿伐";
+ }
+ if (type === 1) {
+ return "涓ラ噸杩斿伐";
+ }
+ if (type === 2) {
+ return "鎶ュ簾";
+ }
+ return "-";
+};
// 鎵撳紑寮规
@@ -194,13 +231,39 @@
userList.value = [];
}
dialogFormVisible.value = true;
- form.value = {};
+ form.value = {
+ checkTime: "",
+ process: "",
+ checkName: "",
+ productName: "",
+ productId: "",
+ model: "",
+ unit: "",
+ quantity: "",
+ checkCompany: "",
+ checkResult: "",
+ inspectType: '',
+ defectivePhenomena: '',
+ dealResult: '',
+ dealType: null,
+ repairWorkHour: 0,
+ regenerateNewOrder: 0,
+ dealName: '',
+ dealTime: '',
+ method: undefined
+ };
getProductOptions();
if (operationType.value === 'edit') {
getQualityUnqualifiedInfo(row.id).then(res => {
const { inspectState, ...rest } = (res.data || {})
- // 鏈夋暟鎹氨鏄剧ず榛樿鍊硷紝娌℃湁灏变笉鏄剧ず
- form.value = { ...rest }
+ const dealType = rest.dealType === '' || rest.dealType === undefined || rest.dealType === null
+ ? null
+ : Number(rest.dealType);
+ // 鐢ㄥ悗绔繑鍥炲�煎洖濉紝閬垮厤鎶婁笉鍚堟牸绫诲瀷灞曠ず鎴愮┖
+ form.value = { ...form.value, ...rest, dealType }
+ if (!form.value.dealResult) {
+ form.value.dealResult = dealType === 2 ? "鎶ュ簾" : "鎶ュ伐";
+ }
})
}
}
@@ -246,6 +309,15 @@
if (valid) {
// 鐘舵�佸瓧娈典笉鍦ㄨ〃鍗曞~鍐欙紝涔熶笉浼犵粰鍚庣锛涘鐞嗙粺涓�璧� /deal 鎺ュ彛
const { inspectState, ...payload } = (form.value || {})
+ if (Number(payload.dealType) === 2) {
+ payload.dealResult = "鎶ュ簾";
+ } else {
+ payload.dealResult = "鎶ュ伐";
+ }
+ if (payload.dealType === 0 && (!payload.repairWorkHour || payload.repairWorkHour <= 0)) {
+ proxy.$modal.msgError("杞诲井杩斿伐蹇呴』濉啓澶т簬0鐨勪慨鐞嗗伐鏃�");
+ return;
+ }
qualityUnqualifiedDeal(payload).then(() => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeDia();
@@ -266,4 +338,4 @@
<style scoped>
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue
index ea9a2cb..7cc984e 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -55,16 +55,51 @@
</div>
<FormDia ref="formDia" @close="handleQuery"></FormDia>
<InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
+ <el-dialog v-model="uploadDialogVisible" title="鎷嶇収涓婁紶闄勪欢" width="680px">
+ <el-form label-width="120px">
+ <el-form-item label="涓嶅悎鏍煎浘鐗囷細">
+ <AttachmentUploadImage
+ v-model:fileList="uploadPhotoList"
+ :limit="6"
+ button-text="鎷嶇収/涓婁紶鍥剧墖"
+ :watermark-text="watermarkText"
+ />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button @click="closeUploadDialog">鍙栨秷</el-button>
+ <el-button type="primary" :loading="uploadSaving" @click="submitUploadDialog">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="photoPreviewVisible" :title="photoPreviewTitle" width="900px">
+ <div v-if="photoPreviewList.length" class="photo-preview-grid">
+ <el-image
+ v-for="(url, index) in photoPreviewList"
+ :key="`${url}-${index}`"
+ :src="url"
+ fit="cover"
+ class="photo-preview-item"
+ :preview-src-list="photoPreviewList"
+ preview-teleported
+ />
+ </div>
+ <el-empty v-else description="鏆傛棤闄勪欢鐓х墖" />
+ <template #footer>
+ <el-button @click="photoPreviewVisible = false">鍏抽棴</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
-import {onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance} from "vue";
+import { computed, onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance } from "vue";
import FormDia from "@/views/qualityManagement/nonconformingManagement/components/formDia.vue";
-import {ElMessageBox} from "element-plus";
-import {qualityUnqualifiedDel, qualityUnqualifiedListPage} from "@/api/qualityManagement/nonconformingManagement.js";
+import AttachmentUploadImage from "@/components/AttachmentUpload/image/index.vue";
+import { ElMessageBox } from "element-plus";
+import { getQualityUnqualifiedInfo, qualityUnqualifiedDel, qualityUnqualifiedListPage, qualityUnqualifiedUpdate } from "@/api/qualityManagement/nonconformingManagement.js";
import InspectionFormDia from "@/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue";
+import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
const data = reactive({
@@ -199,19 +234,26 @@
label: "鎿嶄綔",
align: "center",
fixed: "right",
- width: 100,
- operation: [
- {
- name: "澶勭悊",
- type: "text",
- clickFun: (row) => {
- openInspectionForm("edit", row);
+ width: 240,
+ operation: [
+ {
+ name: "澶勭悊",
+ type: "text",
+ clickFun: (row) => {
+ openInspectionForm("edit", row);
+ },
+ disabled: (row) => row.inspectState === 1,
},
- disabled: (row) => row.inspectState === 1,
- },
- ],
- },
-]);
+ {
+ name: "鎷嶇収涓婁紶",
+ type: "text",
+ clickFun: (row) => {
+ openUploadDialog(row);
+ },
+ },
+ ],
+ },
+ ]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
@@ -222,7 +264,21 @@
});
const formDia = ref()
const inspectionFormDia = ref()
+const photoPreviewVisible = ref(false)
+const photoPreviewTitle = ref("")
+const photoPreviewList = ref([])
+const uploadDialogVisible = ref(false)
+const uploadSaving = ref(false)
+const uploadPhotoList = ref([])
+const currentUploadRow = ref(null)
const { proxy } = getCurrentInstance()
+const userStore = useUserStore()
+const watermarkText = computed(() => {
+ const now = new Date();
+ const pad = (n) => String(n).padStart(2, "0");
+ return `澶╂触甯傜幃鑻撲箰鍣ㄦ湁闄愬叕鍙�
+${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
+})
const changeDaterange = (value) => {
searchForm.value.entryDateStart = undefined;
@@ -281,6 +337,62 @@
inspectionFormDia.value?.openDialog(type, row)
})
};
+const openPhotoPreview = row => {
+ const urls = String(row.photoUrls || "")
+ .split(",")
+ .map(item => item.trim())
+ .filter(Boolean);
+ photoPreviewList.value = urls;
+ photoPreviewTitle.value = `闄勪欢鐓х墖${row.productName ? ` - ${row.productName}` : ""}`;
+ photoPreviewVisible.value = true;
+};
+const openUploadDialog = async (row) => {
+ if (!row?.id) {
+ proxy.$modal.msgWarning("鏈壘鍒板綋鍓嶆暟鎹�");
+ return;
+ }
+ try {
+ const res = await getQualityUnqualifiedInfo(row.id);
+ const detail = res.data || {};
+ currentUploadRow.value = detail;
+ uploadPhotoList.value = detail.photoUrls
+ ? String(detail.photoUrls).split(",").filter(Boolean).map((url, index) => ({
+ name: `image-${index + 1}`,
+ url,
+ }))
+ : [];
+ uploadDialogVisible.value = true;
+ } catch (e) {
+ proxy.$modal.msgError("鍔犺浇闄勪欢淇℃伅澶辫触");
+ }
+};
+const closeUploadDialog = () => {
+ uploadDialogVisible.value = false;
+ uploadSaving.value = false;
+ uploadPhotoList.value = [];
+ currentUploadRow.value = null;
+};
+const submitUploadDialog = async () => {
+ if (!currentUploadRow.value?.id) {
+ proxy.$modal.msgWarning("鏈壘鍒板綋鍓嶆暟鎹�");
+ return;
+ }
+ const payload = {
+ ...currentUploadRow.value,
+ photoUrls: Array.isArray(uploadPhotoList.value)
+ ? uploadPhotoList.value.map(item => item.url || item.previewURL || item.previewUrl || item).filter(Boolean).join(",")
+ : "",
+ };
+ uploadSaving.value = true;
+ try {
+ await qualityUnqualifiedUpdate(payload);
+ proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+ closeUploadDialog();
+ getList();
+ } catch (e) {
+ uploadSaving.value = false;
+ }
+};
// 鍒犻櫎
const handleDelete = () => {
@@ -325,4 +437,17 @@
});
</script>
-<style scoped></style>
+<style scoped>
+.photo-preview-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.photo-preview-item {
+ width: 100%;
+ height: 180px;
+ border-radius: 8px;
+ overflow: hidden;
+}
+</style>
diff --git a/src/views/reportAnalysis/reportManagement/index.vue b/src/views/reportAnalysis/reportManagement/index.vue
index cd8ddd5..5704f43 100644
--- a/src/views/reportAnalysis/reportManagement/index.vue
+++ b/src/views/reportAnalysis/reportManagement/index.vue
@@ -273,6 +273,54 @@
</el-col>
</el-row>
</div>
+ <div class=charts-container>
+ <el-row :gutter=20>
+ <el-col :span=24>
+ <el-card class=chart-card shadow=hover>
+ <template #header>
+ <div class=card-header>
+ <div class=chart-title-line></div>
+ <span>鎸変汉鍛�/鐝粍缁熻璐ㄩ噺鏁版嵁</span>
+ </div>
+ </template>
+ <el-row :gutter=20>
+ <el-col :span=12>
+ <div class=quality-rate-block>
+ <div class=quality-rate-block-title>鎸変汉鍛樼粺璁�</div>
+ <el-table :data=personGroupRateData.personList size=small border class=quality-rate-table>
+ <el-table-column prop=dimensionName label=浜哄憳 min-width=120 />
+ <el-table-column prop=totalCount label=鎬绘暟 min-width=80 />
+ <el-table-column prop=qualifiedCount label=鍚堟牸鏁� min-width=90 />
+ <el-table-column prop=unqualifiedCount label=涓嶅悎鏍兼暟 min-width=100 />
+ <el-table-column prop=reworkCount label=杩斿伐鏁� min-width=90 />
+ <el-table-column prop=scrapCount label=鎶ュ簾鏁� min-width=90 />
+ <el-table-column label=鍚堟牸鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.passRate) }}</template></el-table-column>
+ <el-table-column label=杩斿伐鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.reworkRate) }}</template></el-table-column>
+ <el-table-column label=鎶ュ簾鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.scrapRate) }}</template></el-table-column>
+ </el-table>
+ </div>
+ </el-col>
+ <el-col :span=12>
+ <div class=quality-rate-block>
+ <div class=quality-rate-block-title>鎸夌彮缁勭粺璁�</div>
+ <el-table :data=personGroupRateData.groupList size=small border class=quality-rate-table>
+ <el-table-column prop=dimensionName label=鐝粍 min-width=120 />
+ <el-table-column prop=totalCount label=鎬绘暟 min-width=80 />
+ <el-table-column prop=qualifiedCount label=鍚堟牸鏁� min-width=90 />
+ <el-table-column prop=unqualifiedCount label=涓嶅悎鏍兼暟 min-width=100 />
+ <el-table-column prop=reworkCount label=杩斿伐鏁� min-width=90 />
+ <el-table-column prop=scrapCount label=鎶ュ簾鏁� min-width=90 />
+ <el-table-column label=鍚堟牸鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.passRate) }}</template></el-table-column>
+ <el-table-column label=杩斿伐鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.reworkRate) }}</template></el-table-column>
+ <el-table-column label=鎶ュ簾鐜� min-width=90><template #default="scope">{{ formatRateValue(scope.row.scrapRate) }}</template></el-table-column>
+ </el-table>
+ </div>
+ </el-col>
+ </el-row>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
</div>
</template>
@@ -280,7 +328,7 @@
import { ref, reactive, onMounted, nextTick } from "vue";
import { ElMessage } from "element-plus";
import * as echarts from "echarts";
-import { getInspectStatistics, getPassRateStatistics, getMonthlyPassRateStatistics, getYearlyPassRateStatistics, getMonthlyCompletionDetails, getTopParameters } from "@/api/reportAnalysis/qualityReport";
+import { getInspectStatistics, getPassRateStatistics, getMonthlyPassRateStatistics, getYearlyPassRateStatistics, getMonthlyCompletionDetails, getTopParameters, getPersonGroupRateStatistics } from "@/api/reportAnalysis/qualityReport";
// 鍝嶅簲寮忔暟鎹�
const filterForm = reactive({
@@ -294,6 +342,7 @@
const yearlyPassRateData = ref([]);
const monthlyCompletionDetailsData = ref([]);
const topParametersData = ref({ totalCount: 0, list: [] });
+const personGroupRateData = ref({ personList: [], groupList: [] });
const activeTab = ref("raw");
const getParameterColor = (index) => {
@@ -321,6 +370,24 @@
}
return field === 'completionRate' || field === 'passRate' ? '0%' : 0;
};
+const formatRateValue = value => {
+ return value || value === 0 ? Number(value).toFixed(2) + '%' : '0.00%';
+};
+
+const fetchPersonGroupRateStatisticsData = async () => {
+ try {
+ const res = await getPersonGroupRateStatistics();
+ if (res.code === 200 && res.data) {
+ personGroupRateData.value = {
+ personList: res.data.personList || [],
+ groupList: res.data.groupList || []
+ };
+ }
+ } catch (error) {
+ console.error("Failed to fetch person/group rate statistics:", error);
+ }
+};
+
const fetchInspectStatisticsData = async () => {
try {
@@ -426,6 +493,7 @@
watch(activeTab, () => {
fetchTopParametersData();
+ fetchPersonGroupRateStatisticsData();
});
@@ -884,6 +952,7 @@
fetchYearlyPassRateData();
fetchMonthlyCompletionDetailsData();
fetchTopParametersData();
+ fetchPersonGroupRateStatisticsData();
nextTick(() => {
initSampleChart();
initEquipmentChart();
@@ -1037,6 +1106,21 @@
border-radius: 3px;
}
+.quality-rate-block {
+ min-height: 320px;
+}
+
+.quality-rate-block-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 12px;
+}
+
+.quality-rate-table {
+ width: 100%;
+}
+
.chart-container {
height: 250px;
width: 100%;
--
Gitblit v1.9.3