From e40e1cf5d4aa99412ca3a87771b5d5a8ea5a105d Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 29 四月 2026 15:39:53 +0800
Subject: [PATCH] 天津军泰伟业 1.员工台账添加导入功能
---
src/views/basicData/product/index.vue | 476 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 445 insertions(+), 31 deletions(-)
diff --git a/src/views/basicData/product/index.vue b/src/views/basicData/product/index.vue
index 62550ae..857ec8b 100644
--- a/src/views/basicData/product/index.vue
+++ b/src/views/basicData/product/index.vue
@@ -64,7 +64,46 @@
@selection-change="handleSelectionChange"
:tableLoading="tableLoading"
@pagination="pagination"
- ></PIMTable>
+ >
+ <template #drawingFiles="{ row }">
+ <div v-if="row.salesLedgerFiles && row.salesLedgerFiles.length" class="drawing-thumbs-list">
+ <div
+ v-for="(file, index) in row.salesLedgerFiles.slice(0, 3)"
+ :key="index"
+ class="drawing-thumb-item"
+ @click="handlePreviewFile(file)"
+ >
+ <img
+ v-if="isImageFile(file)"
+ :src="getDrawingFileUrl(file)"
+ class="drawing-thumb-img"
+ />
+ <div v-else class="drawing-thumb-placeholder">
+ {{ getDrawingFileExtension(file).toUpperCase() }}
+ </div>
+ </div>
+ <div v-if="row.salesLedgerFiles.length > 3" class="drawing-thumb-more">
+ +{{ row.salesLedgerFiles.length - 3 }}
+ </div>
+ </div>
+ <div v-else-if="row.drawingFile" class="drawing-thumbs-list">
+ <div
+ class="drawing-thumb-item"
+ @click="handlePreviewDrawing(row.drawingFile)"
+ >
+ <img
+ v-if="isDrawingImageFile(row.drawingFile)"
+ :src="row.drawingFile"
+ class="drawing-thumb-img"
+ />
+ <div v-else class="drawing-thumb-placeholder">
+ {{ getFileExtensionFromUrl(row.drawingFile).toUpperCase() }}
+ </div>
+ </div>
+ </div>
+ <span v-else>-</span>
+ </template>
+ </PIMTable>
</div>
<FormDialog
@@ -144,10 +183,11 @@
:data="upload.data"
:on-success="handleDrawingUploadSuccess"
:on-remove="handleDrawingRemove"
+ :on-preview="handleDrawingPreview"
:before-upload="handleDrawingBeforeUpload"
- :limit="1"
+ :limit="5"
accept=".pdf,.jpg,.jpeg,.png,.dwg"
- list-type="picture-card"
+ list-type="text"
>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<template #tip>
@@ -156,6 +196,39 @@
</div>
</template>
</el-upload>
+ <div v-if="drawingFileList.length" class="drawing-preview-list">
+ <div
+ v-for="file in drawingFileList"
+ :key="file.uid || file.id || file.name"
+ class="drawing-preview-card"
+ @click="handleDrawingPreview(file)"
+ >
+ <img
+ v-if="isImageFile(file)"
+ :src="getDrawingFileUrl(file)"
+ :alt="file.name"
+ class="drawing-preview-image"
+ />
+ <div
+ v-else
+ class="drawing-preview-placeholder"
+ :class="`is-${getDrawingFileExtension(file)}`"
+ >
+ {{ getDrawingFileExtension(file).toUpperCase() }}
+ </div>
+ <div class="drawing-preview-name">{{ file.name }}</div>
+ </div>
+ </div>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ v-model="modelForm.remark"
+ placeholder="璇疯緭鍏ュ娉�"
+ clearable
+ type="textarea"
+ :rows="3"
+ show-word-limit
+ />
</el-form-item>
</el-form>
</FormDialog>
@@ -172,6 +245,7 @@
:limit="1"
accept=".xlsx,.xls"
:action="importUpload.url"
+ :http-request="importUpload.httpRequest"
:headers="importUpload.headers"
:before-upload="importUpload.beforeUpload"
:on-success="importUpload.onSuccess"
@@ -193,15 +267,18 @@
</template>
</el-upload>
</FormDialog>
+ <filePreview ref="filePreviewRef" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
+import axios from "axios";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth.js";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import filePreview from "@/components/filePreview/index.vue";
import {
addOrEditProductModel,
delProduct,
@@ -209,10 +286,12 @@
downloadTemplate,
} from "@/api/basicData/product.js";
import { listPage as getProcessRouteList } from "@/api/productionManagement/processRoute.js";
+import { delLedgerFile } from "@/api/salesManagement/salesLedger.js";
import ImportExcel from "./ImportExcel/index.vue";
const { proxy } = getCurrentInstance();
const importUploadRef = ref(null);
+const filePreviewRef = ref(null);
const modelDia = ref(false);
const importDia = ref(false);
@@ -259,6 +338,16 @@
minWidth: 100,
},
{
+ label: "宸ヨ壓璺嚎",
+ prop: "routeName",
+ minWidth: 100,
+ },
+ {
+ label: "瀛愰」鏁伴噺",
+ prop: "subItemCount",
+ minWidth: 100,
+ },
+ {
label: "浜у搧灞炴��",
prop: "productType",
width: 100,
@@ -269,6 +358,29 @@
return typeMap[String(v)] || "info";
},
},
+ {
+ label: "澶囨敞",
+ prop: "remark",
+ minWidth: 150,
+ showOverflowTooltip: true,
+ },
+ {
+ label: "鍥剧焊",
+ prop: "salesLedgerFiles",
+ minWidth: 200,
+ dataType: "slot",
+ slot: "drawingFiles",
+ },
+ {
+ label: "鍒涘缓鏃堕棿",
+ prop: "createTime",
+ width: 120,
+ },
+ {
+ label: "淇敼鏃堕棿",
+ prop: "updateTime",
+ width: 120,
+ },
{
dataType: "action",
label: "鎿嶄綔",
@@ -288,6 +400,7 @@
const data = reactive({
modelForm: {
+ productId: null,
productName: "",
model: "",
unit: "",
@@ -296,6 +409,7 @@
drawingFile: "",
tempFileIds: [],
salesLedgerFiles: [],
+ remark: "",
},
modelRules: {
productName: [
@@ -309,12 +423,92 @@
});
const { modelForm, modelRules } = toRefs(data);
+const createDefaultModelForm = () => ({
+ productId: null,
+ productName: "",
+ model: "",
+ unit: "",
+ productType: null,
+ routeId: null,
+ drawingFile: "",
+ remark: "",
+ tempFileIds: [],
+ salesLedgerFiles: [],
+});
+
+const downloadImportErrorFile = (blob, filename = "import-error.xlsx") => {
+ const downloadElement = document.createElement("a");
+ const href = window.URL.createObjectURL(blob);
+ downloadElement.href = href;
+ downloadElement.download = filename;
+ document.body.appendChild(downloadElement);
+ downloadElement.click();
+ document.body.removeChild(downloadElement);
+ window.URL.revokeObjectURL(href);
+};
+
+const tryParseJsonBlob = async (blob) => {
+ try {
+ const text = await blob.text();
+ if (!text || !text.trim()) {
+ return null;
+ }
+ return JSON.parse(text);
+ } catch (_) {
+ return null;
+ }
+};
+
const importUpload = reactive({
title: "浜у搧瀵煎叆",
open: false,
url: import.meta.env.VITE_APP_BASE_API + "/basic/product/import",
headers: { Authorization: "Bearer " + getToken() },
isUploading: false,
+ httpRequest: async (options) => {
+ const { file, onProgress, onSuccess, onError } = options;
+ importUpload.isUploading = true;
+ const formData = new FormData();
+ formData.append("file", file);
+ try {
+ const response = await axios({
+ url: importUpload.url,
+ method: "post",
+ headers: {
+ ...importUpload.headers,
+ "Content-Type": "multipart/form-data",
+ },
+ data: formData,
+ responseType: "blob",
+ onUploadProgress: (progressEvent) => {
+ const total = progressEvent.total || 1;
+ const percent = Math.round((progressEvent.loaded * 100) / total);
+ onProgress?.({ percent }, file);
+ },
+ });
+ importUpload.isUploading = false;
+ const blob = response.data;
+ // Contract: success => empty response body; failure => binary error file.
+ if (!blob || blob.size === 0) {
+ onSuccess?.({ code: 200, msg: "import success" }, file);
+ return;
+ }
+ const json = await tryParseJsonBlob(blob);
+ if (json) {
+ if (String(json.code) === "200" || json.success === true) {
+ onSuccess?.(json, file);
+ } else {
+ onError?.(new Error(json.msg || json.message || "import failed"), file);
+ }
+ return;
+ }
+ downloadImportErrorFile(blob);
+ onError?.(new Error("import failed, error file downloaded"), file);
+ } catch (error) {
+ importUpload.isUploading = false;
+ onError?.(error, file);
+ }
+ },
beforeUpload: (file) => {
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
const isLt10M = file.size / 1024 / 1024 < 10;
@@ -337,7 +531,7 @@
onSuccess: (response, file, fileList) => {
console.log('涓婁紶鎴愬姛', response, file, fileList);
importUpload.isUploading = false;
- if (response.code === 200) {
+ if (String(response?.code) === "200" || response?.success === true) {
proxy.$modal.msgSuccess("瀵煎叆鎴愬姛");
importDia.value = false;
if (importUploadRef.value) {
@@ -371,21 +565,20 @@
const openModelDia = (type, data) => {
modelOperationType.value = type;
modelDia.value = true;
- modelForm.value.productName = "";
- modelForm.value.model = "";
- modelForm.value.id = "";
- modelForm.value.unit = "";
- modelForm.value.productType = null;
- modelForm.value.routeId = null;
- modelForm.value.drawingFile = "";
- modelForm.value.tempFileIds = [];
- modelForm.value.salesLedgerFiles = [];
+ Object.assign(modelForm.value, createDefaultModelForm());
drawingFileList.value = [];
if (type === "edit") {
- modelForm.value = { ...data };
+ Object.assign(modelForm.value, data);
modelForm.value.tempFileIds = data.tempFileIds || [];
modelForm.value.salesLedgerFiles = data.salesLedgerFiles || [];
- if (data.drawingFile) {
+ // 澶勭悊鍥剧焊鏂囦欢鍙嶆樉
+ if (data.salesLedgerFiles && data.salesLedgerFiles.length > 0) {
+ drawingFileList.value = data.salesLedgerFiles.map(file => ({
+ id: file.id, // 甯︿笂id鐢ㄤ簬鍒犻櫎鏃惰皟鐢ㄦ帴鍙�
+ name: file.name,
+ url: file.url
+ }));
+ } else if (data.drawingFile) {
drawingFileList.value = [{
name: data.drawingFile.split('/').pop(),
url: data.drawingFile
@@ -397,7 +590,13 @@
const submitModelForm = () => {
modelFormRef.value.validate((valid) => {
if (valid) {
- addOrEditProductModel(modelForm.value).then((res) => {
+ // 鏋勫缓鎻愪氦鏁版嵁锛岀‘淇� routeId 涓虹┖鏃朵紶 null锛屽悓鏃舵竻绌� routeName
+ const submitData = {
+ ...modelForm.value,
+ routeId: modelForm.value.routeId || 0,
+ routeName: modelForm.value.routeId ? modelForm.value.routeName : null
+ };
+ addOrEditProductModel(submitData).then((res) => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
closeModelDia();
getModelList();
@@ -407,7 +606,9 @@
};
const closeModelDia = () => {
- modelFormRef.value.resetFields();
+ modelFormRef.value?.resetFields();
+ Object.assign(modelForm.value, createDefaultModelForm());
+ drawingFileList.value = [];
modelDia.value = false;
};
@@ -515,22 +716,96 @@
console.log('涓婁紶鎴愬姛鍝嶅簲', response);
console.log('response.data', response.data);
if (response.code === 200) {
- modelForm.value.tempFileIds = [response.data?.tempId];
- modelForm.value.salesLedgerFiles = [{
+ file.url = response.data?.tempPath || file.url;
+ file.name = response.data?.originalName || file.name;
+ file.tempId = response.data?.tempId;
+ // 鏀寔澶氭枃浠讹紝杩藉姞鍒版暟缁�
+ modelForm.value.tempFileIds.push(response.data?.tempId);
+ modelForm.value.salesLedgerFiles.push({
tempId: response.data?.tempId,
originalName: response.data?.originalName || file.name,
tempPath: response.data?.tempPath,
type: response.data?.type || 13
- }];
+ });
proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
} else {
proxy.$modal.msgError(response.msg || "涓婁紶澶辫触");
}
};
+const getDrawingFileUrl = (file) => {
+ return file.url || file.response?.data?.tempPath || file.tempPath || "";
+};
+
+const getDrawingFileName = (file) => {
+ return file.name || file.originalName || getDrawingFileUrl(file).split("/").pop() || "";
+};
+
+const getDrawingFileExtension = (file) => {
+ const name = getDrawingFileName(file).split("?")[0];
+ const nameParts = name.split(".");
+ return nameParts.length > 1 ? nameParts.pop().toLowerCase() : "file";
+};
+
+const isImageFile = (file) => {
+ return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(getDrawingFileExtension(file));
+};
+
+const isDrawingImageFile = (url) => {
+ if (!url) return false;
+ const ext = url.split("?")[0].split(".").pop()?.toLowerCase();
+ return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext);
+};
+
+const getFileExtensionFromUrl = (url) => {
+ if (!url) return "file";
+ const cleanUrl = url.split("?")[0];
+ const parts = cleanUrl.split(".");
+ return parts.length > 1 ? parts.pop().toLowerCase() : "file";
+};
+
+const handleDrawingPreview = (file) => {
+ const fileUrl = getDrawingFileUrl(file);
+ if (!fileUrl) {
+ return;
+ }
+ filePreviewRef.value?.open(fileUrl);
+};
+
+const handlePreviewFile = (file) => {
+ const fileUrl = file.url || file.tempPath || "";
+ if (!fileUrl) {
+ return;
+ }
+ filePreviewRef.value?.open(fileUrl);
+};
+
+const handlePreviewDrawing = (drawingFile) => {
+ if (!drawingFile) {
+ return;
+ }
+ filePreviewRef.value?.open(drawingFile);
+};
+
const handleDrawingRemove = (file) => {
- modelForm.value.tempFileIds = [];
- modelForm.value.salesLedgerFiles = [];
+ // 濡傛灉鏄紪杈戞ā寮忎笅宸插瓨鍦ㄧ殑鏂囦欢锛堝甫鏈塱d锛夛紝璋冪敤鍒犻櫎鎺ュ彛
+ if (file.id) {
+ delLedgerFile({ id: file.id }).then(res => {
+ if (res.code === 200) {
+ proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+ }
+ }).catch(err => {
+ console.error("鍒犻櫎鏂囦欢澶辫触锛�", err);
+ });
+ }
+ // 浠庢暟缁勪腑绉婚櫎瀵瑰簲鐨勬枃浠�
+ const index = modelForm.value.salesLedgerFiles.findIndex(item =>
+ item.tempId === file.response?.data?.tempId || item.tempId === file.tempId
+ );
+ if (index > -1) {
+ modelForm.value.tempFileIds.splice(index, 1);
+ modelForm.value.salesLedgerFiles.splice(index, 1);
+ }
};
onMounted(() => {
@@ -579,20 +854,159 @@
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
- width: 148px;
- height: 148px;
+ width: 88px;
+ height: 88px;
text-align: center;
- line-height: 148px;
+ line-height: 88px;
}
-:deep(.el-upload--picture-card) {
- width: 148px;
- height: 148px;
+:deep(.el-upload--text) {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 88px;
+ height: 88px;
+ border: 1px dashed #dcdfe6;
+ border-radius: 8px;
}
-:deep(.el-upload-list__item) {
- width: 148px;
- height: 148px;
+:deep(.el-upload-list--text) {
+ margin-top: 8px;
+}
+
+.drawing-preview-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 12px;
+}
+
+.drawing-preview-card {
+ width: 120px;
+ cursor: pointer;
+}
+
+.drawing-preview-image,
+.drawing-preview-placeholder {
+ width: 120px;
+ height: 120px;
+ border: 1px solid #dcdfe6;
+ border-radius: 8px;
+ background: #f5f7fa;
+}
+
+.drawing-preview-image {
+ object-fit: cover;
+}
+
+.drawing-files-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ align-items: center;
+}
+
+.drawing-file-tag {
+ cursor: pointer;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.drawing-file-tag:hover {
+ color: #409eff;
+}
+
+.drawing-file-link {
+ color: #409eff;
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.drawing-file-link:hover {
+ color: #66b1ff;
+}
+
+.drawing-thumbs-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ align-items: center;
+}
+
+.drawing-thumb-item {
+ width: 50px;
+ height: 50px;
+ border-radius: 4px;
+ overflow: hidden;
+ cursor: pointer;
+ border: 1px solid #dcdfe6;
+ transition: all 0.2s;
+}
+
+.drawing-thumb-item:hover {
+ border-color: #409eff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+ transform: scale(1.05);
+}
+
+.drawing-thumb-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.drawing-thumb-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 600;
+ color: #606266;
+ background: #f5f7fa;
+}
+
+.drawing-thumb-more {
+ width: 50px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ color: #909399;
+ background: #f5f7fa;
+ border-radius: 4px;
+ border: 1px dashed #dcdfe6;
+}
+
+.drawing-preview-placeholder {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+ font-weight: 600;
+ color: #606266;
+}
+
+.drawing-preview-placeholder.is-pdf {
+ color: #f56c6c;
+ background: #fef0f0;
+}
+
+.drawing-preview-placeholder.is-dwg {
+ color: #409eff;
+ background: #ecf5ff;
+}
+
+.drawing-preview-name {
+ margin-top: 6px;
+ font-size: 12px;
+ line-height: 1.4;
+ color: #606266;
+ word-break: break-all;
}
:deep(.el-upload__tip) {
--
Gitblit v1.9.3