tuqin
2026-09-24 dc3fc550fe292043cc452dc9aeb0c8a1eeab3b30
feat: 新增多模块导入导出入口

- 反馈登记:新增导入按钮
- 售后处理:补导出按钮
- 临期售后管理:新增导入、导出
- 入库管理:新增导入
- 出库台账:新增导入
- 库存管理:新增导入出库
- 产品维护:新增导出
已修改7个文件
已添加1个文件
517 ■■■■■ 文件已修改
src/views/basicData/product/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/afterSalesHandling/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/expiryAfterSales/index.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customerService/feedbackRegistration/index.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/Record.vue 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/Record.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/ImportOut.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Record.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue
@@ -81,6 +81,10 @@
                   plain>
          åˆ é™¤
        </el-button>
        <el-button @click="handleExportModels"
                   style="margin-left: 10px">
          å¯¼å‡º
        </el-button>
      </div>
      <PIMTable rowKey="id"
                :column="tableColumn"
@@ -573,6 +577,19 @@
        proxy.$modal.msg("已取消");
      });
  };
  // å¯¼å‡ºå½“前产品的规格型号
  const handleExportModels = () => {
    if (!currentId.value) {
      proxy.$modal.msgWarning("请先选择产品");
      return;
    }
    proxy.download(
      "/basic/product/exportModel",
      { id: currentId.value },
      "产品规格型号.xlsx"
    );
  };
  const debounce = (fn, delay = 300) => {
    let timer;
    return (...args) => {
src/views/customerService/afterSalesHandling/index.vue
@@ -89,6 +89,9 @@
        </el-row>
      </el-form>
    </div>
        <div class="table_header" style="display: flex; justify-content: flex-end; padding-bottom: 10px;">
            <el-button @click="handleOut">导出</el-button>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
src/views/customerService/expiryAfterSales/index.vue
@@ -34,10 +34,13 @@
                >重置</el-button
                >
            </div>
            <div class="table_actions">
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="danger" @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_header" style="display: flex; justify-content: flex-end; align-items: center; gap: 10px; padding-bottom: 10px;">
            <el-button type="primary" @click="openForm('add')">新增</el-button>
            <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
            <el-button @click="handleExport">导出</el-button>
            <el-button type="danger" @click="handleDelete">删除</el-button>
        </div>
        <div class="table_list">
@@ -65,6 +68,28 @@
            </PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <!-- ä¸´æœŸå”®åŽå¯¼å…¥å¯¹è¯æ¡† -->
        <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
            <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
                :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
                :on-error="handleFileError" :auto-upload="false" drag>
                <el-icon class="el-icon--upload"><upload-filled /></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="importTemplate">下载模板</el-link>
                    </div>
                </template>
            </el-upload>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
                    <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
@@ -75,6 +100,7 @@
import {ElMessageBox} from "element-plus";
import {expiryAfterSalesDelete, expiryAfterSalesListPage} from "@/api/customerService/index.js";
import useUserStore from "@/store/modules/user.js";
import { getToken } from "@/utils/auth.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -263,6 +289,68 @@
        });
};
// å¯¼å‡º
const handleExport = () => {
    ElMessageBox.confirm("是否确认导出全部临期售后数据?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    }).then(() => {
        proxy.download("/afterSalesNearExpiryService/export", {}, "临期售后管理.xlsx");
    }).catch(() => {
        proxy.$modal.msg("已取消");
    });
};
// å¯¼å…¥
const uploadRef = ref();
const upload = reactive({
    open: false,
    title: "",
    isUploading: false,
    headers: { Authorization: "Bearer " + getToken() },
    url: import.meta.env.VITE_APP_BASE_API + "/afterSalesNearExpiryService/import",
});
const handleImport = () => {
    upload.title = "临期售后导入";
    upload.open = true;
};
const importTemplate = () => {
    proxy.download("/afterSalesNearExpiryService/importTemplate", {}, "临期售后管理导入模板.xlsx");
};
const submitFileForm = () => {
    upload.isUploading = true;
    uploadRef.value.submit();
};
const handleFileUploadProgress = () => {
    upload.isUploading = true;
};
const handleFileSuccess = (response) => {
    upload.isUploading = false;
    upload.open = false;
    uploadRef.value.clearFiles();
    if (response.code === 200) {
        const failCount = response.failCount || 0;
        if (failCount > 0) {
            const errors = Array.isArray(response.errors) ? response.errors : [];
            proxy.$modal.alert(`${response.msg}\n\n${errors.join("\n")}`, "导入结果", {
                confirmButtonText: "知道了",
            }).then(() => {
                getList();
            });
        } else {
            proxy.$modal.msgSuccess(response.msg || "导入成功");
            getList();
        }
    } else {
        proxy.$modal.msgError(response.msg || "导入失败");
    }
};
const handleFileError = () => {
    upload.isUploading = false;
    proxy.$modal.msgError("文件上传失败");
};
onMounted(() => {
    getList();
});
src/views/customerService/feedbackRegistration/index.vue
@@ -95,6 +95,10 @@
        </div>
        <div>
          <el-button @click="handleOut">导出</el-button>
          <el-button type="info"
                     plain
                     icon="Upload"
                     @click="handleImport">导入</el-button>
          <el-button type="danger"
                     plain
                     @click="handleDelete">删除</el-button>
