gaoluyang
2026-02-03 d43d7b9c90ae36c2d369f5c52b207b5549675766
双奇点改造
1.产品维护添加图片、高度等字段
已添加3个文件
已修改3个文件
405 ■■■■■ 文件已修改
multiple/assets/favicon/SQDico.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/SQDLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/screen/SQDView.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ImportExcel/index.vue 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 326 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/SQDico.ico
multiple/assets/logo/SQDLogo.png
multiple/assets/screen/SQDView.png
multiple/config.json
@@ -3,19 +3,19 @@
    "env": {
      "VITE_APP_TITLE": "芯导云(管理信息系统)"
    },
    "screen": "screen/JZYJView.png",
    "logo": "logo/HYSNLogo.png",
    "favicon": "favicon/HYSNico.ico"
    "screen": "screen/SQDView.png",
    "logo": "logo/SQDLogo.png",
    "favicon": "favicon/SQDico.ico"
  },
  "TEST": {
  "SQD": {
    "env": {
      "VITE_APP_TITLE": "中小企业数字化转型二级套餐包",
      "VITE_BASE_API": "http://1.15.17.182:9003",
      "VITE_JAVA_API": "http://1.15.17.182:9002"
      "VITE_APP_TITLE": "天津双奇点管理系统",
      "VITE_BASE_API": "http://114.132.189.42:9042",
      "VITE_JAVA_API": "http://114.132.189.42:9044"
    },
    "screen": "screen/HYSNView.png",
    "logo": "logo/ZGLTLogo.png",
    "favicon": "favicon/favicon.ico"
    "screen": "screen/SQDView.png",
    "logo": "logo/SQDLogo.png",
    "favicon": "favicon/SQDico.ico"
  },
  "screen": "/src/assets/images/login-background.png",
  "logo": "/src/assets/logo/logo.png",
src/views/basicData/product/ImportExcel/index.vue
@@ -3,15 +3,36 @@
    导入
  </el-button>
  <el-dialog v-model="upload.open" :title="upload.title">
    <FileUpload
      ref="fileUploadRef"
    <el-upload
      ref="uploadRef"
      :limit="1"
      accept=".xlsx, .xls"
      :headers="upload.headers"
      :action="upload.url + '?updateSupport=' + upload.updateSupport"
      :disabled="upload.isUploading"
      :showTip="false"
      @success="handleFileSuccess"
    />
      :before-upload="upload.beforeUpload"
      :on-progress="upload.onProgress"
      :on-success="upload.onSuccess"
      :on-error="upload.onError"
      :on-change="upload.onChange"
      :auto-upload="false"
      drag
    >
      <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <template #tip>
        <div class="el-upload__tip text-center">
          <span>仅允许导入xls、xlsx格式文件。</span>
          <el-link
            type="primary"
            :underline="false"
            style="font-size: 12px; vertical-align: baseline"
            @click="handleDownloadTemplate"
            >下载模板</el-link
          >
        </div>
      </template>
    </el-upload>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">确 定</el-button>
@@ -22,17 +43,18 @@
</template>
<script setup>
import { reactive } from "vue";
import { reactive, ref, getCurrentInstance } from "vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import { ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
defineOptions({
  name: "产品维护导入",
});
const emits = defineEmits(["uploadSuccess"]);
const fileUploadRef = ref();
const uploadRef = ref();
const { proxy } = getCurrentInstance();
const upload = reactive({
  // 是否显示弹出层(供应商导入)
  open: false,
@@ -44,6 +66,20 @@
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
  updateSupport: false,
  beforeUpload: () => {
    upload.isUploading = true;
  },
  onProgress: () => {},
  onChange: () => {},
  onError: () => {
    upload.isUploading = false;
    ElMessage({ message: "上传失败", type: "error" });
  },
  onSuccess: (response) => {
    upload.isUploading = false;
    handleFileSuccess(response);
  },
});
// 点击导入
const handleImport = () => {
@@ -51,8 +87,13 @@
  upload.title = "产品导入";
};
// 下载导入模板
const handleDownloadTemplate = () => {
  proxy.download("/basic/product/downloadTemplate", {}, "产品导入模板.xlsx");
};
const submitFileForm = () => {
  fileUploadRef.value.uploadApi();
  uploadRef.value.submit();
};
const handleFileSuccess = (response) => {
src/views/basicData/product/index.vue
@@ -92,7 +92,18 @@
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      ></PIMTable>
      >
        <template #productImage="{ row }">
          <img
            v-if="row.url"
            class="upload-img"
            :src="javaApiUrl + row.url"
            @click="previewImage(row.url)"
            style="cursor: pointer"
          />
          <span v-else style="color: #909399">暂无图片</span>
        </template>
      </PIMTable>
    </div>
    <el-dialog v-model="productDia" title="产品" width="400px" @keydown.enter.prevent>
      <el-form
@@ -127,7 +138,7 @@
    <el-dialog
      v-model="modelDia"
      title="规格型号"
      width="400px"
      width="600px"
      @close="closeModelDia"
      @keydown.enter.prevent
    >
@@ -138,8 +149,8 @@
        :rules="modelRules"
        ref="modelFormRef"
      >
        <el-row>
          <el-col :span="24">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="model">
              <el-input
                v-model="modelForm.model"
@@ -149,9 +160,7 @@
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input
                v-model="modelForm.unit"
@@ -159,6 +168,79 @@
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="高度:" prop="height">
              <el-input
                v-model="modelForm.height"
                placeholder="请输入高度"
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="每件数量/支:" prop="boxNum">
              <el-input-number
                :step="1"
                :min="0"
                style="width: 100%"
                v-model="modelForm.boxNum"
                @change="calculateTotalPrice"
                placeholder="请输入每件数量"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="单价(元)/件:" prop="taxInclusiveUnitPrice">
              <el-input-number
                :step="0.01"
                :min="0"
                style="width: 100%"
                v-model="modelForm.taxInclusiveUnitPrice"
                @change="calculateTotalPrice"
                placeholder="请输入单价"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="单价(美元)/件:" prop="dollarPrice">
              <el-input-number
                :step="0.01"
                :min="0"
                style="width: 100%"
                v-model="modelForm.dollarPrice"
                placeholder="请输入美元单价"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="产品图片:" prop="url">
              <el-upload
                :action="uploadUrl"
                :before-upload="handleBeforeUpload"
                :on-success="(res, file) => handleUploadSuccess(res, file)"
                :on-error="handleUploadError"
                name="file"
                :show-file-list="false"
                :headers="headers"
                accept="image/*"
                :data="{ type: 13 }"
              >
                <img
                  v-if="modelForm.url"
                  class="upload-img-dialog"
                  :src="javaApiUrl + modelForm.url"
                />
                <el-icon v-else class="avatar-uploader-icon-dialog"><Plus /></el-icon>
              </el-upload>
            </el-form-item>
          </el-col>
        </el-row>
@@ -174,8 +256,10 @@
</template>
<script setup>
import { ref } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth";
import {
  addOrEditProduct,
  addOrEditProductModel,
@@ -185,6 +269,7 @@
  productTreeList,
} from "@/api/basicData/product.js";
import ImportExcel from "./ImportExcel/index.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
const { proxy } = getCurrentInstance();
const tree = ref(null);
@@ -202,12 +287,40 @@
const expandedKeys = ref([]);
const tableColumn = ref([
  {
    label: "产品图片",
    prop: "url",
    dataType: "slot",
    slot: "productImage",
    align: "center",
    width: 100,
  },
  {
    label: "规格型号",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "高度",
    prop: "height",
    width: 120,
  },
  {
    label: "每件数量/支",
    prop: "boxNum",
    width: 120,
  },
  {
    label: "单价(元)/件",
    prop: "taxInclusiveUnitPrice",
    width: 120,
  },
  {
    label: "单价(美元)/件",
    prop: "dollarPrice",
    width: 130,
  },
  {
    dataType: "action",
@@ -228,6 +341,11 @@
const tableLoading = ref(false);
const isShowButton = ref(false);
const selectedRows = ref([]);
// 上传配置
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const javaApiUrl = proxy.javaApi || import.meta.env.VITE_APP_BASE_API;
const page = reactive({
  current: 1,
  size: 10,
@@ -246,10 +364,20 @@
  modelForm: {
    model: "",
    unit: "",
    url: "",
    height: "",
    boxNum: null,
    taxInclusiveUnitPrice: null,
    dollarPrice: null,
  },
  modelRules: {
    model: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: true, message: "请输入规格型号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    url: [{ required: true, message: "请上传产品图片", trigger: "change" }],
    height: [{ required: true, message: "请输入高度", trigger: "blur" }],
    boxNum: [{ required: true, message: "请输入每件数量/支", trigger: "change" }],
    taxInclusiveUnitPrice: [{ required: true, message: "请输入单价(元)/件", trigger: "change" }],
    dollarPrice: [{ required: true, message: "请输入单价(美元)/件", trigger: "change" }],
  },
});
const { form, rules, modelForm, modelRules } = toRefs(data);
@@ -285,11 +413,30 @@
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  modelForm.value.model = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  if (type === "edit") {
    modelForm.value = { ...data };
  // 重置表单
  modelForm.value = {
    model: "",
    unit: "",
    url: "",
    height: "",
    boxNum: null,
    taxInclusiveUnitPrice: null,
    dollarPrice: null,
    id: "",
  };
  if (type === "edit" && data) {
    // 如果 url 是 Windows 路径,需要转换
    let url = data.url || "";
    if (url && url.indexOf("\\") > -1) {
      url = processFileUrl(url);
    }
    modelForm.value = {
      ...data,
      url: url,
      boxNum: data.boxNum || null,
      taxInclusiveUnitPrice: data.taxInclusiveUnitPrice || null,
      dollarPrice: data.dollarPrice || null,
    };
  }
};
// 提交产品名称修改
@@ -391,10 +538,107 @@
    size: page.size,
  }).then((res) => {
    console.log("res", res);
    tableData.value = res.records;
    // 处理返回的数据,转换 Windows 路径为可访问的 URL
    tableData.value = (res.records || []).map((item) => {
      if (item.url && item.url.indexOf("\\") > -1) {
        item.url = processFileUrl(item.url);
      }
      return item;
    });
    page.total = res.total;
    tableLoading.value = false;
  });
};
// 上传前校验
const handleBeforeUpload = (file) => {
  const isImage = file.type.startsWith("image/");
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isImage) {
    proxy.$modal.msgError("上传文件只能是图片格式!");
    return false;
  }
  if (!isLt5M) {
    proxy.$modal.msgError("上传图片大小不能超过 5MB!");
    return false;
  }
  return true;
};
// 处理文件路径:将 Windows 路径转换为可访问的 URL
const processFileUrl = (filePath) => {
  if (!filePath) return "";
  // 如果路径是 Windows 路径格式(包含反斜杠),需要转换
  if (filePath && filePath.indexOf("\\") > -1) {
    // 查找 temp 或 uploads 关键字的位置
    const tempIndex = filePath.toLowerCase().indexOf("temp");
    const uploadsIndex = filePath.toLowerCase().indexOf("uploads");
    if (tempIndex > -1) {
      // 从 temp 开始提取相对路径,并将反斜杠替换为正斜杠
      const relativePath = filePath.substring(tempIndex).replace(/\\/g, "/");
      filePath = "/" + relativePath;
    } else if (uploadsIndex > -1) {
      // 从 uploads 开始提取相对路径
      const relativePath = filePath.substring(uploadsIndex).replace(/\\/g, "/");
      filePath = "/" + relativePath;
    } else {
      // 如果没有找到关键字,提取文件名
      const parts = filePath.split("\\");
      const fileName = parts[parts.length - 1];
      filePath = "/temp/uploads/" + fileName;
    }
  }
  // 确保路径以 / 开头
  if (filePath && !filePath.startsWith("/")) {
    filePath = "/" + filePath;
  }
  return filePath;
};
// 上传成功
const handleUploadSuccess = (res, file) => {
  if (res.code === 200) {
    // 从 res.data 中获取 tempPath,并转换为可访问的 URL
    const tempPath = res.data?.tempPath || res.tempPath;
    if (tempPath) {
      const relativePath = processFileUrl(tempPath);
      modelForm.value.url = relativePath;
      proxy.$modal.msgSuccess("上传成功");
      // 触发表单验证
      nextTick(() => {
        proxy.$refs.modelFormRef?.validateField("url");
      });
    } else {
      proxy.$modal.msgError("上传成功但未返回文件路径");
    }
  } else {
    proxy.$modal.msgError(res.msg || "上传失败");
  }
};
// 上传失败
const handleUploadError = () => {
  proxy.$modal.msgError("上传失败,请重试");
};
// 计算总价
const calculateTotalPrice = () => {
  // 如果需要计算总价,可以在这里添加逻辑
  // if (modelForm.value.boxNum && modelForm.value.taxInclusiveUnitPrice) {
  //   modelForm.value.totalPrice = modelForm.value.boxNum * modelForm.value.taxInclusiveUnitPrice;
  // }
};
// 预览图片
const previewImage = (url) => {
  if (url) {
    window.open(javaApiUrl + url, "_blank");
  }
};
// 删除规格型号
const handleDelete = () => {
@@ -529,4 +773,54 @@
.product-tree-scroll::-webkit-scrollbar-thumb:hover {
  background: #909399;
}
.upload-img {
  width: 50px;
  height: 50px;
  object-fit: cover;
  cursor: pointer;
  border-radius: 4px;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 50px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  border: 1px dashed #d9d9d9;
  border-radius: 4px;
  cursor: pointer;
}
.avatar-uploader-icon:hover {
  border-color: #409eff;
}
.upload-img-dialog {
  width: 100px;
  height: 100px;
  object-fit: cover;
  cursor: pointer;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}
.avatar-uploader-icon-dialog {
  font-size: 28px;
  color: #8c939d;
  width: 100px;
  height: 100px;
  line-height: 100px;
  text-align: center;
  border: 1px dashed #d9d9d9;
  border-radius: 4px;
  cursor: pointer;
  display: block;
}
.avatar-uploader-icon-dialog:hover {
  border-color: #409eff;
}
</style>