gaoluyang
2026-04-29 e40e1cf5d4aa99412ca3a87771b5d5a8ea5a105d
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="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>
@@ -194,6 +267,7 @@
        </template>
      </el-upload>
    </FormDialog>
    <filePreview ref="filePreviewRef" />
  </div>
</template>
@@ -204,6 +278,7 @@
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,
@@ -211,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);
@@ -281,6 +358,19 @@
      return typeMap[String(v)] || "info";
    },
  },
  {
    label: "备注",
    prop: "remark",
    minWidth: 150,
    showOverflowTooltip: true,
  },
  {
    label: "图纸",
    prop: "salesLedgerFiles",
    minWidth: 200,
    dataType: "slot",
    slot: "drawingFiles",
  },
   {
      label: "创建时间",
      prop: "createTime",
@@ -310,6 +400,7 @@
const data = reactive({
  modelForm: {
    productId: null,
    productName: "",
    model: "",
    unit: "",
@@ -318,6 +409,7 @@
    drawingFile: "",
    tempFileIds: [],
    salesLedgerFiles: [],
    remark: "",
  },
  modelRules: {
    productName: [
@@ -330,6 +422,19 @@
  },
});
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");
@@ -460,18 +565,10 @@
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 || [];
    // 处理图纸文件反显
@@ -509,7 +606,9 @@
};
const closeModelDia = () => {
  modelFormRef.value.resetFields();
  modelFormRef.value?.resetFields();
  Object.assign(modelForm.value, createDefaultModelForm());
  drawingFileList.value = [];
  modelDia.value = false;
};
@@ -617,6 +716,9 @@
  console.log('上传成功响应', response);
  console.log('response.data', response.data);
  if (response.code === 200) {
    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({
@@ -629,6 +731,60 @@
  } 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) => {
@@ -698,15 +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--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) {