@@ -113,6 +117,42 @@
    <form-dia ref="formDia"
              @close="handleQuery"></form-dia>
    <view-dia ref="viewDia"></view-dia>
    <!-- åé¦ˆç™»è®°å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog :title="upload.title"
               v-model="upload.open"
               width="400px"
               append-to-body>
      <el-upload ref="uploadRef"
                 :limit="1"
                 accept=".xlsx, .xls"
                 :headers="upload.headers"
                 :action="upload.url"
                 :disabled="upload.isUploading"
                 :on-progress="handleFileUploadProgress"
                 :on-success="handleFileSuccess"
                 :on-error="handleFileError"
                 :auto-upload="false"
                 drag>
        <el-icon class="el-icon--upload"><upload-filled /></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="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
@@ -135,6 +175,7 @@
    getSalesLedgerDetail,
  } from "@/api/customerService/index.js";
  import useUserStore from "@/store/modules/user.js";
  import { getToken } from "@/utils/auth.js";
  const { proxy } = getCurrentInstance();
  const userStore = useUserStore();
  import { Document, FolderOpened, UserFilled } from "@element-plus/icons-vue";
@@ -328,6 +369,16 @@
  };
  const formDia = ref();
  const viewDia = ref();
  const uploadRef = ref();
  // å¯¼å…¥å¼¹çª—
  const upload = reactive({
    open: false,
    title: "",
    isUploading: false,
    headers: { Authorization: "Bearer " + getToken() },
    url: import.meta.env.VITE_APP_BASE_API + "/afterSalesService/import",
  });
  // å­—典获取
  /* 
@@ -434,6 +485,64 @@
      });
  };
  /** å¯¼å…¥æŒ‰é’®æ“ä½œ */
  const handleImport = () => {
    upload.title = "反馈登记导入";
    upload.open = true;
  };
  /** ä¸‹è½½æ¨¡æ¿ */
  const importTemplate = () => {
    proxy.download(
      "/afterSalesService/importTemplate",
      {},
      "反馈登记导入模板.xlsx"
    );
  };
  /** æäº¤ä¸Šä¼ æ–‡ä»¶ */
  const submitFileForm = () => {
    upload.isUploading = true;
    uploadRef.value.submit();
  };
  /** æ–‡ä»¶ä¸Šä¼ ä¸­å¤„理 */
  const handleFileUploadProgress = () => {
    upload.isUploading = true;
  };
  /** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
  const handleFileSuccess = response => {
    upload.isUploading = false;
    upload.open = false;
    uploadRef.value.clearFiles();
    if (response.code === 200) {
      const failCount = response.failCount || 0;
      if (failCount > 0) {
        const errors = Array.isArray(response.errors) ? response.errors : [];
        proxy.$modal
          .alert(`${response.msg}\n\n${errors.join("\n")}`, "导入结果", {
            confirmButtonText: "知道了",
            dangerouslyUseHTMLString: false,
          })
          .then(() => {
            getList();
          });
      } else {
        proxy.$modal.msgSuccess(response.msg || "导入成功");
        getList();
      }
    } else {
      proxy.$modal.msgError(response.msg || "导入失败");
    }
  };
  /** æ–‡ä»¶ä¸Šä¼ å¤±è´¥å¤„理 */
  const handleFileError = () => {
    upload.isUploading = false;
    proxy.$modal.msgError("文件上传失败");
  };
  const getStatsCountByStatus = (list, status) => {
    if (!Array.isArray(list)) return 0;
    return list.find(item => item?.status === status)?.count || 0;
src/views/inventoryManagement/dispatchLog/Record.vue
@@ -76,6 +76,7 @@
    </div>
    <div class="actions">
      <!-- <el-button type="primary" @click="handleBatchApprove">审批</el-button> -->
      <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
      <el-button @click="handleOut">导出</el-button>
      <el-button type="danger" plain @click="handleDelete">删除</el-button>
      <el-button type="primary" plain @click="handlePrint">打印</el-button>
@@ -153,6 +154,41 @@
        @pagination="paginationChange"
      />
    </div>
    <!-- å‡ºåº“导入对话框 -->
    <el-dialog :title="upload.title"
               v-model="upload.open"
               width="400px"
               append-to-body>
      <el-upload ref="uploadRef"
                 :limit="1"
                 accept=".xlsx, .xls"
                 :headers="upload.headers"
                 :action="upload.url"
                 :disabled="upload.isUploading"
                 :on-progress="handleFileUploadProgress"
                 :on-success="handleFileSuccess"
                 :on-error="handleFileError"
                 :auto-upload="false"
                 drag>
        <el-icon class="el-icon--upload"><upload-filled /></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="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
@@ -172,6 +208,7 @@
  findAllUnQualifiedStockOutRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
import { getToken } from "@/utils/auth.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -682,6 +719,55 @@
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
};
// å¯¼å…¥
const uploadRef = ref();
const upload = reactive({
  open: false,
  title: "",
  isUploading: false,
  headers: { Authorization: "Bearer " + getToken() },
  url: import.meta.env.VITE_APP_BASE_API + "/stockOutRecord/import?type=" + props.type,
});
const handleImport = () => {
  upload.title = "出库导入";
  upload.open = true;
};
const importTemplate = () => {
  proxy.download("/stockOutRecord/importTemplate", {}, "出库导入模板.xlsx");
};
const submitFileForm = () => {
  upload.isUploading = true;
  uploadRef.value.submit();
};
const handleFileUploadProgress = () => {
  upload.isUploading = true;
};
const handleFileSuccess = (response) => {
  upload.isUploading = false;
  upload.open = false;
  uploadRef.value.clearFiles();
  if (response.code === 200) {
    const failCount = response.failCount || 0;
    if (failCount > 0) {
      const errors = Array.isArray(response.errors) ? response.errors : [];
      proxy.$modal.alert(`${response.msg}\n\n${errors.join("\n")}`, "导入结果", {
        confirmButtonText: "知道了",
      }).then(() => {
        getList();
      });
    } else {
      proxy.$modal.msgSuccess(response.msg || "导入成功");
      getList();
    }
  } else {
    proxy.$modal.msgError(response.msg || "导入失败");
  }
};
const handleFileError = () => {
  upload.isUploading = false;
  proxy.$modal.msgError("文件上传失败");
};
onMounted(() => {
  getList();
  fetchStockRecordTypeOptions();
src/views/inventoryManagement/receiptManagement/Record.vue
@@ -84,6 +84,10 @@
                 @click="handleBatchApprove">审批</el-button>
      <el-button :disabled="!canReverseApprove"
                 @click="handleReverseApprove">反审</el-button> -->
      <el-button type="info"
                 plain
                 icon="Upload"
                 @click="handleImport">导入</el-button>
      <el-button @click="handleOut">导出</el-button>
      <el-button type="danger"
                 plain
@@ -173,6 +177,42 @@
                  :limit="page.size"
                  @pagination="pageProductChange"/>
    </div>
    <!-- å…¥åº“导入对话框 -->
    <el-dialog :title="upload.title"
               v-model="upload.open"
               width="400px"
               append-to-body>
      <el-upload ref="uploadRef"
                 :limit="1"
                 accept=".xlsx, .xls"
                 :headers="upload.headers"
                 :action="upload.url"
                 :disabled="upload.isUploading"
                 :on-progress="handleFileUploadProgress"
                 :on-success="handleFileSuccess"
                 :on-error="handleFileError"
                 :auto-upload="false"
                 drag>
        <el-icon class="el-icon--upload"><upload-filled /></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="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
@@ -198,6 +238,7 @@
  // findAllUnQualifiedStockInRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
import { getToken } from "@/utils/auth.js";
const {proxy} = getCurrentInstance();
@@ -454,6 +495,55 @@
      });
};
// å¯¼å…¥
const uploadRef = ref();
const upload = reactive({
  open: false,
  title: "",
  isUploading: false,
  headers: { Authorization: "Bearer " + getToken() },
  url: import.meta.env.VITE_APP_BASE_API + "/stockInRecord/import?type=" + props.type,
});
const handleImport = () => {
  upload.title = "入库导入";
  upload.open = true;
};
const importTemplate = () => {
  proxy.download("/stockInRecord/importTemplate", {}, "入库导入模板.xlsx");
};
const submitFileForm = () => {
  upload.isUploading = true;
  uploadRef.value.submit();
};
const handleFileUploadProgress = () => {
  upload.isUploading = true;
};
const handleFileSuccess = (response) => {
  upload.isUploading = false;
  upload.open = false;
  uploadRef.value.clearFiles();
  if (response.code === 200) {
    const failCount = response.failCount || 0;
    if (failCount > 0) {
      const errors = Array.isArray(response.errors) ? response.errors : [];
      proxy.$modal.alert(`${response.msg}\n\n${errors.join("\n")}`, "导入结果", {
        confirmButtonText: "知道了",
      }).then(() => {
        getList();
      });
    } else {
      proxy.$modal.msgSuccess(response.msg || "导入成功");
      getList();
    }
  } else {
    proxy.$modal.msgError(response.msg || "导入失败");
  }
};
const handleFileError = () => {
  upload.isUploading = false;
  proxy.$modal.msgError("文件上传失败");
};
onMounted(() => {
  getList();
  fetchStockRecordTypeOptions();
src/views/inventoryManagement/stockManagement/ImportOut.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
<template>
  <el-dialog v-model="isShow" title="导入出库" @close="closeModal">
    <FileUpload
      ref="fileUploadRef"
      accept=".xlsx, .xls"
      :headers="upload.headers"
      :action="upload.url"
      :disabled="upload.isUploading"
      :showTip="true"
      @success="handleFileSuccess"
      :downloadTemplate="downloadTemplate"
    />
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
        <el-button @click="closeModal">取 æ¶ˆ</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed, getCurrentInstance, reactive } from "vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import { ElMessage } from "element-plus";
defineOptions({
  name: "导入出库",
});
const { proxy } = getCurrentInstance();
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
});
const emit = defineEmits(["update:visible", "uploadSuccess"]);
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit("update:visible", val);
  },
});
const fileUploadRef = ref();
const upload = reactive({
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ åœ°å€ï¼šå¤ç”¨å‡ºåº“台账导入接口(type=0 åˆæ ¼ï¼‰ï¼Œç”Ÿæˆå¾…审批出库记录,导入时不扣库存
  url: import.meta.env.VITE_APP_BASE_API + "/stockOutRecord/import?type=0",
});
const submitFileForm = () => {
  fileUploadRef.value.uploadApi();
};
const handleFileSuccess = (response) => {
  const { code, msg } = response;
  if (code == 200) {
    const failCount = response.failCount || 0;
    if (failCount > 0) {
      const errors = Array.isArray(response.errors) ? response.errors : [];
      proxy.$modal
        .alert(`${response.msg}\n\n${errors.join("\n")}`, "导入结果", {
          confirmButtonText: "知道了",
        })
        .then(() => {
          emit("uploadSuccess");
          closeModal();
        });
    } else {
      ElMessage({ message: response.msg || "导入成功", type: "success" });
      emit("uploadSuccess");
      closeModal();
    }
  } else {
    ElMessage({ message: msg, type: "error" });
  }
};
const downloadTemplate = () => {
  proxy.download("/stockOutRecord/importTemplate", {}, "出库导入模板.xlsx");
};
const closeModal = () => {
  isShow.value = false;
};
</script>
src/views/inventoryManagement/stockManagement/Record.vue
@@ -54,6 +54,14 @@
                  >
                    å¯¼å…¥åº“å­˜
                  </el-button>
                  <el-button
                    type="info"
                    plain
                    icon="Upload"
                    @click="isShowImportOutModal = true"
                  >
                    å¯¼å…¥å‡ºåº“
                  </el-button>
                  <el-button @click="handleOut">导出</el-button>
                </div>
              </div>
@@ -169,6 +177,12 @@
      type="qualified"
      @uploadSuccess="handleQuery"
    />
    <!-- å¯¼å…¥å‡ºåº“-->
    <import-stock-out
      v-if="isShowImportOutModal"
      v-model:visible="isShowImportOutModal"
      @uploadSuccess="handleQuery"
    />
    <!-- å†»ç»“/解冻库存-->
    <frozen-and-thaw-stock-inventory
      v-if="isShowFrozenAndThawModal"
@@ -203,6 +217,9 @@
const ImportStockInventory = defineAsyncComponent(() =>
  import("@/views/inventoryManagement/stockManagement/Import.vue")
);
const ImportStockOut = defineAsyncComponent(() =>
  import("@/views/inventoryManagement/stockManagement/ImportOut.vue")
);
const FrozenAndThawStockInventory = defineAsyncComponent(() =>
  import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue")
);
@@ -231,6 +248,8 @@
const operationType = ref("frozen");
// æ˜¯å¦æ˜¾ç¤ºå¯¼å…¥å¼¹æ¡†
const isShowImportModal = ref(false);
// æ˜¯å¦æ˜¾ç¤ºå¯¼å…¥å‡ºåº“弹框
const isShowImportOutModal = ref(false);
const data = reactive({
  searchForm: {
    productName: "",