From f4a4737d69746268f3648effcf66e1c1c83e64e1 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 29 四月 2026 14:03:23 +0800
Subject: [PATCH] 公司7004 1.标书台账页面开发与联调 2.客户管理添加客户类型字段

---
 src/views/basicData/customerFile/index.vue                |   31 ++
 src/views/salesManagement/opportunityManagement/index.vue |   37 +
 src/api/salesManagement/bidWinningLedger.js               |   56 +++
 src/views/salesManagement/bidWinningLedger/index.vue      |  724 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 835 insertions(+), 13 deletions(-)

diff --git a/src/api/salesManagement/bidWinningLedger.js b/src/api/salesManagement/bidWinningLedger.js
new file mode 100644
index 0000000..4a2f28e
--- /dev/null
+++ b/src/api/salesManagement/bidWinningLedger.js
@@ -0,0 +1,56 @@
+import request from "@/utils/request";
+
+export function getProvinceList() {
+  return request({
+    url: "/sales/bidWinningLedger/getProvinceList",
+    method: "get",
+  });
+}
+
+export function getCityList(params) {
+  return request({
+    url: "/sales/bidWinningLedger/getCityList",
+    method: "get",
+    params,
+  });
+}
+
+export function bidWinningLedgerListPage(params) {
+  return request({
+    url: "/sales/bidWinningLedger/listPage",
+    method: "get",
+    params,
+  });
+}
+
+export function bidWinningLedgerDetail(params) {
+  return request({
+    url: "/sales/bidWinningLedger/detail",
+    method: "get",
+    params,
+  });
+}
+
+export function bidWinningLedgerAdd(data) {
+  return request({
+    url: "/sales/bidWinningLedger/add",
+    method: "post",
+    data,
+  });
+}
+
+export function bidWinningLedgerUpdate(data) {
+  return request({
+    url: "/sales/bidWinningLedger/update",
+    method: "post",
+    data,
+  });
+}
+
+export function bidWinningLedgerDelete(data) {
+  return request({
+    url: "/sales/bidWinningLedger/delete",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/views/basicData/customerFile/index.vue b/src/views/basicData/customerFile/index.vue
index 8043d1a..e358b53 100644
--- a/src/views/basicData/customerFile/index.vue
+++ b/src/views/basicData/customerFile/index.vue
@@ -122,6 +122,19 @@
               />
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="瀹㈡埛绫诲瀷锛�" prop="customerType">
+              <el-select
+                v-model="form.customerType"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+              >
+                <el-option label="鏅�氬鎴�" :value="1" />
+                <el-option label="涓皬瀹㈡埛" :value="2" />
+              </el-select>
+            </el-form-item>
+          </el-col>
         </el-row>
 				<el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index">
 					<el-col :span="12">
@@ -247,6 +260,22 @@
 const userStore = useUserStore();
 
 const tableColumn = ref([
+	{
+		label: "瀹㈡埛绫诲瀷",
+		prop: "customerType",
+		width: 120,
+		dataType: "tag",
+		formatData: (value) => {
+			if (String(value) === "1") return "鏅�氬鎴�";
+			if (String(value) === "2") return "涓皬瀹㈡埛";
+			return "";
+		},
+		formatType: (value) => {
+			if (String(value) === "1") return "primary";
+			if (String(value) === "2") return "success";
+			return "info";
+		},
+	},
   {
     label: "瀹㈡埛鍚嶇О",
     prop: "customerName",
@@ -342,6 +371,7 @@
   },
   form: {
     customerName: "",
+    customerType: undefined,
     taxpayerIdentificationNumber: "",
     companyAddress: "",
     companyPhone: "",
@@ -355,6 +385,7 @@
   },
   rules: {
     customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+    customerType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
     taxpayerIdentificationNumber: [
       { required: true, message: "璇疯緭鍏�", trigger: "blur" },
     ],
diff --git a/src/views/salesManagement/bidWinningLedger/index.vue b/src/views/salesManagement/bidWinningLedger/index.vue
new file mode 100644
index 0000000..870f1be
--- /dev/null
+++ b/src/views/salesManagement/bidWinningLedger/index.vue
@@ -0,0 +1,724 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="鐪佷唤">
+          <el-select
+            v-model="searchForm.provinceId"
+            placeholder="璇烽�夋嫨鐪佷唤"
+            clearable
+            filterable
+            style="width: 180px"
+            @change="handleSearchProvinceChange"
+          >
+            <el-option
+              v-for="item in provinceOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鍩庡競">
+          <el-select
+            v-model="searchForm.cityId"
+            placeholder="璇烽�夋嫨鍩庡競"
+            clearable
+            filterable
+            style="width: 180px"
+            @change="handleQuery"
+          >
+            <el-option
+              v-for="item in searchCityOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="瀹㈡埛鍚嶇О">
+          <el-select
+            v-model="searchForm.customerName"
+            placeholder="璇烽�夋嫨瀹㈡埛鍚嶇О"
+            clearable
+            filterable
+            style="width: 200px"
+            @change="handleQuery"
+          >
+            <el-option
+              v-for="item in customerOptions"
+              :key="item.id || item.customerName"
+              :label="item.customerName"
+              :value="item.customerName"
+            >
+              {{ item.customerName }}{{ item.taxpayerIdentificationNumber ? ` - ${item.taxpayerIdentificationNumber}` : "" }}
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="褰曞叆浜�">
+          <el-input
+            v-model="searchForm.entryPerson"
+            placeholder="璇疯緭鍏ュ綍鍏ヤ汉"
+            clearable
+            style="width: 180px"
+            @change="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="褰曞叆鏃ユ湡">
+          <el-date-picker
+            v-model="searchForm.entryDateRange"
+            type="daterange"
+            value-format="YYYY-MM-DD"
+            format="YYYY-MM-DD"
+            range-separator="-"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            clearable
+            @change="changeDateRange"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetQuery">閲嶇疆</el-button>
+          <el-button type="primary" @click="handleAdd">鏂板</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <el-table
+        :data="tableData"
+        border
+        stripe
+        v-loading="tableLoading"
+        height="calc(100vh - 18.5em)"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="55" align="center" fixed="left" />
+        <el-table-column type="index" label="搴忓彿" width="60" align="center" fixed="left" />
+        <el-table-column prop="province" label="鐪佷唤" min-width="120" />
+        <el-table-column prop="city" label="鍩庡競" min-width="120" />
+        <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" min-width="220" show-overflow-tooltip />
+        <el-table-column prop="contractAmount" label="涓爣閲戦" min-width="130" />
+        <el-table-column prop="bidBond" label="鎶曟爣淇濊瘉閲�" min-width="130" />
+        <el-table-column prop="winningServiceFee" label="涓爣鏈嶅姟璐�" min-width="130" />
+        <el-table-column prop="remark" label="澶囨敞" min-width="180" show-overflow-tooltip />
+        <el-table-column prop="entryPerson" label="褰曞叆浜�" min-width="100" />
+        <el-table-column prop="entryDate" label="褰曞叆鏃ユ湡" min-width="120" />
+        <el-table-column prop="updateTime" label="淇敼鏃堕棿" min-width="170" />
+        <el-table-column fixed="right" label="鎿嶄綔" width="180" align="center">
+          <template #default="{ row }">
+						<el-button link type="primary" size="small" @click="handleEdit(row)">
+							缂栬緫
+						</el-button>
+            <el-button link type="primary" size="small" @click="handleDetail(row)">
+              璇︽儏
+            </el-button>
+            <el-button link type="danger" size="small" @click="handleDelete(row)">
+              鍒犻櫎
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        :page="page.current"
+        :limit="page.size"
+        @pagination="paginationChange"
+      />
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="900px"
+      @close="closeDialog"
+    >
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="rules"
+        label-width="100px"
+        :disabled="dialogMode === 'detail'"
+      >
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+              <el-select
+                v-model="form.customerName"
+                placeholder="璇烽�夋嫨瀹㈡埛鍚嶇О"
+                clearable
+                filterable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in customerOptions"
+                  :key="item.id || item.customerName"
+                  :label="item.customerName"
+                  :value="item.customerName"
+                >
+                  {{ item.customerName }}{{ item.taxpayerIdentificationNumber ? ` - ${item.taxpayerIdentificationNumber}` : "" }}
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鐪佷唤" prop="provinceId">
+              <el-select
+                v-model="form.provinceId"
+                placeholder="璇烽�夋嫨鐪佷唤"
+                filterable
+                style="width: 100%"
+                @change="handleFormProvinceChange"
+              >
+                <el-option
+                  v-for="item in provinceOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍩庡競" prop="cityId">
+              <el-select
+                v-model="form.cityId"
+                placeholder="璇烽�夋嫨鍩庡競"
+                filterable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in cityOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="涓爣閲戦" prop="contractAmount">
+              <el-input-number
+                v-model="form.contractAmount"
+                :min="0"
+                :precision="2"
+                style="width: 100%"
+                controls-position="right"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎶曟爣淇濊瘉閲�" prop="bidBond">
+              <el-input-number
+                v-model="form.bidBond"
+                :min="0"
+                :precision="2"
+                style="width: 100%"
+                controls-position="right"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="涓爣鏈嶅姟璐�" prop="winningServiceFee">
+              <el-input-number
+                v-model="form.winningServiceFee"
+                :min="0"
+                :precision="2"
+                style="width: 100%"
+                controls-position="right"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="褰曞叆浜�" prop="entryPerson">
+              <el-select
+                v-model="form.entryPerson"
+                placeholder="璇烽�夋嫨褰曞叆浜�"
+                filterable
+                allow-create
+                default-first-option
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in userOptions"
+                  :key="item.userId || item.nickName"
+                  :label="item.nickName"
+                  :value="item.nickName"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="褰曞叆鏃ユ湡" prop="entryDate">
+              <el-date-picker
+                v-model="form.entryDate"
+                type="date"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                placeholder="璇烽�夋嫨褰曞叆鏃ユ湡"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            :rows="3"
+            placeholder="璇疯緭鍏ュ娉�"
+          />
+        </el-form-item>
+
+        <el-form-item label="闄勪欢" v-if="dialogMode !== 'detail'">
+          <el-upload
+            ref="fileUploadRef"
+            v-model:file-list="fileList"
+            :action="upload.url"
+            :headers="upload.headers"
+            :data="upload.data"
+            multiple
+            auto-upload
+            :before-upload="handleBeforeUpload"
+            :on-success="handleUploadSuccess"
+            :on-error="handleUploadError"
+            :on-remove="handleRemove"
+          >
+            <el-button type="primary">涓婁紶闄勪欢</el-button>
+            <template #tip>
+              <div class="el-upload__tip">鏀寔甯歌鍔炲叕鏂囨。涓庡浘鐗囨牸寮�</div>
+            </template>
+          </el-upload>
+        </el-form-item>
+
+      </el-form>
+
+      <div v-if="dialogMode === 'detail'">
+        <el-divider content-position="left">闄勪欢</el-divider>
+        <el-table :data="form.commonFiles || []" border stripe empty-text="鏆傛棤闄勪欢">
+          <el-table-column prop="name" label="闄勪欢鍚嶇О" min-width="300" show-overflow-tooltip />
+          <el-table-column label="鎿嶄綔" width="160" align="center">
+            <template #default="{ row }">
+              <el-button link type="primary" size="small" @click="downloadFile(row)">
+                涓嬭浇
+              </el-button>
+              <el-button link type="primary" size="small" @click="previewFile(row)">
+                棰勮
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <filePreview ref="filePreviewRef" />
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDialog">鍙栨秷</el-button>
+          <el-button type="primary" v-if="dialogMode !== 'detail'" @click="submitForm">
+            纭畾
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { computed, getCurrentInstance, onMounted, reactive, ref } from "vue";
+import dayjs from "dayjs";
+import { ElMessageBox } from "element-plus";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import filePreview from "@/components/filePreview/index.vue";
+import useUserStore from "@/store/modules/user";
+import { getToken } from "@/utils/auth";
+import { userListNoPage } from "@/api/system/user";
+import { customerList } from "@/api/salesManagement/salesLedger";
+import {
+  bidWinningLedgerAdd,
+  bidWinningLedgerDelete,
+  bidWinningLedgerDetail,
+  bidWinningLedgerListPage,
+  bidWinningLedgerUpdate,
+  getCityList,
+  getProvinceList,
+} from "@/api/salesManagement/bidWinningLedger";
+
+const { proxy } = getCurrentInstance();
+const userStore = useUserStore();
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const selectedRows = ref([]);
+const total = ref(0);
+const userOptions = ref([]);
+const customerOptions = ref([]);
+const provinceOptions = ref([]);
+const cityOptions = ref([]);
+const searchCityOptions = ref([]);
+const dialogVisible = ref(false);
+const dialogMode = ref("add");
+const formRef = ref();
+const fileUploadRef = ref();
+const filePreviewRef = ref();
+const fileList = ref([]);
+
+const upload = reactive({
+  url: `${import.meta.env.VITE_APP_BASE_API}/file/upload`,
+  headers: {
+    Authorization: `Bearer ${getToken()}`,
+  },
+  data: {
+    type: 10,
+  },
+});
+
+const page = reactive({
+  current: 1,
+  size: 20,
+});
+
+const createSearchForm = () => ({
+  provinceId: "",
+  cityId: "",
+  customerName: "",
+  entryPerson: "",
+  entryDateRange: [],
+  entryDateStart: "",
+  entryDateEnd: "",
+});
+
+const createFormData = () => ({
+  id: undefined,
+  province: "",
+  city: "",
+  provinceId: "",
+  cityId: "",
+  customerName: "",
+  contractAmount: undefined,
+  bidBond: undefined,
+  winningServiceFee: undefined,
+  remark: "",
+  entryPerson: userStore.nickName || "",
+  entryDate: dayjs().format("YYYY-MM-DD"),
+  commonFiles: [],
+  tempFileIds: [],
+});
+
+const searchForm = reactive(createSearchForm());
+const form = reactive(createFormData());
+
+const rules = reactive({
+  provinceId: [{ required: true, message: "璇烽�夋嫨鐪佷唤", trigger: "change" }],
+  cityId: [{ required: true, message: "璇烽�夋嫨鍩庡競", trigger: "change" }],
+  customerName: [{ required: true, message: "璇烽�夋嫨瀹㈡埛鍚嶇О", trigger: "change" }],
+  entryPerson: [{ required: true, message: "璇烽�夋嫨褰曞叆浜�", trigger: "change" }],
+  entryDate: [{ required: true, message: "璇烽�夋嫨褰曞叆鏃ユ湡", trigger: "change" }],
+});
+
+const dialogTitle = computed(() => {
+  if (dialogMode.value === "add") return "鏂板涓爣鍙拌处";
+  if (dialogMode.value === "edit") return "缂栬緫涓爣鍙拌处";
+  return "涓爣鍙拌处璇︽儏";
+});
+
+const getOptionName = (options, id) => {
+  const target = options.find((item) => String(item.id) === String(id));
+  return target?.name || "";
+};
+
+const buildQueryParams = () => {
+  const params = {
+    current: page.current,
+    size: page.size,
+    customerName: searchForm.customerName,
+    entryPerson: searchForm.entryPerson,
+    province: getOptionName(provinceOptions.value, searchForm.provinceId),
+    city: getOptionName(searchCityOptions.value, searchForm.cityId),
+  };
+
+  if (searchForm.entryDateStart) {
+    params.entryDateStart = searchForm.entryDateStart;
+  }
+  if (searchForm.entryDateEnd) {
+    params.entryDateEnd = searchForm.entryDateEnd;
+  }
+
+  Object.keys(params).forEach((key) => {
+    if (params[key] === "" || params[key] === undefined || params[key] === null) {
+      delete params[key];
+    }
+  });
+
+  return params;
+};
+
+const getList = async () => {
+  tableLoading.value = true;
+  try {
+    const res = await bidWinningLedgerListPage(buildQueryParams());
+    tableData.value = res?.data?.records || [];
+    total.value = res?.data?.total || 0;
+  } finally {
+    tableLoading.value = false;
+  }
+};
+
+const loadProvinceOptions = async () => {
+  const res = await getProvinceList();
+  provinceOptions.value = res?.data || [];
+};
+
+const loadCityOptions = async (provinceId, target = "form") => {
+  if (!provinceId) {
+    if (target === "form") {
+      cityOptions.value = [];
+    } else {
+      searchCityOptions.value = [];
+    }
+    return [];
+  }
+  const res = await getCityList({ provinceId });
+  const list = res?.data || [];
+  if (target === "form") {
+    cityOptions.value = list;
+  } else {
+    searchCityOptions.value = list;
+  }
+  return list;
+};
+
+const loadUserOptions = async () => {
+  const res = await userListNoPage();
+  userOptions.value = res?.data || [];
+};
+
+const loadCustomerOptions = async () => {
+  const res = await customerList();
+  customerOptions.value = res || [];
+};
+
+const changeDateRange = (value) => {
+  if (value?.length === 2) {
+    searchForm.entryDateStart = value[0];
+    searchForm.entryDateEnd = value[1];
+  } else {
+    searchForm.entryDateStart = "";
+    searchForm.entryDateEnd = "";
+  }
+  handleQuery();
+};
+
+const handleSearchProvinceChange = async () => {
+  searchForm.cityId = "";
+  await loadCityOptions(searchForm.provinceId, "search");
+  handleQuery();
+};
+
+const handleFormProvinceChange = async () => {
+  form.cityId = "";
+  await loadCityOptions(form.provinceId, "form");
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const resetQuery = async () => {
+  Object.assign(searchForm, createSearchForm());
+  searchCityOptions.value = [];
+  page.current = 1;
+  await getList();
+};
+
+const paginationChange = ({ page: current, limit }) => {
+  page.current = current;
+  page.size = limit;
+  getList();
+};
+
+const handleSelectionChange = (rows) => {
+  selectedRows.value = rows;
+};
+
+const resetFormState = () => {
+  Object.assign(form, createFormData());
+  cityOptions.value = [];
+  fileList.value = [];
+  formRef.value?.clearValidate();
+};
+
+const mapNameToId = (options, name) => {
+  const target = options.find((item) => item.name === name);
+  return target?.id || "";
+};
+
+const openDialog = async (mode, row) => {
+  dialogMode.value = mode;
+  resetFormState();
+  await Promise.all([loadUserOptions(), loadProvinceOptions(), loadCustomerOptions()]);
+
+  if (mode === "add") {
+    dialogVisible.value = true;
+    return;
+  }
+
+  const res = await bidWinningLedgerDetail({ id: row.id });
+  const detail = res?.data || {};
+
+  Object.assign(form, detail, {
+    entryDate: detail.entryDate ? dayjs(detail.entryDate).format("YYYY-MM-DD") : "",
+    commonFiles: detail.commonFiles || [],
+  });
+
+  form.provinceId = mapNameToId(provinceOptions.value, detail.province);
+  await loadCityOptions(form.provinceId, "form");
+  form.cityId = mapNameToId(cityOptions.value, detail.city);
+  fileList.value = (detail.commonFiles || []).map((item) => ({
+    name: item.name,
+    url: item.url,
+    id: item.id,
+  }));
+  dialogVisible.value = true;
+};
+
+const handleAdd = () => {
+  openDialog("add");
+};
+
+const handleEdit = (row) => {
+  openDialog("edit", row);
+};
+
+const handleDetail = (row) => {
+  openDialog("detail", row);
+};
+
+const buildSubmitData = () => {
+  const tempFileIds = fileList.value
+    .map((item) => item.tempId)
+    .filter((item) => item !== undefined && item !== null && item !== "");
+
+  return {
+    id: form.id,
+    province: getOptionName(provinceOptions.value, form.provinceId),
+    city: getOptionName(cityOptions.value, form.cityId),
+    customerName: form.customerName,
+    contractAmount: form.contractAmount,
+    bidBond: form.bidBond,
+    winningServiceFee: form.winningServiceFee,
+    remark: form.remark,
+    entryPerson: form.entryPerson,
+    entryDate: form.entryDate,
+    tempFileIds,
+  };
+};
+
+const submitForm = () => {
+  formRef.value.validate(async (valid) => {
+    if (!valid) {
+      return;
+    }
+
+    const api = dialogMode.value === "edit" ? bidWinningLedgerUpdate : bidWinningLedgerAdd;
+    await api(buildSubmitData());
+    proxy.$modal.msgSuccess(dialogMode.value === "edit" ? "淇敼鎴愬姛" : "鏂板鎴愬姛");
+    closeDialog();
+    getList();
+  });
+};
+
+const handleDelete = (row) => {
+  const ids = row ? [row.id] : selectedRows.value.map((item) => item.id);
+  if (!ids.length) {
+    proxy.$modal.msgWarning("璇烽�夋嫨瑕佸垹闄ょ殑涓爣鍙拌处");
+    return;
+  }
+
+  ElMessageBox.confirm("纭鍒犻櫎閫変腑鐨勪腑鏍囧彴璐﹀悧锛�", "鎻愮ず", {
+    confirmButtonText: "纭畾",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(async () => {
+      await bidWinningLedgerDelete(ids);
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+      getList();
+    })
+    .catch(() => {});
+};
+
+const closeDialog = () => {
+  dialogVisible.value = false;
+  resetFormState();
+};
+
+const handleBeforeUpload = () => {
+  proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+  return true;
+};
+
+const handleUploadSuccess = (res, file) => {
+  proxy.$modal.closeLoading();
+  if (res.code === 200) {
+    file.tempId = res.data?.tempId;
+    proxy.$modal.msgSuccess("涓婁紶鎴愬姛");
+  } else {
+    proxy.$modal.msgError(res.msg || "涓婁紶澶辫触");
+    fileUploadRef.value?.handleRemove(file);
+  }
+};
+
+const handleUploadError = () => {
+  proxy.$modal.closeLoading();
+  proxy.$modal.msgError("涓婁紶澶辫触");
+};
+
+const handleRemove = () => {};
+
+const downloadFile = (row) => {
+  if (row?.url) {
+    proxy.$download.name(row.url);
+  }
+};
+
+const previewFile = (row) => {
+  if (row?.url) {
+    filePreviewRef.value.open(row.url);
+  }
+};
+
+onMounted(async () => {
+  await Promise.all([loadProvinceOptions(), loadUserOptions(), loadCustomerOptions()]);
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.table_list {
+  margin-top: unset;
+}
+
+.dialog-footer {
+  text-align: right;
+}
+
+:deep(.el-input-number) {
+  width: 100%;
+}
+</style>
diff --git a/src/views/salesManagement/opportunityManagement/index.vue b/src/views/salesManagement/opportunityManagement/index.vue
index 8547ffb..900f5ec 100644
--- a/src/views/salesManagement/opportunityManagement/index.vue
+++ b/src/views/salesManagement/opportunityManagement/index.vue
@@ -456,7 +456,9 @@
 const userList = ref([])
 const customerOption = ref([])
 const DEFAULT_USER_QUERY = { postCode: 'Market_Sales' }
+const DEFAULT_CUSTOMER_QUERY = { customerType: 2 }
 let userListPromise = null
+let customerListPromise = null
 
 const loadUserList = async (query = DEFAULT_USER_QUERY) => {
   if (userListPromise) return userListPromise
@@ -473,6 +475,23 @@
     }
   })()
   return userListPromise
+}
+
+const loadCustomerList = async (query = DEFAULT_CUSTOMER_QUERY) => {
+  if (customerListPromise) return customerListPromise
+  customerListPromise = (async () => {
+    try {
+      const res = await customerList(query)
+      customerOption.value = res || []
+      return customerOption.value
+    } catch (err) {
+      console.error('鑾峰彇瀹㈡埛鍒楄〃澶辫触:', err)
+      customerOption.value = []
+      customerListPromise = null
+      throw err
+    }
+  })()
+  return customerListPromise
 }
 
 // 鍒嗛〉閰嶇疆
@@ -703,9 +722,7 @@
   
   // 鍔犺浇鐢ㄦ埛鍒楄〃鍜屽鎴峰垪琛�
   await loadUserList()
-  customerList().then((res) => {
-    customerOption.value = res
-  })
+  await loadCustomerList()
 	getProvinceList().then(res => {
 		provinceOptions.value = res.data
 	})
@@ -724,9 +741,7 @@
   
   // 鍔犺浇鐢ㄦ埛鍒楄〃鍜屽鎴峰垪琛�
   await loadUserList()
-  customerList().then((res) => {
-    customerOption.value = res
-  })
+  await loadCustomerList()
   
   // 浣跨敤褰撳墠琛屾暟鎹綔涓哄熀纭�锛屼絾鍙兘淇敼鐘舵�佸拰鎷滆璁板綍锛涗粯娆炬弿杩般�佹敼閫犲唴瀹圭瓑淇濈暀鍙嶆樉
   Object.assign(form, row, {
@@ -745,9 +760,7 @@
   
   // 鍔犺浇鐢ㄦ埛鍒楄〃鍜屽鎴峰垪琛�
   await loadUserList()
-  customerList().then((res) => {
-    customerOption.value = res
-  })
+  await loadCustomerList()
   
   // 浣跨敤updateTime浣滀负褰曞叆鏃堕棿鍙嶆樉
   Object.assign(form, row, {
@@ -787,9 +800,7 @@
   
   // 鍔犺浇鐢ㄦ埛鍒楄〃鍜屽鎴峰垪琛�
   await loadUserList()
-  customerList().then((res) => {
-    customerOption.value = res
-  })
+  await loadCustomerList()
   
   // 鍔犺浇鐪佷唤鍒楄〃
   await getProvinceList().then(res => {
@@ -1162,4 +1173,4 @@
     }
   }
 }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3