From 009ccd0ee69766908a681adadaef22be0598b9a2 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 19 八月 2026 11:20:59 +0800
Subject: [PATCH] 天津-豹鸣 1.招投标管理、合同管理、诉讼管理、宽带与通信管理、房屋租赁管理前端页面开发

---
 src/api/collaborativeApproval/houseLeaseManagement.js            |   38 
 src/views/salesManagement/biddingManagement/index.vue            |  524 +++++++++
 src/api/salesManagement/biddingManagement.js                     |   38 
 src/views/salesManagement/lawsuitManagement/index.vue            |  488 +++++++++
 src/api/salesManagement/lawsuitManagement.js                     |   38 
 src/views/salesManagement/contractManagement/index.vue           |  805 +++++++++++++++
 src/views/collaborativeApproval/houseLeaseManagement/index.vue   |  638 ++++++++++++
 vite.config.js                                                   |    2 
 src/api/salesManagement/contractManagement.js                    |   47 
 src/api/collaborativeApproval/broadbandCommunication.js          |   38 
 src/views/collaborativeApproval/broadbandCommunication/index.vue |  481 +++++++++
 11 files changed, 3,136 insertions(+), 1 deletions(-)

diff --git a/src/api/collaborativeApproval/broadbandCommunication.js b/src/api/collaborativeApproval/broadbandCommunication.js
new file mode 100644
index 0000000..ccbf8a4
--- /dev/null
+++ b/src/api/collaborativeApproval/broadbandCommunication.js
@@ -0,0 +1,38 @@
+// 瀹藉甫涓庨�氫俊绠$悊椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function broadbandListPage(query) {
+  return request({
+    url: "/broadband/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏌ヨ璇︽儏
+export function getBroadbandDetail(query) {
+  return request({
+    url: "/broadband/getDetail",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板銆佷慨鏀�
+export function addOrUpdateBroadband(data) {
+  return request({
+    url: "/broadband/addOrUpdate",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎
+export function delBroadband(data) {
+  return request({
+    url: "/broadband/del",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/api/collaborativeApproval/houseLeaseManagement.js b/src/api/collaborativeApproval/houseLeaseManagement.js
new file mode 100644
index 0000000..dc4da83
--- /dev/null
+++ b/src/api/collaborativeApproval/houseLeaseManagement.js
@@ -0,0 +1,38 @@
+// 鎴垮眿绉熻祦绠$悊椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function houseLeaseListPage(query) {
+  return request({
+    url: "/houseLease/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏌ヨ璇︽儏
+export function getHouseLeaseDetail(query) {
+  return request({
+    url: "/houseLease/getDetail",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板銆佷慨鏀�
+export function addOrUpdateHouseLease(data) {
+  return request({
+    url: "/houseLease/addOrUpdate",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎
+export function delHouseLease(data) {
+  return request({
+    url: "/houseLease/del",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/api/salesManagement/biddingManagement.js b/src/api/salesManagement/biddingManagement.js
new file mode 100644
index 0000000..2cc5c28
--- /dev/null
+++ b/src/api/salesManagement/biddingManagement.js
@@ -0,0 +1,38 @@
+// 鎷涙姇鏍囩鐞嗛〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function biddingListPage(query) {
+  return request({
+    url: "/sales/bidding/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏌ヨ璇︽儏
+export function getBiddingDetail(query) {
+  return request({
+    url: "/sales/bidding/getDetail",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板銆佷慨鏀�
+export function addOrUpdateBidding(data) {
+  return request({
+    url: "/sales/bidding/addOrUpdate",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎
+export function delBidding(data) {
+  return request({
+    url: "/sales/bidding/del",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/api/salesManagement/contractManagement.js b/src/api/salesManagement/contractManagement.js
new file mode 100644
index 0000000..2d1994c
--- /dev/null
+++ b/src/api/salesManagement/contractManagement.js
@@ -0,0 +1,47 @@
+// 鍚堝悓绠$悊椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function contractListPage(query) {
+  return request({
+    url: "/sales/contract/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鍚堝悓涓嶅垎椤垫煡璇紙涓嬫媺閫夋嫨鐢級
+export function contractListNoPage(query) {
+  return request({
+    url: "/sales/contract/listNoPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏌ヨ鍚堝悓璇︽儏
+export function getContractDetail(query) {
+  return request({
+    url: "/sales/contract/getDetail",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板銆佷慨鏀瑰悎鍚�
+export function addOrUpdateContract(data) {
+  return request({
+    url: "/sales/contract/addOrUpdate",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎鍚堝悓
+export function delContract(data) {
+  return request({
+    url: "/sales/contract/del",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/api/salesManagement/lawsuitManagement.js b/src/api/salesManagement/lawsuitManagement.js
new file mode 100644
index 0000000..98b47be
--- /dev/null
+++ b/src/api/salesManagement/lawsuitManagement.js
@@ -0,0 +1,38 @@
+// 璇夎绠$悊椤甸潰鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ
+export function lawsuitListPage(query) {
+  return request({
+    url: "/sales/lawsuit/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏌ヨ璇夎璇︽儏
+export function getLawsuitDetail(query) {
+  return request({
+    url: "/sales/lawsuit/getDetail",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板銆佷慨鏀硅瘔璁艰褰�
+export function addOrUpdateLawsuit(data) {
+  return request({
+    url: "/sales/lawsuit/addOrUpdate",
+    method: "post",
+    data,
+  });
+}
+
+// 鍒犻櫎璇夎璁板綍
+export function delLawsuit(data) {
+  return request({
+    url: "/sales/lawsuit/del",
+    method: "delete",
+    data,
+  });
+}
diff --git a/src/views/collaborativeApproval/broadbandCommunication/index.vue b/src/views/collaborativeApproval/broadbandCommunication/index.vue
new file mode 100644
index 0000000..e090cc7
--- /dev/null
+++ b/src/views/collaborativeApproval/broadbandCommunication/index.vue
@@ -0,0 +1,481 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="鍖哄煙锛�">
+          <el-input v-model="searchForm.area" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鍔炰簨澶勶細">
+          <el-input v-model="searchForm.office" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="椹荤渷浜哄憳锛�">
+          <el-input v-model="searchForm.residentStaff" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="棰勮鐘舵�侊細">
+          <el-select v-model="searchForm.warnStatus" placeholder="璇烽�夋嫨" clearable style="width: 140px">
+            <el-option label="姝e父" value="normal" />
+            <el-option label="鍗冲皢鍒版湡" value="expiring" />
+            <el-option label="宸查�炬湡" value="overdue" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetSearch">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <div class="actions">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="openForm('add')">鏂板瀹藉甫</el-button>
+          <el-button type="primary" plain @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </div>
+      </div>
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" type="selection" width="55" fixed="left" />
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" fixed="left" />
+        <el-table-column label="鍖哄煙" prop="area" width="100" show-overflow-tooltip />
+        <el-table-column label="鍔炰簨澶�" prop="office" width="120" show-overflow-tooltip />
+        <el-table-column label="椹荤渷浜哄憳" prop="residentStaff" width="100" show-overflow-tooltip />
+        <el-table-column label="鍦板潃" prop="address" min-width="180" show-overflow-tooltip />
+        <el-table-column label="瀹夎鏃ユ湡" prop="installDate" width="110" show-overflow-tooltip />
+        <el-table-column label="瀹夎璐�(鍏�)" prop="installFee" width="110" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="缂磋垂鍛ㄦ湡" prop="paymentCycleText" width="90" align="center" />
+        <el-table-column label="缂磋垂鏂瑰紡" prop="paymentMethod" width="100" align="center" />
+        <el-table-column label="棰勫瓨(鍏�)" prop="prepaidAmount" width="110" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="璧勮垂(鍏�)" prop="monthlyFee" width="100" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="濂楅鍐呭" prop="packageContent" min-width="160" show-overflow-tooltip />
+        <el-table-column label="涓嬫缂磋垂鏃ユ湡" prop="nextPaymentDate" width="120" align="center">
+          <template #default="scope">
+            <span :class="{ 'warn-date': isExpiring(scope.row), 'overdue-date': isOverdue(scope.row) }">
+              {{ scope.row.nextPaymentDate || "-" }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="棰勮鐘舵��" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="getWarnStatus(scope.row).type">{{ getWarnStatus(scope.row).text }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openForm('view', scope.row)">璇︽儏</el-button>
+            <el-button link type="primary" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+            <el-button link type="danger" @click="handleRowDelete(scope.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>
+
+    <!-- 鏂板/缂栬緫/璇︽儏寮圭獥 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="
+        operationType === 'add'
+          ? '鏂板瀹藉甫'
+          : operationType === 'edit'
+          ? '缂栬緫瀹藉甫'
+          : '瀹藉甫璇︽儏'
+      "
+      :width="'60%'"
+      :operation-type="operationType"
+      @close="closeDialog"
+      @confirm="submitForm"
+      @cancel="closeDialog"
+    >
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px" label-position="top">
+        <el-divider content-position="left">鍩虹淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鍖哄煙锛�" prop="area">
+              <el-input v-model="form.area" placeholder="璇疯緭鍏ュ尯鍩�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍔炰簨澶勶細" prop="office">
+              <el-input v-model="form.office" placeholder="璇疯緭鍏ュ姙浜嬪" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="椹荤渷浜哄憳锛�" prop="residentStaff">
+              <el-input v-model="form.residentStaff" placeholder="璇疯緭鍏ラ┗鐪佷汉鍛�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="16">
+            <el-form-item label="鍦板潃锛�" prop="address">
+              <el-input v-model="form.address" placeholder="璇疯緭鍏ュ湴鍧�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="瀹夎鏃ユ湡锛�" prop="installDate">
+              <el-date-picker
+                v-model="form.installDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+                @change="calcNextPaymentDate"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">璐圭敤涓庣即璐�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="瀹夎璐�(鍏�)锛�">
+              <el-input-number v-model="form.installFee" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="缂磋垂鍛ㄦ湡锛�" prop="paymentCycle">
+              <el-select v-model="form.paymentCycle" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'" @change="calcNextPaymentDate">
+                <el-option v-for="item in paymentCycleOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="缂磋垂鏂瑰紡锛�" prop="paymentMethod">
+              <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'">
+                <el-option v-for="item in paymentMethodOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="棰勫瓨(鍏�)锛�">
+              <el-input-number v-model="form.prepaidAmount" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璧勮垂(鍏�)锛�">
+              <el-input-number v-model="form.monthlyFee" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="涓嬫缂磋垂鏃ユ湡锛�" prop="nextPaymentDate">
+              <el-date-picker
+                v-model="form.nextPaymentDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="鑷姩璁$畻"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">濂楅淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="濂楅鍐呭锛�">
+              <el-input v-model="form.packageContent" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ椁愬唴瀹�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  broadbandListPage,
+  getBroadbandDetail,
+  addOrUpdateBroadband,
+  delBroadband,
+} from "@/api/collaborativeApproval/broadbandCommunication.js";
+
+const { proxy } = getCurrentInstance();
+
+// 缂磋垂鍛ㄦ湡閫夐」锛堟湀鏁帮級
+const paymentCycleOptions = [
+  { label: "鏈堜粯", value: 1 },
+  { label: "瀛d粯", value: 3 },
+  { label: "鍗婂勾浠�", value: 6 },
+  { label: "骞翠粯", value: 12 },
+];
+
+// 缂磋垂鏂瑰紡閫夐」
+const paymentMethodOptions = [
+  { label: "鐜伴噾", value: "鐜伴噾" },
+  { label: "閾惰杞处", value: "閾惰杞处" },
+  { label: "鏀粯瀹�", value: "鏀粯瀹�" },
+  { label: "寰俊", value: "寰俊" },
+];
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const selectedRows = ref([]);
+const page = reactive({ current: 1, size: 10 });
+
+const searchForm = reactive({
+  area: "",
+  office: "",
+  residentStaff: "",
+  warnStatus: "",
+});
+
+const operationType = ref("");
+const dialogVisible = ref(false);
+const currentId = ref("");
+const formRef = ref();
+
+const form = reactive({
+  area: "",
+  office: "",
+  residentStaff: "",
+  address: "",
+  installDate: "",
+  installFee: undefined,
+  paymentCycle: undefined,
+  paymentMethod: "",
+  prepaidAmount: undefined,
+  monthlyFee: undefined,
+  packageContent: "",
+  nextPaymentDate: "",
+});
+
+const rules = {
+  area: [{ required: true, message: "璇疯緭鍏ュ尯鍩�", trigger: "blur" }],
+  office: [{ required: true, message: "璇疯緭鍏ュ姙浜嬪", trigger: "blur" }],
+  residentStaff: [{ required: true, message: "璇疯緭鍏ラ┗鐪佷汉鍛�", trigger: "blur" }],
+  address: [{ required: true, message: "璇疯緭鍏ュ湴鍧�", trigger: "blur" }],
+  installDate: [{ required: true, message: "璇烽�夋嫨瀹夎鏃ユ湡", trigger: "change" }],
+  paymentCycle: [{ required: true, message: "璇烽�夋嫨缂磋垂鍛ㄦ湡", trigger: "change" }],
+  paymentMethod: [{ required: true, message: "璇烽�夋嫨缂磋垂鏂瑰紡", trigger: "change" }],
+};
+
+const formatAmount = (val) => {
+  if (val === undefined || val === null || val === "") return "0.00";
+  const num = Number(val);
+  return Number.isNaN(num) ? "0.00" : num.toFixed(2);
+};
+const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
+
+// 缂磋垂鍛ㄦ湡鏂囨湰鏄犲皠
+const getPaymentCycleText = (cycle) => {
+  const item = paymentCycleOptions.find((i) => Number(i.value) === Number(cycle));
+  return item ? item.label : cycle || "-";
+};
+
+// 涓嬫缂磋垂鏃ユ湡鑷姩璁$畻 = 瀹夎鏃ユ湡 + 缂磋垂鍛ㄦ湡
+const calcNextPaymentDate = () => {
+  if (form.installDate && form.paymentCycle) {
+    form.nextPaymentDate = dayjs(form.installDate)
+      .add(Number(form.paymentCycle), "month")
+      .format("YYYY-MM-DD");
+  }
+};
+
+// 鍓╀綑澶╂暟锛堜笅娆$即璐规棩鏈� - 浠婂ぉ锛�
+const getRemainingDays = (row) => {
+  if (!row.nextPaymentDate) return null;
+  return dayjs(row.nextPaymentDate).startOf("day").diff(dayjs().startOf("day"), "day");
+};
+
+const isOverdue = (row) => {
+  const days = getRemainingDays(row);
+  return days !== null && days < 0;
+};
+
+const isExpiring = (row) => {
+  const days = getRemainingDays(row);
+  return days !== null && days >= 0 && days <= 15;
+};
+
+// 棰勮鐘舵�侊細姝e父 / 鍗冲皢鍒版湡锛�15澶╁唴锛�/ 宸查�炬湡
+const getWarnStatus = (row) => {
+  const days = getRemainingDays(row);
+  if (days === null) return { text: "-", type: "info" };
+  if (days < 0) return { text: `宸查�炬湡${Math.abs(days)}澶ー, type: "danger" };
+  if (days <= 15) return { text: `鍗冲皢鍒版湡(${days}澶�)`, type: "warning" };
+  return { text: "姝e父", type: "success" };
+};
+
+const resetSearch = () => {
+  searchForm.area = "";
+  searchForm.office = "";
+  searchForm.residentStaff = "";
+  searchForm.warnStatus = "";
+  page.current = 1;
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const params = { ...searchForm, current: page.current, size: page.size };
+  return broadbandListPage(params)
+    .then((res) => {
+      tableLoading.value = false;
+      const list = res.records || res.data?.records || [];
+      // 鍓嶇璁$畻缂磋垂鍛ㄦ湡鏂囨湰锛屼緵鍒楄〃灞曠ず
+      tableData.value = list.map((item) => ({
+        ...item,
+        paymentCycleText: getPaymentCycleText(item.paymentCycle),
+      }));
+      total.value = res.total ?? res.data?.total ?? 0;
+    })
+    .catch(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const resetFormData = () => {
+  Object.keys(form).forEach((key) => {
+    form[key] = "";
+  });
+  form.installFee = undefined;
+  form.paymentCycle = undefined;
+  form.prepaidAmount = undefined;
+  form.monthlyFee = undefined;
+};
+
+const openForm = (type, row) => {
+  operationType.value = type;
+  resetFormData();
+  if (type === "add") {
+    dialogVisible.value = true;
+  } else {
+    currentId.value = row.id;
+    getBroadbandDetail({ id: row.id }).then((res) => {
+      const detail = res.data || res;
+      Object.keys(form).forEach((key) => {
+        if (detail[key] !== undefined && detail[key] !== null) {
+          form[key] = detail[key];
+        }
+      });
+      dialogVisible.value = true;
+    });
+  }
+};
+
+const submitForm = () => {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (!valid) return;
+    addOrUpdateBroadband(form).then(() => {
+      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+      closeDialog();
+      getList();
+    });
+  });
+};
+
+const closeDialog = () => {
+  proxy.resetForm("formRef");
+  dialogVisible.value = false;
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delBroadband(ids).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleRowDelete = (row) => {
+  ElMessageBox.confirm("璇ュ甯﹁褰曞皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delBroadband([row.id]).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleExport = () => {
+  proxy.download("/broadband/export", { ...searchForm }, "瀹藉甫涓庨�氫俊绠$悊.xlsx");
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  margin-bottom: 10px;
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+
+.warn-date {
+  color: #e6a23c;
+  font-weight: 600;
+}
+
+.overdue-date {
+  color: #f56c6c;
+  font-weight: 600;
+}
+</style>
diff --git a/src/views/collaborativeApproval/houseLeaseManagement/index.vue b/src/views/collaborativeApproval/houseLeaseManagement/index.vue
new file mode 100644
index 0000000..89ded11
--- /dev/null
+++ b/src/views/collaborativeApproval/houseLeaseManagement/index.vue
@@ -0,0 +1,638 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="鍔炰簨澶勶細">
+          <el-input v-model="searchForm.office" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="浣跨敤浜猴細">
+          <el-input v-model="searchForm.userName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鎵跨浜猴細">
+          <el-input v-model="searchForm.tenant" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鍚堝悓鐢叉柟锛�">
+          <el-input v-model="searchForm.partyA" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="棰勮鐘舵�侊細">
+          <el-select v-model="searchForm.warnStatus" placeholder="璇烽�夋嫨" clearable style="width: 140px">
+            <el-option label="姝e父" value="normal" />
+            <el-option label="鍗冲皢鍒版湡" value="expiring" />
+            <el-option label="宸查�炬湡" value="overdue" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetSearch">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <div class="actions">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="openForm('add')">鏂板绉熻祦</el-button>
+          <el-button type="primary" plain @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </div>
+      </div>
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" type="selection" width="55" fixed="left" />
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" fixed="left" />
+        <el-table-column label="鍔炰簨澶�" prop="office" width="120" show-overflow-tooltip />
+        <el-table-column label="浣跨敤浜�" prop="userName" width="100" show-overflow-tooltip />
+        <el-table-column label="椹荤渷浜哄憳" prop="residentStaff" width="100" show-overflow-tooltip />
+        <el-table-column label="鍦板潃" prop="address" min-width="180" show-overflow-tooltip />
+        <el-table-column label="璧风鏃堕棿" prop="rentStartDate" width="110" show-overflow-tooltip />
+        <el-table-column label="鍚堝悓璧风鏃�" prop="contractStartDate" width="110" show-overflow-tooltip />
+        <el-table-column label="鍚堝悓鍒版湡鏃�" prop="contractEndDate" width="120" align="center">
+          <template #default="scope">
+            <span :class="{ 'warn-date': isExpiring(scope.row), 'overdue-date': isOverdue(scope.row) }">
+              {{ scope.row.contractEndDate || "-" }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎵跨浜�" prop="tenant" width="100" show-overflow-tooltip />
+        <el-table-column label="鍚堝悓鐢叉柟" prop="partyA" width="120" show-overflow-tooltip />
+        <el-table-column label="鐢佃瘽" prop="phone" width="120" show-overflow-tooltip />
+        <el-table-column label="闈㈢Н(銕�)" prop="area" width="100" align="right" />
+        <el-table-column label="鏀粯鏂瑰紡" prop="paymentMethod" width="100" align="center" />
+        <el-table-column label="鎶奸噾(鍏�)" prop="deposit" width="110" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="绉熼噾(鍏�/鏈�)" prop="rent" width="110" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="棰勮鐘舵��" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="getWarnStatus(scope.row).type">{{ getWarnStatus(scope.row).text }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openForm('view', scope.row)">璇︽儏</el-button>
+            <el-button link type="primary" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+            <el-button link type="danger" @click="handleRowDelete(scope.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>
+
+    <!-- 鏂板/缂栬緫/璇︽儏寮圭獥 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="
+        operationType === 'add'
+          ? '鏂板绉熻祦'
+          : operationType === 'edit'
+          ? '缂栬緫绉熻祦'
+          : '绉熻祦璇︽儏'
+      "
+      :width="'70%'"
+      :operation-type="operationType"
+      @close="closeDialog"
+      @confirm="submitForm"
+      @cancel="closeDialog"
+    >
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px" label-position="top">
+        <el-divider content-position="left">绉熻祦淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鍔炰簨澶勶細" prop="office">
+              <el-input v-model="form.office" placeholder="璇疯緭鍏ュ姙浜嬪" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="浣跨敤浜猴細" prop="userName">
+              <el-input v-model="form.userName" placeholder="璇疯緭鍏ヤ娇鐢ㄤ汉" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="椹荤渷浜哄憳锛�" prop="residentStaff">
+              <el-input v-model="form.residentStaff" placeholder="璇疯緭鍏ラ┗鐪佷汉鍛�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="16">
+            <el-form-item label="鍦板潃锛�" prop="address">
+              <el-input v-model="form.address" placeholder="璇疯緭鍏ュ湴鍧�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璧风鏃堕棿锛�" prop="rentStartDate">
+              <el-date-picker
+                v-model="form.rentStartDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鍚堝悓淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓璧风鏃ワ細" prop="contractStartDate">
+              <el-date-picker
+                v-model="form.contractStartDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+                @change="generateCostMonths"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓鍒版湡鏃ワ細" prop="contractEndDate">
+              <el-date-picker
+                v-model="form.contractEndDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+                @change="generateCostMonths"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎵跨浜猴細" prop="tenant">
+              <el-input v-model="form.tenant" placeholder="璇疯緭鍏ユ壙绉熶汉" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓鐢叉柟锛�" prop="partyA">
+              <el-input v-model="form.partyA" placeholder="璇疯緭鍏ュ悎鍚岀敳鏂�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鐢佃瘽锛�">
+              <el-input v-model="form.phone" placeholder="璇疯緭鍏ョ數璇�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="闈㈢Н(銕�)锛�">
+              <el-input-number v-model="form.area" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鏀粯鏂瑰紡锛�" prop="paymentMethod">
+              <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'">
+                <el-option v-for="item in paymentMethodOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎶奸噾(鍏�)锛�">
+              <el-input-number v-model="form.deposit" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="绉熼噾(鍏�/鏈�)锛�" prop="rent">
+              <el-input-number v-model="form.rent" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" @change="syncRentToCost" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鎸夋湀褰掗泦璐圭敤锛堟埧绉� + 姘寸數 + 鐗╀笟 + 蹇�� + 瀹藉甫锛�</el-divider>
+        <el-table :data="costList" border size="small" style="width: 100%" max-height="360">
+          <el-table-column label="鏈堜唤" prop="month" width="110" align="center" />
+          <el-table-column label="鎴跨(鍏�)" width="120" align="center">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.rent"
+                :min="0"
+                :precision="2"
+                :step="0.01"
+                controls-position="right"
+                size="small"
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="姘寸數(鍏�)" width="120" align="center">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.waterElectricity"
+                :min="0"
+                :precision="2"
+                :step="0.01"
+                controls-position="right"
+                size="small"
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="鐗╀笟(鍏�)" width="120" align="center">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.property"
+                :min="0"
+                :precision="2"
+                :step="0.01"
+                controls-position="right"
+                size="small"
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="蹇��(鍏�)" width="120" align="center">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.express"
+                :min="0"
+                :precision="2"
+                :step="0.01"
+                controls-position="right"
+                size="small"
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="瀹藉甫(鍏�)" width="120" align="center">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.broadband"
+                :min="0"
+                :precision="2"
+                :step="0.01"
+                controls-position="right"
+                size="small"
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="鏈堝悎璁�(鍏�)" width="110" align="right">
+            <template #default="scope">{{ formatAmount(calcRowTotal(scope.row)) }}</template>
+          </el-table-column>
+        </el-table>
+        <div class="cost-summary">
+          <span>绱锛氭埧绉� {{ formatAmount(sumCost('rent')) }} 鍏�</span>
+          <span>姘寸數 {{ formatAmount(sumCost('waterElectricity')) }} 鍏�</span>
+          <span>鐗╀笟 {{ formatAmount(sumCost('property')) }} 鍏�</span>
+          <span>蹇�� {{ formatAmount(sumCost('express')) }} 鍏�</span>
+          <span>瀹藉甫 {{ formatAmount(sumCost('broadband')) }} 鍏�</span>
+          <span class="cost-total">鍚堣 {{ formatAmount(sumCostTotal()) }} 鍏�</span>
+        </div>
+      </el-form>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  houseLeaseListPage,
+  getHouseLeaseDetail,
+  addOrUpdateHouseLease,
+  delHouseLease,
+} from "@/api/collaborativeApproval/houseLeaseManagement.js";
+
+const { proxy } = getCurrentInstance();
+
+// 鏀粯鏂瑰紡閫夐」
+const paymentMethodOptions = [
+  { label: "鐜伴噾", value: "鐜伴噾" },
+  { label: "閾惰杞处", value: "閾惰杞处" },
+  { label: "鏀粯瀹�", value: "鏀粯瀹�" },
+  { label: "寰俊", value: "寰俊" },
+];
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const selectedRows = ref([]);
+const page = reactive({ current: 1, size: 10 });
+
+const searchForm = reactive({
+  office: "",
+  userName: "",
+  tenant: "",
+  partyA: "",
+  warnStatus: "",
+});
+
+const operationType = ref("");
+const dialogVisible = ref(false);
+const currentId = ref("");
+const formRef = ref();
+const costList = ref([]);
+
+const form = reactive({
+  office: "",
+  userName: "",
+  residentStaff: "",
+  address: "",
+  rentStartDate: "",
+  contractStartDate: "",
+  contractEndDate: "",
+  tenant: "",
+  partyA: "",
+  phone: "",
+  area: undefined,
+  paymentMethod: "",
+  deposit: undefined,
+  rent: undefined,
+});
+
+const rules = {
+  office: [{ required: true, message: "璇疯緭鍏ュ姙浜嬪", trigger: "blur" }],
+  userName: [{ required: true, message: "璇疯緭鍏ヤ娇鐢ㄤ汉", trigger: "blur" }],
+  residentStaff: [{ required: true, message: "璇疯緭鍏ラ┗鐪佷汉鍛�", trigger: "blur" }],
+  address: [{ required: true, message: "璇疯緭鍏ュ湴鍧�", trigger: "blur" }],
+  rentStartDate: [{ required: true, message: "璇烽�夋嫨璧风鏃堕棿", trigger: "change" }],
+  contractStartDate: [{ required: true, message: "璇烽�夋嫨鍚堝悓璧风鏃�", trigger: "change" }],
+  contractEndDate: [{ required: true, message: "璇烽�夋嫨鍚堝悓鍒版湡鏃�", trigger: "change" }],
+  tenant: [{ required: true, message: "璇疯緭鍏ユ壙绉熶汉", trigger: "blur" }],
+  partyA: [{ required: true, message: "璇疯緭鍏ュ悎鍚岀敳鏂�", trigger: "blur" }],
+  paymentMethod: [{ required: true, message: "璇烽�夋嫨鏀粯鏂瑰紡", trigger: "change" }],
+  rent: [{ required: true, message: "璇疯緭鍏ョ閲�", trigger: "change" }],
+};
+
+const formatAmount = (val) => {
+  if (val === undefined || val === null || val === "") return "0.00";
+  const num = Number(val);
+  return Number.isNaN(num) ? "0.00" : num.toFixed(2);
+};
+const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
+
+// 鎸夋湀褰掗泦锛氭牴鎹悎鍚岃捣绉熸棩鍒板悎鍚屽埌鏈熸棩鐢熸垚鏈堜唤琛�
+const generateCostMonths = () => {
+  if (!form.contractStartDate || !form.contractEndDate) return;
+  const start = dayjs(form.contractStartDate);
+  const end = dayjs(form.contractEndDate);
+  if (end.isBefore(start)) return;
+
+  const months = [];
+  let cursor = start.startOf("month");
+  while (cursor.isBefore(end) || cursor.isSame(end, "month")) {
+    months.push(cursor.format("YYYY-MM"));
+    cursor = cursor.add(1, "month");
+  }
+
+  // 淇濈暀宸插~鍐欑殑鏁版嵁锛屼粎鍚屾鏈堜唤鑼冨洿
+  const oldMap = {};
+  costList.value.forEach((item) => {
+    oldMap[item.month] = item;
+  });
+
+  costList.value = months.map((m) => {
+    const old = oldMap[m];
+    return {
+      month: m,
+      rent: old && old.rent !== undefined && old.rent !== null ? old.rent : form.rent,
+      waterElectricity: old?.waterElectricity ?? undefined,
+      property: old?.property ?? undefined,
+      express: old?.express ?? undefined,
+      broadband: old?.broadband ?? undefined,
+    };
+  });
+};
+
+// 淇敼绉熼噾鏃跺悓姝ュ埌鍚勬湀鐨勬埧绉燂紙鑻ヨ鏈堟埧绉熶负绌烘垨绛変簬鏃у�硷級
+const syncRentToCost = () => {
+  costList.value.forEach((item) => {
+    if (item.rent === undefined || item.rent === null || item.rent === "") {
+      item.rent = form.rent;
+    }
+  });
+};
+
+const calcRowTotal = (row) =>
+  (Number(row.rent) || 0) +
+  (Number(row.waterElectricity) || 0) +
+  (Number(row.property) || 0) +
+  (Number(row.express) || 0) +
+  (Number(row.broadband) || 0);
+
+const sumCost = (key) => costList.value.reduce((acc, item) => acc + (Number(item[key]) || 0), 0);
+const sumCostTotal = () => costList.value.reduce((acc, item) => acc + calcRowTotal(item), 0);
+
+// 鍓╀綑澶╂暟锛堝悎鍚屽埌鏈熸棩 - 浠婂ぉ锛�
+const getRemainingDays = (row) => {
+  if (!row.contractEndDate) return null;
+  return dayjs(row.contractEndDate).startOf("day").diff(dayjs().startOf("day"), "day");
+};
+
+const isOverdue = (row) => {
+  const days = getRemainingDays(row);
+  return days !== null && days < 0;
+};
+
+const isExpiring = (row) => {
+  const days = getRemainingDays(row);
+  return days !== null && days >= 0 && days <= 30;
+};
+
+// 棰勮鐘舵�侊細姝e父 / 鍗冲皢鍒版湡锛�30澶╁唴锛�/ 宸查�炬湡
+const getWarnStatus = (row) => {
+  const days = getRemainingDays(row);
+  if (days === null) return { text: "-", type: "info" };
+  if (days < 0) return { text: `宸查�炬湡${Math.abs(days)}澶ー, type: "danger" };
+  if (days <= 30) return { text: `鍗冲皢鍒版湡(${days}澶�)`, type: "warning" };
+  return { text: "姝e父", type: "success" };
+};
+
+const resetSearch = () => {
+  searchForm.office = "";
+  searchForm.userName = "";
+  searchForm.tenant = "";
+  searchForm.partyA = "";
+  searchForm.warnStatus = "";
+  page.current = 1;
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const params = { ...searchForm, current: page.current, size: page.size };
+  return houseLeaseListPage(params)
+    .then((res) => {
+      tableLoading.value = false;
+      tableData.value = res.records || res.data?.records || [];
+      total.value = res.total ?? res.data?.total ?? 0;
+    })
+    .catch(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const resetFormData = () => {
+  Object.keys(form).forEach((key) => {
+    form[key] = "";
+  });
+  form.area = undefined;
+  form.deposit = undefined;
+  form.rent = undefined;
+  costList.value = [];
+};
+
+const openForm = (type, row) => {
+  operationType.value = type;
+  resetFormData();
+  if (type === "add") {
+    dialogVisible.value = true;
+  } else {
+    currentId.value = row.id;
+    getHouseLeaseDetail({ id: row.id }).then((res) => {
+      const detail = res.data || res;
+      Object.keys(form).forEach((key) => {
+        if (detail[key] !== undefined && detail[key] !== null) {
+          form[key] = detail[key];
+        }
+      });
+      costList.value = detail.costList || detail.costVOs || [];
+      dialogVisible.value = true;
+    });
+  }
+};
+
+const submitForm = () => {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (!valid) return;
+    form.costList = costList.value;
+    addOrUpdateHouseLease(form).then(() => {
+      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+      closeDialog();
+      getList();
+    });
+  });
+};
+
+const closeDialog = () => {
+  proxy.resetForm("formRef");
+  dialogVisible.value = false;
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delHouseLease(ids).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleRowDelete = (row) => {
+  ElMessageBox.confirm("璇ョ璧佽褰曞皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delHouseLease([row.id]).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleExport = () => {
+  proxy.download("/houseLease/export", { ...searchForm }, "鎴垮眿绉熻祦绠$悊.xlsx");
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  margin-bottom: 10px;
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+
+.warn-date {
+  color: #e6a23c;
+  font-weight: 600;
+}
+
+.overdue-date {
+  color: #f56c6c;
+  font-weight: 600;
+}
+
+.cost-summary {
+  margin-top: 12px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  justify-content: flex-end;
+  font-size: 13px;
+  color: #606266;
+
+  .cost-total {
+    font-weight: 600;
+    color: #f56c6c;
+  }
+}
+</style>
diff --git a/src/views/salesManagement/biddingManagement/index.vue b/src/views/salesManagement/biddingManagement/index.vue
new file mode 100644
index 0000000..310d0c5
--- /dev/null
+++ b/src/views/salesManagement/biddingManagement/index.vue
@@ -0,0 +1,524 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="椤圭洰鍚嶇О锛�">
+          <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鎷涙爣鍗曚綅锛�">
+          <el-input v-model="searchForm.tenderUnit" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鎶曟爣鏃ユ湡锛�">
+          <el-date-picker
+            v-model="searchForm.bidDateRange"
+            value-format="YYYY-MM-DD"
+            format="YYYY-MM-DD"
+            type="daterange"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            clearable
+            @change="changeBidDateRange"
+          />
+        </el-form-item>
+        <el-form-item label="鎷涙姇鏍囩粨鏋滐細">
+          <el-select v-model="searchForm.bidResult" placeholder="璇烽�夋嫨" clearable style="width: 160px">
+            <el-option v-for="item in bidResultOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetSearch">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <div class="actions">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="openForm('add')">鏂板鎷涙姇鏍�</el-button>
+          <el-button type="primary" plain @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </div>
+      </div>
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" type="selection" width="55" fixed="left" />
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" fixed="left" />
+        <el-table-column label="鎵�灞為」鐩�" prop="projectName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鏍囦功鍚嶇О" prop="bidDocName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鎷涙爣鍗曚綅" prop="tenderUnit" width="160" show-overflow-tooltip />
+        <el-table-column label="鎶曟爣鍗曚綅" prop="bidUnit" width="160" show-overflow-tooltip />
+        <el-table-column label="鎷涙爣鏃ユ湡" prop="tenderDate" width="110" show-overflow-tooltip />
+        <el-table-column label="鎶曟爣鏃ユ湡" prop="bidDate" width="110" show-overflow-tooltip />
+        <el-table-column label="鏍囦功娆鹃噾棰�(鍏�)" prop="bidDocFee" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="鏍囦功娆剧姸鎬�" prop="bidDocFeeStatus" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getBidDocFeeStatusType(scope.row.bidDocFeeStatus)">
+              {{ scope.row.bidDocFeeStatus || "-" }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎶曟爣淇濊瘉閲�(鍏�)" prop="bidDeposit" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="鎷涙姇鏍囩粨鏋�" prop="bidResult" width="140" align="center">
+          <template #default="scope">
+            <el-tag :type="getBidResultType(scope.row.bidResult)">
+              {{ scope.row.bidResult || "-" }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" width="220" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openForm('view', scope.row)">璇︽儏</el-button>
+            <el-button link type="primary" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+            <el-button link type="danger" @click="handleRowDelete(scope.row)">鍒犻櫎</el-button>
+            <el-button v-if="isWon(scope.row)" link type="success" @click="handleStartContract(scope.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>
+
+    <!-- 鏂板/缂栬緫/璇︽儏寮圭獥 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="
+        operationType === 'add'
+          ? '鏂板鎷涙姇鏍�'
+          : operationType === 'edit'
+          ? '缂栬緫鎷涙姇鏍�'
+          : '鎷涙姇鏍囪鎯�'
+      "
+      :width="'60%'"
+      :operation-type="operationType"
+      @close="closeDialog"
+      @confirm="submitForm"
+      @cancel="closeDialog"
+    >
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="160px" label-position="top">
+        <el-divider content-position="left">鍏宠仈椤圭洰</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="16">
+            <el-form-item label="鎵�灞為」鐩細" prop="projectId">
+              <el-select v-model="form.projectId" placeholder="璇烽�夋嫨椤圭洰" clearable filterable style="width: 100%" :disabled="operationType === 'view'" @change="handleProjectChange">
+                <el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鎷涙爣涓庢姇鏍囦俊鎭�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鏍囦功鍚嶇О锛�" prop="bidDocName">
+              <el-input v-model="form.bidDocName" placeholder="璇疯緭鍏ユ爣涔﹀悕绉�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎷涙爣鍗曚綅锛�" prop="tenderUnit">
+              <el-input v-model="form.tenderUnit" placeholder="璇疯緭鍏ユ嫑鏍囧崟浣�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎶曟爣鍗曚綅锛�" prop="bidUnit">
+              <el-input v-model="form.bidUnit" placeholder="璇疯緭鍏ユ姇鏍囧崟浣�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鎷涙爣鏃ユ湡锛�" prop="tenderDate">
+              <el-date-picker
+                v-model="form.tenderDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎶曟爣鏃ユ湡锛�" prop="bidDate">
+              <el-date-picker
+                v-model="form.bidDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鏍囦功娆句笌淇濊瘉閲�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鏍囦功娆鹃噾棰�(鍏�)锛�">
+              <el-input-number v-model="form.bidDocFee" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鏍囦功娆剧姸鎬侊細" prop="bidDocFeeStatus">
+              <el-select v-model="form.bidDocFeeStatus" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'" @change="handleBidDocFeeStatusChange">
+                <el-option v-for="item in bidDocFeeStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鏍囦功娆剧姸鎬佸彉鏇存棩鏈燂細">
+              <el-date-picker
+                v-model="form.bidDocFeeStatusChangeDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="鑷姩濉叆"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鎶曟爣淇濊瘉閲戦噾棰�(鍏�)锛�">
+              <el-input-number v-model="form.bidDeposit" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鎷涙姇鏍囩粨鏋�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鎷涙姇鏍囩粨鏋滐細" prop="bidResult">
+              <el-select v-model="form.bidResult" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'">
+                <el-option v-for="item in bidResultOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { listProject } from "@/api/projectManagement/project.js";
+import {
+  biddingListPage,
+  getBiddingDetail,
+  addOrUpdateBidding,
+  delBidding,
+} from "@/api/salesManagement/biddingManagement.js";
+
+const { proxy } = getCurrentInstance();
+
+// 鏍囦功娆剧姸鎬侀�夐」
+const bidDocFeeStatusOptions = [
+  { label: "鏈氦", value: "鏈氦" },
+  { label: "宸蹭氦", value: "宸蹭氦" },
+  { label: "宸查��", value: "宸查��" },
+];
+
+// 鎷涙姇鏍囩粨鏋滈�夐」
+const bidResultOptions = [
+  { label: "搴熸爣", value: "搴熸爣" },
+  { label: "娴佹爣", value: "娴佹爣" },
+  { label: "鏈腑鏍�", value: "鏈腑鏍�" },
+  { label: "涓爣寰呭嚭閫氱煡涔�", value: "涓爣寰呭嚭閫氱煡涔�" },
+  { label: "宸插嚭涓爣閫氱煡涔�", value: "宸插嚭涓爣閫氱煡涔�" },
+  { label: "宸茬鍚堝悓", value: "宸茬鍚堝悓" },
+];
+
+// 涓爣鐘舵�侊紙鍙彂璧峰悎鍚屽綍鍏ワ級
+const wonStatuses = ["涓爣寰呭嚭閫氱煡涔�", "宸插嚭涓爣閫氱煡涔�", "宸茬鍚堝悓"];
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const selectedRows = ref([]);
+const page = reactive({ current: 1, size: 10 });
+
+const searchForm = reactive({
+  projectName: "",
+  tenderUnit: "",
+  bidResult: "",
+  bidDateRange: null,
+  bidDateStart: undefined,
+  bidDateEnd: undefined,
+});
+
+const operationType = ref("");
+const dialogVisible = ref(false);
+const currentId = ref("");
+const formRef = ref();
+
+const form = reactive({
+  projectId: "",
+  projectName: "",
+  bidDocName: "",
+  tenderUnit: "",
+  bidUnit: "",
+  tenderDate: "",
+  bidDate: "",
+  bidDocFee: undefined,
+  bidDocFeeStatus: "",
+  bidDocFeeStatusChangeDate: "",
+  bidDeposit: undefined,
+  bidResult: "",
+});
+
+const rules = {
+  projectId: [{ required: true, message: "璇烽�夋嫨鎵�灞為」鐩�", trigger: "change" }],
+  bidDocName: [{ required: true, message: "璇疯緭鍏ユ爣涔﹀悕绉�", trigger: "blur" }],
+  tenderUnit: [{ required: true, message: "璇疯緭鍏ユ嫑鏍囧崟浣�", trigger: "blur" }],
+  bidUnit: [{ required: true, message: "璇疯緭鍏ユ姇鏍囧崟浣�", trigger: "blur" }],
+  tenderDate: [{ required: true, message: "璇烽�夋嫨鎷涙爣鏃ユ湡", trigger: "change" }],
+  bidDate: [{ required: true, message: "璇烽�夋嫨鎶曟爣鏃ユ湡", trigger: "change" }],
+  bidDocFeeStatus: [{ required: true, message: "璇烽�夋嫨鏍囦功娆剧姸鎬�", trigger: "change" }],
+  bidResult: [{ required: true, message: "璇烽�夋嫨鎷涙姇鏍囩粨鏋�", trigger: "change" }],
+};
+
+// 椤圭洰涓嬫媺
+const projectOptions = ref([]);
+
+const formatAmount = (val) => {
+  if (val === undefined || val === null || val === "") return "0.00";
+  const num = Number(val);
+  return Number.isNaN(num) ? "0.00" : num.toFixed(2);
+};
+const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
+
+const getBidDocFeeStatusType = (status) => {
+  const map = {
+    鏈氦: "warning",
+    宸蹭氦: "success",
+    宸查��: "info",
+  };
+  return map[status] || "info";
+};
+
+const getBidResultType = (status) => {
+  const map = {
+    搴熸爣: "info",
+    娴佹爣: "info",
+    鏈腑鏍�: "warning",
+    涓爣寰呭嚭閫氱煡涔�: "primary",
+    宸插嚭涓爣閫氱煡涔�: "success",
+    宸茬鍚堝悓: "success",
+  };
+  return map[status] || "info";
+};
+
+const isWon = (row) => wonStatuses.includes(row.bidResult);
+
+const initProjects = async () => {
+  try {
+    const res = await listProject({ pageSize: 1000 });
+    const rows = res?.data?.records || res?.rows || res?.records || [];
+    projectOptions.value = rows.map((p) => ({
+      label: p.title ?? p.projectName,
+      value: p.id,
+    }));
+  } catch (e) {
+    console.error("鍔犺浇椤圭洰鍒楄〃澶辫触", e);
+  }
+};
+
+const handleProjectChange = (val) => {
+  const project = projectOptions.value.find((item) => item.value === val);
+  form.projectName = project ? project.label : "";
+};
+
+// 鏍囦功娆剧姸鎬佸彉鍖栨椂鑷姩濉叆鍙樻洿鏃ユ湡
+const handleBidDocFeeStatusChange = (val) => {
+  if (val && !form.bidDocFeeStatusChangeDate) {
+    form.bidDocFeeStatusChangeDate = dayjs().format("YYYY-MM-DD");
+  }
+};
+
+const changeBidDateRange = (value) => {
+  if (value) {
+    searchForm.bidDateStart = value[0];
+    searchForm.bidDateEnd = value[1];
+  } else {
+    searchForm.bidDateStart = undefined;
+    searchForm.bidDateEnd = undefined;
+  }
+  handleQuery();
+};
+
+const resetSearch = () => {
+  searchForm.projectName = "";
+  searchForm.tenderUnit = "";
+  searchForm.bidResult = "";
+  searchForm.bidDateRange = null;
+  searchForm.bidDateStart = undefined;
+  searchForm.bidDateEnd = undefined;
+  page.current = 1;
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const { bidDateRange, ...rest } = searchForm;
+  const params = { ...rest, current: page.current, size: page.size };
+  delete params.bidDateRange;
+  return biddingListPage(params)
+    .then((res) => {
+      tableLoading.value = false;
+      tableData.value = res.records || res.data?.records || [];
+      total.value = res.total ?? res.data?.total ?? 0;
+    })
+    .catch(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const resetFormData = () => {
+  Object.keys(form).forEach((key) => {
+    form[key] = "";
+  });
+  form.bidDocFee = undefined;
+  form.bidDeposit = undefined;
+};
+
+const openForm = async (type, row) => {
+  operationType.value = type;
+  resetFormData();
+  await initProjects();
+  if (type === "add") {
+    dialogVisible.value = true;
+  } else {
+    currentId.value = row.id;
+    getBiddingDetail({ id: row.id }).then((res) => {
+      const detail = res.data || res;
+      Object.keys(form).forEach((key) => {
+        if (detail[key] !== undefined && detail[key] !== null) {
+          form[key] = detail[key];
+        }
+      });
+      dialogVisible.value = true;
+    });
+  }
+};
+
+const submitForm = () => {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (!valid) return;
+    addOrUpdateBidding(form).then(() => {
+      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+      closeDialog();
+      getList();
+    });
+  });
+};
+
+const closeDialog = () => {
+  proxy.resetForm("formRef");
+  dialogVisible.value = false;
+};
+
+// 涓爣鍚庡彂璧烽攢鍞悎鍚屽綍鍏ワ細璺宠浆鍚堝悓绠$悊椤碉紝鎼哄甫椤圭洰涓庢嫑鏍囧崟浣嶄俊鎭�
+const handleStartContract = (row) => {
+  proxy.$tab.openPage("鍙戣捣閿�鍞悎鍚屽綍鍏�", "/salesManagement/contractManagement", {
+    projectId: row.projectId,
+    projectName: row.projectName,
+    signingUnit: row.tenderUnit,
+  });
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delBidding(ids).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleRowDelete = (row) => {
+  ElMessageBox.confirm("璇ユ嫑鎶曟爣璁板綍灏嗚鍒犻櫎锛屾槸鍚︾‘璁ゅ垹闄わ紵", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delBidding([row.id]).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleExport = () => {
+  proxy.download("/sales/bidding/export", { ...searchForm }, "鎷涙姇鏍囩鐞�.xlsx");
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  margin-bottom: 10px;
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+</style>
diff --git a/src/views/salesManagement/contractManagement/index.vue b/src/views/salesManagement/contractManagement/index.vue
new file mode 100644
index 0000000..1ed0cc4
--- /dev/null
+++ b/src/views/salesManagement/contractManagement/index.vue
@@ -0,0 +1,805 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="鍚堝悓缂栧彿锛�">
+          <el-input
+            v-model="searchForm.contractNo"
+            placeholder="璇疯緭鍏�"
+            clearable
+            prefix-icon="Search"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="绛剧害鍗曚綅绠�绉帮細">
+          <el-input
+            v-model="searchForm.signingUnit"
+            placeholder="璇疯緭鍏�"
+            clearable
+            prefix-icon="Search"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="瀛樻。鐘舵�侊細">
+          <el-select
+            v-model="searchForm.archiveStatus"
+            placeholder="璇烽�夋嫨"
+            clearable
+            style="width: 160px"
+          >
+            <el-option
+              v-for="item in archiveStatusOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="绛捐鏃堕棿锛�">
+          <el-date-picker
+            v-model="searchForm.signDateRange"
+            value-format="YYYY-MM-DD"
+            format="YYYY-MM-DD"
+            type="daterange"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            clearable
+            @change="changeSignDateRange"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetSearch">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <div class="actions">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="openForm('add')">鏂板鍚堝悓</el-button>
+          <el-button type="primary" plain @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </div>
+      </div>
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" type="selection" width="55" fixed="left" />
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" fixed="left" />
+        <el-table-column label="鍚堝悓缂栧彿" prop="contractNo" width="160" show-overflow-tooltip />
+        <el-table-column label="绛捐鏃堕棿" prop="signDate" width="110" show-overflow-tooltip />
+        <el-table-column label="绛剧害鍗曚綅绠�绉�" prop="signingUnit" min-width="160" show-overflow-tooltip />
+        <el-table-column label="绛剧害鍛ㄦ湡" prop="contractPeriod" width="120" show-overflow-tooltip />
+        <el-table-column label="瀛樻。鐘舵��" prop="archiveStatus" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="getArchiveStatusType(scope.row.archiveStatus)">
+              {{ scope.row.archiveStatus || "-" }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="灞ョ害淇濊瘉閲�(鍏�)" prop="performanceBond" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="璐ㄤ繚閲戞儏鍐�" prop="warrantyDeposit" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="浜у��(鍏�)" prop="outputValue" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="宸插洖娆鹃噾棰�(鍏�)" prop="receivedAmount" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="缁撶畻娆℃暟" prop="settlementCount" width="90" align="center" />
+        <el-table-column label="搴旀敹閲戦(鍏�)" prop="receivableAmount" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="搴斾粯姣斾緥" prop="payableRatio" width="100" align="center" />
+        <el-table-column label="鏈�杩戜竴娆″紑绁ㄦ椂闂�" prop="lastInvoiceDate" width="130" show-overflow-tooltip />
+        <el-table-column label="宸插紑绁ㄩ噾棰�(鍏�)" prop="invoicedAmount" width="130" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="鎯呭喌璇存槑" prop="remark" min-width="160" show-overflow-tooltip />
+        <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openForm('view', scope.row)">璇︽儏</el-button>
+            <el-button link type="primary" @click="openForm('edit', scope.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>
+
+    <!-- 鏂板/缂栬緫/璇︽儏寮圭獥 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="
+        operationType === 'add'
+          ? '鏂板鍚堝悓'
+          : operationType === 'edit'
+          ? '缂栬緫鍚堝悓'
+          : '鍚堝悓璇︽儏'
+      "
+      :width="'78%'"
+      :operation-type="operationType"
+      @close="closeDialog"
+      @confirm="submitForm"
+      @cancel="closeDialog"
+    >
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px" label-position="top">
+        <el-divider content-position="left">鍚堝悓鍩烘湰淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓缂栧彿锛�" prop="contractNo">
+              <el-input v-model="form.contractNo" placeholder="璇疯緭鍏ュ悎鍚岀紪鍙�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="绛捐鏃堕棿锛�" prop="signDate">
+              <el-date-picker
+                v-model="form.signDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="绛剧害鍗曚綅绠�绉帮細" prop="signingUnit">
+              <el-input v-model="form.signingUnit" placeholder="璇疯緭鍏ョ绾﹀崟浣嶇畝绉�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="绛剧害鍛ㄦ湡锛�" prop="contractPeriod">
+              <el-input v-model="form.contractPeriod" placeholder="渚嬪锛�1骞� / 2024-01-01 鑷� 2025-12-31" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="瀛樻。鐘舵�侊細" prop="archiveStatus">
+              <el-select v-model="form.archiveStatus" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'">
+                <el-option v-for="item in archiveStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="瀛樻。鎯呭喌璇存槑锛�" prop="archiveRemark">
+              <el-input v-model="form.archiveRemark" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">浜у搧淇℃伅</el-divider>
+        <div v-if="operationType !== 'view'" style="margin-bottom: 10px">
+          <el-button type="primary" plain @click="openProductForm('add')">娣诲姞浜у搧</el-button>
+        </div>
+        <el-table :data="form.productList" border show-summary :summary-method="summarizeProducts">
+          <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+          <el-table-column label="浜у搧澶х被" prop="productCategory" min-width="140" show-overflow-tooltip />
+          <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" min-width="140" show-overflow-tooltip />
+          <el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" width="130" align="right">
+            <template #default="scope">{{ formatAmount(scope.row.taxInclusiveUnitPrice) }}</template>
+          </el-table-column>
+          <el-table-column label="绾﹀畾/鏆備及鐢ㄩ噺" prop="agreedQuantity" width="130" align="right" />
+          <el-table-column label="鍚堝悓璁¢噺鍗曚綅" prop="contractUnit" width="120" align="center" />
+          <el-table-column label="宸插彂璐ф暟閲�" prop="shippedQuantity" width="120" align="right" />
+          <el-table-column label="浜у��(鍏�)" prop="outputValue" width="130" align="right">
+            <template #default="scope">{{ formatAmount(productOutputValue(scope.row)) }}</template>
+          </el-table-column>
+          <el-table-column v-if="operationType !== 'view'" label="鎿嶄綔" width="120" align="center">
+            <template #default="scope">
+              <el-button link type="primary" @click="openProductForm('edit', scope.row, scope.$index)">缂栬緫</el-button>
+              <el-button link type="danger" @click="removeProduct(scope.$index)">鍒犻櫎</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <el-divider content-position="left">灞ョ害涓庤川淇�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="灞ョ害淇濊瘉閲�(鍏�)锛�">
+              <el-input-number v-model="form.performanceBond" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璐ㄤ繚閲戞儏鍐�(鍏�)锛�">
+              <el-input-number v-model="form.warrantyDeposit" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="浜у��(鍏�)锛堣嚜鍔ㄦ眹鎬伙級锛�">
+              <el-input :model-value="formatAmount(computedOutputValue)" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鍥炴涓庣粨绠�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="宸插洖娆鹃噾棰�(鍏�)锛�">
+              <el-input-number v-model="form.receivedAmount" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="缁撶畻娆℃暟锛�">
+              <el-input-number v-model="form.settlementCount" :min="0" :precision="0" :step="1" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="搴旀敹閲戦(鍏�)锛�">
+              <el-input-number v-model="form.receivableAmount" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="搴斾粯姣斾緥锛�">
+              <el-input v-model="form.payableRatio" placeholder="渚嬪锛�30% 鎴� 0.3" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓鑺傜偣搴旀敹(鍏�)锛�">
+              <el-input-number v-model="form.nodeReceivable" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍚堝悓绾﹀畾浠樻鑺傜偣锛�">
+              <el-input v-model="form.paymentNodes" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="缁撶畻璁板綍锛�">
+              <el-input v-model="form.settlementRecord" type="textarea" :rows="3" placeholder="璇疯緭鍏�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">寮�绁ㄤ笌杩濈害</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="寮�绁ㄨ姹傦細">
+              <el-input v-model="form.invoiceRequirement" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鏈�杩戜竴娆″紑绁ㄦ椂闂达細">
+              <el-date-picker
+                v-model="form.lastInvoiceDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="宸插紑绁ㄩ噾棰�(鍏�)锛�">
+              <el-input-number v-model="form.invoicedAmount" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="杩濈害閲戠害瀹氾細">
+              <el-input v-model="form.penaltyAgreement" type="textarea" :rows="2" placeholder="璇疯緭鍏�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎯呭喌璇存槑锛�">
+              <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </FormDialog>
+
+    <!-- 娣诲姞/缂栬緫浜у搧寮圭獥 -->
+    <FormDialog
+      v-model="productDialogVisible"
+      :title="productOperationType === 'add' ? '娣诲姞浜у搧' : '缂栬緫浜у搧'"
+      :width="'40%'"
+      @close="closeProductDialog"
+      @confirm="submitProduct"
+      @cancel="closeProductDialog"
+    >
+      <el-form :model="productForm" :rules="productRules" ref="productFormRef" label-width="140px" label-position="top">
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="浜у搧澶х被锛�" prop="categoryId">
+              <el-tree-select
+                v-model="productForm.categoryId"
+                placeholder="璇烽�夋嫨"
+                clearable
+                filterable
+                check-strictly
+                :data="productOptions"
+                :render-after-expand="false"
+                style="width: 100%"
+                @change="getModels"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
+              <el-select
+                v-model="productForm.productModelId"
+                placeholder="璇烽�夋嫨"
+                clearable
+                filterable
+                style="width: 100%"
+                @change="getProductModel"
+              >
+                <el-option
+                  v-for="item in modelOptions"
+                  :key="item.id"
+                  :label="item.model"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="鍚堝悓璁¢噺鍗曚綅锛�">
+              <el-input v-model="productForm.contractUnit" placeholder="璇疯緭鍏�" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍚◣鍗曚环(鍏�)锛�">
+              <el-input-number v-model="productForm.taxInclusiveUnitPrice" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="绾﹀畾/鏆備及鐢ㄩ噺锛�">
+              <el-input-number v-model="productForm.agreedQuantity" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="宸插彂璐ф暟閲忥細">
+              <el-input-number v-model="productForm.shippedQuantity" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, toRefs, getCurrentInstance, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { productTreeList, modelList } from "@/api/basicData/product.js";
+import {
+  contractListPage,
+  getContractDetail,
+  addOrUpdateContract,
+  delContract,
+} from "@/api/salesManagement/contractManagement.js";
+
+const { proxy } = getCurrentInstance();
+
+// 瀛樻。鐘舵�侀�夐」
+const archiveStatusOptions = [
+  { label: "宸插瓨妗�", value: "宸插瓨妗�" },
+  { label: "宸茬敤绔犳湭瀵勫洖", value: "宸茬敤绔犳湭瀵勫洖" },
+  { label: "鏈绾�", value: "鏈绾�" },
+];
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const selectedRows = ref([]);
+const page = reactive({ current: 1, size: 10 });
+
+const searchForm = reactive({
+  contractNo: "",
+  signingUnit: "",
+  archiveStatus: "",
+  signDateRange: null,
+  signDateStart: undefined,
+  signDateEnd: undefined,
+});
+
+const operationType = ref("");
+const dialogVisible = ref(false);
+const currentId = ref("");
+
+const formRef = ref();
+const form = reactive({
+  contractNo: "",
+  signDate: "",
+  signingUnit: "",
+  contractPeriod: "",
+  archiveStatus: "",
+  archiveRemark: "",
+  productList: [],
+  performanceBond: undefined,
+  warrantyDeposit: undefined,
+  outputValue: 0,
+  receivedAmount: undefined,
+  settlementCount: 0,
+  receivableAmount: undefined,
+  payableRatio: "",
+  nodeReceivable: undefined,
+  paymentNodes: "",
+  settlementRecord: "",
+  invoiceRequirement: "",
+  lastInvoiceDate: "",
+  invoicedAmount: undefined,
+  penaltyAgreement: "",
+  remark: "",
+});
+
+const rules = {
+  contractNo: [{ required: true, message: "璇疯緭鍏ュ悎鍚岀紪鍙�", trigger: "blur" }],
+  signDate: [{ required: true, message: "璇烽�夋嫨绛捐鏃堕棿", trigger: "change" }],
+  signingUnit: [{ required: true, message: "璇疯緭鍏ョ绾﹀崟浣嶇畝绉�", trigger: "blur" }],
+  archiveStatus: [{ required: true, message: "璇烽�夋嫨瀛樻。鐘舵��", trigger: "change" }],
+};
+
+// 浜у搧寮圭獥鐩稿叧
+const productDialogVisible = ref(false);
+const productOperationType = ref("");
+const productIndex = ref(-1);
+const productOptions = ref([]);
+const modelOptions = ref([]);
+const productFormRef = ref();
+const productFormData = reactive({
+  productForm: {
+    categoryId: "",
+    productCategory: "",
+    productModelId: "",
+    specificationModel: "",
+    contractUnit: "",
+    taxInclusiveUnitPrice: undefined,
+    agreedQuantity: undefined,
+    shippedQuantity: undefined,
+  },
+  productRules: {
+    categoryId: [{ required: true, message: "璇烽�夋嫨浜у搧澶х被", trigger: "change" }],
+    productModelId: [{ required: true, message: "璇烽�夋嫨瑙勬牸鍨嬪彿", trigger: "change" }],
+  },
+});
+const { productForm, productRules } = toRefs(productFormData);
+
+// 閲戦鏍煎紡鍖�
+const formatAmount = (val) => {
+  if (val === undefined || val === null || val === "") return "0.00";
+  const num = Number(val);
+  return Number.isNaN(num) ? "0.00" : num.toFixed(2);
+};
+const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
+
+// 鍗曚釜浜у搧浜у�� = 鍚◣鍗曚环 * 宸插彂璐ф暟閲�
+const productOutputValue = (row) => {
+  const price = Number(row?.taxInclusiveUnitPrice || 0);
+  const qty = Number(row?.shippedQuantity || 0);
+  return price * qty;
+};
+
+// 鍚堝悓浜у�艰嚜鍔ㄦ眹鎬�
+const computedOutputValue = computed(() => {
+  return (form.productList || []).reduce((sum, row) => sum + productOutputValue(row), 0);
+});
+
+const getArchiveStatusType = (status) => {
+  const map = {
+    宸插瓨妗�: "success",
+    宸茬敤绔犳湭瀵勫洖: "warning",
+    鏈绾�: "info",
+  };
+  return map[status] || "info";
+};
+
+// 浜у搧琛ㄥ悎璁�
+const summarizeProducts = (param) => {
+  const { columns, data } = param;
+  return columns.map((col, index) => {
+    if (index === 0) return "鍚堣";
+    if (["agreedQuantity", "shippedQuantity"].includes(col.property)) {
+      return (data || []).reduce((sum, row) => sum + Number(row[col.property] || 0), 0).toFixed(2);
+    }
+    if (col.property === "outputValue") {
+      return (data || []).reduce((sum, row) => sum + productOutputValue(row), 0).toFixed(2);
+    }
+    return "";
+  });
+};
+
+// 鑾峰彇浜у搧澶х被鏍�
+const getProductOptions = () => {
+  return productTreeList().then((res) => {
+    productOptions.value = convertIdToValue(res);
+    return productOptions.value;
+  });
+};
+
+// 閫夋嫨浜у搧澶х被鍚庡姞杞借鏍煎瀷鍙�
+const getModels = (value) => {
+  productForm.value.productCategory = findNodeById(productOptions.value, value);
+  productForm.value.productModelId = "";
+  productForm.value.specificationModel = "";
+  productForm.value.contractUnit = "";
+  modelList({ id: value }).then((res) => {
+    modelOptions.value = res;
+  });
+};
+
+// 閫夋嫨瑙勬牸鍨嬪彿鍚庡甫鍑哄崟浣�
+const getProductModel = (value) => {
+  const index = modelOptions.value.findIndex((item) => item.id === value);
+  if (index !== -1) {
+    productForm.value.specificationModel = modelOptions.value[index].model;
+    productForm.value.contractUnit = modelOptions.value[index].unit;
+  } else {
+    productForm.value.specificationModel = "";
+    productForm.value.contractUnit = "";
+  }
+};
+
+function convertIdToValue(data) {
+  return (data || []).map((item) => {
+    const { id, children, ...rest } = item;
+    const newItem = { ...rest, value: id };
+    if (children && children.length > 0) {
+      newItem.children = convertIdToValue(children);
+    }
+    return newItem;
+  });
+}
+
+function findNodeById(nodes, productId) {
+  for (let i = 0; i < nodes.length; i++) {
+    if (nodes[i].value === productId) return nodes[i].label;
+    if (nodes[i].children && nodes[i].children.length > 0) {
+      const found = findNodeById(nodes[i].children, productId);
+      if (found) return found;
+    }
+  }
+  return null;
+}
+
+function findNodeIdByLabel(nodes, label) {
+  if (!label) return null;
+  for (let i = 0; i < nodes.length; i++) {
+    const node = nodes[i];
+    if (node.label === label) return node.value;
+    if (node.children && node.children.length > 0) {
+      const found = findNodeIdByLabel(node.children, label);
+      if (found !== null && found !== undefined) return found;
+    }
+  }
+  return null;
+}
+
+// 鎵撳紑浜у搧寮圭獥
+const openProductForm = async (type, row, index) => {
+  productOperationType.value = type;
+  productIndex.value = index ?? -1;
+  proxy.resetForm("productFormRef");
+  Object.keys(productForm.value).forEach((key) => {
+    productForm.value[key] = key === "categoryId" || key === "productModelId" ? "" : key.includes("Price") || key.includes("Quantity") ? undefined : "";
+  });
+  if (type === "add") {
+    if (!productOptions.value.length) {
+      await getProductOptions();
+    }
+  } else {
+    productForm.value.productCategory = row.productCategory;
+    productForm.value.specificationModel = row.specificationModel;
+    productForm.value.contractUnit = row.contractUnit;
+    productForm.value.taxInclusiveUnitPrice = row.taxInclusiveUnitPrice;
+    productForm.value.agreedQuantity = row.agreedQuantity;
+    productForm.value.shippedQuantity = row.shippedQuantity;
+    try {
+      const options = productOptions.value.length ? productOptions.value : await getProductOptions();
+      const categoryId = findNodeIdByLabel(options, row.productCategory);
+      if (categoryId) {
+        productForm.value.categoryId = categoryId;
+        const models = await modelList({ id: categoryId });
+        modelOptions.value = models || [];
+        const currentModel = (modelOptions.value || []).find(
+          (m) => m.model === row.specificationModel
+        );
+        if (currentModel) productForm.value.productModelId = currentModel.id;
+      }
+    } catch (e) {
+      console.error("鍔犺浇浜у搧瑙勬牸鍨嬪彿澶辫触", e);
+    }
+  }
+  productDialogVisible.value = true;
+};
+
+const submitProduct = () => {
+  proxy.$refs["productFormRef"].validate((valid) => {
+    if (!valid) return;
+    const item = {
+      productCategory: productForm.value.productCategory,
+      specificationModel: productForm.value.specificationModel,
+      productModelId: productForm.value.productModelId,
+      contractUnit: productForm.value.contractUnit,
+      taxInclusiveUnitPrice: productForm.value.taxInclusiveUnitPrice,
+      agreedQuantity: productForm.value.agreedQuantity,
+      shippedQuantity: productForm.value.shippedQuantity,
+    };
+    if (productOperationType.value === "add") {
+      form.productList.push(item);
+    } else {
+      form.productList[productIndex.value] = item;
+    }
+    closeProductDialog();
+  });
+};
+
+const closeProductDialog = () => {
+  proxy.resetForm("productFormRef");
+  productDialogVisible.value = false;
+};
+
+const removeProduct = (index) => {
+  form.productList.splice(index, 1);
+};
+
+const changeSignDateRange = (value) => {
+  if (value) {
+    searchForm.signDateStart = value[0];
+    searchForm.signDateEnd = value[1];
+  } else {
+    searchForm.signDateStart = undefined;
+    searchForm.signDateEnd = undefined;
+  }
+  handleQuery();
+};
+
+const resetSearch = () => {
+  searchForm.contractNo = "";
+  searchForm.signingUnit = "";
+  searchForm.archiveStatus = "";
+  searchForm.signDateRange = null;
+  searchForm.signDateStart = undefined;
+  searchForm.signDateEnd = undefined;
+  page.current = 1;
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const { signDateRange, ...rest } = searchForm;
+  const params = { ...rest, current: page.current, size: page.size };
+  delete params.signDateRange;
+  return contractListPage(params)
+    .then((res) => {
+      tableLoading.value = false;
+      tableData.value = res.records || res.data?.records || [];
+      total.value = res.total ?? res.data?.total ?? 0;
+    })
+    .catch(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const resetFormData = () => {
+  Object.keys(form).forEach((key) => {
+    if (key === "productList") form[key] = [];
+    else if (key === "settlementCount") form[key] = 0;
+    else if (key === "outputValue") form[key] = 0;
+    else form[key] = "";
+  });
+};
+
+const openForm = (type, row) => {
+  operationType.value = type;
+  resetFormData();
+  if (type === "add") {
+    dialogVisible.value = true;
+  } else {
+    currentId.value = row.id;
+    getContractDetail({ id: row.id }).then((res) => {
+      const detail = res.data || res;
+      Object.keys(form).forEach((key) => {
+        if (key === "productList") {
+          form[key] = Array.isArray(detail[key]) ? detail[key] : [];
+        } else if (detail[key] !== undefined && detail[key] !== null) {
+          form[key] = detail[key];
+        }
+      });
+      dialogVisible.value = true;
+    });
+  }
+};
+
+const submitForm = () => {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (!valid) return;
+    if (!form.productList || form.productList.length === 0) {
+      proxy.$modal.msgWarning("璇疯嚦灏戞坊鍔犱竴鏉′骇鍝佷俊鎭�");
+      return;
+    }
+    form.outputValue = Number(computedOutputValue.value.toFixed(2));
+    addOrUpdateContract(form).then(() => {
+      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+      closeDialog();
+      getList();
+    });
+  });
+};
+
+const closeDialog = () => {
+  proxy.resetForm("formRef");
+  dialogVisible.value = false;
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delContract(ids).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleExport = () => {
+  proxy.download("/sales/contract/export", { ...searchForm }, "鍚堝悓绠$悊.xlsx");
+};
+
+onMounted(() => {
+  getList();
+  getProductOptions();
+});
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  margin-bottom: 10px;
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+</style>
diff --git a/src/views/salesManagement/lawsuitManagement/index.vue b/src/views/salesManagement/lawsuitManagement/index.vue
new file mode 100644
index 0000000..1cd39f2
--- /dev/null
+++ b/src/views/salesManagement/lawsuitManagement/index.vue
@@ -0,0 +1,488 @@
+<template>
+  <div class="app-container">
+    <div class="search_form">
+      <el-form :model="searchForm" :inline="true">
+        <el-form-item label="妗堜欢缂栧彿锛�">
+          <el-input v-model="searchForm.caseNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="椤圭洰鍚嶇О锛�">
+          <el-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="鍚堝悓缂栧彿锛�">
+          <el-input v-model="searchForm.contractNo" placeholder="璇疯緭鍏�" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="璇夎鐘舵�侊細">
+          <el-select v-model="searchForm.lawsuitStatus" placeholder="璇烽�夋嫨" clearable style="width: 140px">
+            <el-option v-for="item in lawsuitStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="绔嬫鏃堕棿锛�">
+          <el-date-picker
+            v-model="searchForm.filingDateRange"
+            value-format="YYYY-MM-DD"
+            format="YYYY-MM-DD"
+            type="daterange"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            clearable
+            @change="changeFilingDateRange"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button @click="resetSearch">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table_list">
+      <div class="actions">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="openForm('add')">鏂板璇夎</el-button>
+          <el-button type="primary" plain @click="handleExport">瀵煎嚭</el-button>
+          <el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
+        </div>
+      </div>
+      <el-table
+        :data="tableData"
+        border
+        v-loading="tableLoading"
+        @selection-change="handleSelectionChange"
+        style="width: 100%"
+        height="calc(100vh - 18.5em)"
+      >
+        <el-table-column align="center" type="selection" width="55" fixed="left" />
+        <el-table-column align="center" label="搴忓彿" type="index" width="60" fixed="left" />
+        <el-table-column label="妗堜欢缂栧彿" prop="caseNo" width="160" show-overflow-tooltip />
+        <el-table-column label="鎵�灞為」鐩�" prop="projectName" min-width="160" show-overflow-tooltip />
+        <el-table-column label="鍚堝悓缂栧彿" prop="contractNo" width="160" show-overflow-tooltip />
+        <el-table-column label="璇夎鐘舵��" prop="lawsuitStatus" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getLawsuitStatusType(scope.row.lawsuitStatus)">
+              {{ scope.row.lawsuitStatus || "-" }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="绔嬫鏃堕棿" prop="filingDate" width="110" show-overflow-tooltip />
+        <el-table-column label="鎵垮姙寰嬪笀/娉曞姟璐熻矗浜�" prop="lawyer" width="150" show-overflow-tooltip />
+        <el-table-column label="寮�搴椂闂�" prop="hearingDate" width="110" show-overflow-tooltip />
+        <el-table-column label="璇夎鏍囩殑閲戦(鍏�)" prop="claimAmount" width="140" align="right" :formatter="formatAmountColumn" />
+        <el-table-column label="鍒ゅ喅缁撴灉鎽樿" prop="judgmentSummary" min-width="180" show-overflow-tooltip />
+        <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openForm('view', scope.row)">璇︽儏</el-button>
+            <el-button link type="primary" @click="openForm('edit', scope.row)">缂栬緫</el-button>
+            <el-button link type="danger" @click="handleRowDelete(scope.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>
+
+    <!-- 鏂板/缂栬緫/璇︽儏寮圭獥 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="
+        operationType === 'add'
+          ? '鏂板璇夎'
+          : operationType === 'edit'
+          ? '缂栬緫璇夎'
+          : '璇夎璇︽儏'
+      "
+      :width="'60%'"
+      :operation-type="operationType"
+      @close="closeDialog"
+      @confirm="submitForm"
+      @cancel="closeDialog"
+    >
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="150px" label-position="top">
+        <el-divider content-position="left">妗堜欢淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="妗堜欢缂栧彿锛�" prop="caseNo">
+              <el-input v-model="form.caseNo" placeholder="璇疯緭鍏ユ浠剁紪鍙�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璇夎鐘舵�侊細" prop="lawsuitStatus">
+              <el-select v-model="form.lawsuitStatus" placeholder="璇烽�夋嫨" clearable style="width: 100%" :disabled="operationType === 'view'">
+                <el-option v-for="item in lawsuitStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璇夎鏍囩殑閲戦(鍏�)锛�">
+              <el-input-number v-model="form.claimAmount" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="8">
+            <el-form-item label="绔嬫鏃堕棿锛�" prop="filingDate">
+              <el-date-picker
+                v-model="form.filingDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="寮�搴椂闂达細">
+              <el-date-picker
+                v-model="form.hearingDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+                :disabled="operationType === 'view'"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鎵垮姙寰嬪笀/娉曞姟璐熻矗浜猴細">
+              <el-input v-model="form.lawyer" placeholder="璇疯緭鍏�" clearable :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鍏宠仈淇℃伅</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="鎵�灞為」鐩細" prop="projectId">
+              <el-select v-model="form.projectId" placeholder="璇烽�夋嫨椤圭洰" clearable filterable style="width: 100%" :disabled="operationType === 'view'" @change="handleProjectChange">
+                <el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍏宠仈閿�鍞悎鍚岋細" prop="contractId">
+              <el-select v-model="form.contractId" placeholder="璇烽�夋嫨鍚堝悓" clearable filterable style="width: 100%" :disabled="operationType === 'view'" @change="handleContractChange">
+                <el-option v-for="item in contractOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鍒ゅ喅涓庤繘灞�</el-divider>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="鍒ゅ喅缁撴灉鎽樿锛�">
+              <el-input v-model="form.judgmentSummary" type="textarea" :rows="3" placeholder="璇疯緭鍏�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="璇夎杩涘睍澶囨敞锛�">
+              <el-input v-model="form.progressRemark" type="textarea" :rows="3" placeholder="璇疯緭鍏�" :disabled="operationType === 'view'" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鐩稿叧闄勪欢</el-divider>
+        <el-form-item v-if="operationType !== 'view'" label="璧疯瘔鐘躲�佸垽鍐充功绛夛細">
+          <FileUpload v-model:file-list="fileList" :disabled="operationType === 'view'" />
+        </el-form-item>
+        <div v-else>
+          <FileUpload v-model:file-list="fileList" :disabled="true" />
+        </div>
+      </el-form>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, onMounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+import { listProject } from "@/api/projectManagement/project.js";
+import { contractListNoPage } from "@/api/salesManagement/contractManagement.js";
+import {
+  lawsuitListPage,
+  getLawsuitDetail,
+  addOrUpdateLawsuit,
+  delLawsuit,
+} from "@/api/salesManagement/lawsuitManagement.js";
+
+const { proxy } = getCurrentInstance();
+
+// 璇夎鐘舵�侀�夐」
+const lawsuitStatusOptions = [
+  { label: "绔嬫涓�", value: "绔嬫涓�" },
+  { label: "瀹$悊涓�", value: "瀹$悊涓�" },
+  { label: "宸插垽鍐�", value: "宸插垽鍐�" },
+  { label: "宸叉挙璇�", value: "宸叉挙璇�" },
+  { label: "宸插拰瑙�", value: "宸插拰瑙�" },
+];
+
+const tableData = ref([]);
+const tableLoading = ref(false);
+const total = ref(0);
+const selectedRows = ref([]);
+const page = reactive({ current: 1, size: 10 });
+
+const searchForm = reactive({
+  caseNo: "",
+  projectName: "",
+  contractNo: "",
+  lawsuitStatus: "",
+  filingDateRange: null,
+  filingDateStart: undefined,
+  filingDateEnd: undefined,
+});
+
+const operationType = ref("");
+const dialogVisible = ref(false);
+const currentId = ref("");
+const formRef = ref();
+const fileList = ref([]);
+
+const form = reactive({
+  caseNo: "",
+  projectId: "",
+  projectName: "",
+  contractId: "",
+  contractNo: "",
+  lawsuitStatus: "",
+  filingDate: "",
+  lawyer: "",
+  hearingDate: "",
+  claimAmount: undefined,
+  judgmentSummary: "",
+  progressRemark: "",
+});
+
+const rules = {
+  caseNo: [{ required: true, message: "璇疯緭鍏ユ浠剁紪鍙�", trigger: "blur" }],
+  lawsuitStatus: [{ required: true, message: "璇烽�夋嫨璇夎鐘舵��", trigger: "change" }],
+  filingDate: [{ required: true, message: "璇烽�夋嫨绔嬫鏃堕棿", trigger: "change" }],
+  projectId: [{ required: true, message: "璇烽�夋嫨鎵�灞為」鐩�", trigger: "change" }],
+  contractId: [{ required: true, message: "璇烽�夋嫨鍏宠仈閿�鍞悎鍚�", trigger: "change" }],
+};
+
+// 椤圭洰 / 閿�鍞悎鍚屼笅鎷�
+const projectOptions = ref([]);
+const contractOptions = ref([]);
+
+const formatAmount = (val) => {
+  if (val === undefined || val === null || val === "") return "0.00";
+  const num = Number(val);
+  return Number.isNaN(num) ? "0.00" : num.toFixed(2);
+};
+const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
+
+const getLawsuitStatusType = (status) => {
+  const map = {
+    绔嬫涓�: "warning",
+    瀹$悊涓�: "primary",
+    宸插垽鍐�: "success",
+    宸叉挙璇�: "info",
+    宸插拰瑙�: "success",
+  };
+  return map[status] || "info";
+};
+
+const initProjects = async () => {
+  try {
+    const res = await listProject({ pageSize: 1000 });
+    const rows = res?.rows || res?.data?.rows || [];
+    projectOptions.value = rows.map((p) => ({ label: p.projectName, value: p.id }));
+  } catch (e) {
+    console.error("鍔犺浇椤圭洰鍒楄〃澶辫触", e);
+  }
+};
+
+const initContracts = async () => {
+  try {
+    const res = await contractListNoPage({});
+    const list = Array.isArray(res) ? res : res?.data ?? res?.records ?? [];
+    contractOptions.value = list.map((item) => ({
+      label: item.contractNo + (item.signingUnit ? `锛�${item.signingUnit}锛塦 : ""),
+      value: item.id,
+      contractNo: item.contractNo,
+    }));
+  } catch (e) {
+    console.error("鍔犺浇鍚堝悓鍒楄〃澶辫触", e);
+  }
+};
+
+const handleProjectChange = (val) => {
+  const project = projectOptions.value.find((item) => item.value === val);
+  form.projectName = project ? project.label : "";
+};
+
+const handleContractChange = (val) => {
+  const contract = contractOptions.value.find((item) => item.value === val);
+  form.contractNo = contract ? contract.contractNo : "";
+};
+
+const changeFilingDateRange = (value) => {
+  if (value) {
+    searchForm.filingDateStart = value[0];
+    searchForm.filingDateEnd = value[1];
+  } else {
+    searchForm.filingDateStart = undefined;
+    searchForm.filingDateEnd = undefined;
+  }
+  handleQuery();
+};
+
+const resetSearch = () => {
+  searchForm.caseNo = "";
+  searchForm.projectName = "";
+  searchForm.contractNo = "";
+  searchForm.lawsuitStatus = "";
+  searchForm.filingDateRange = null;
+  searchForm.filingDateStart = undefined;
+  searchForm.filingDateEnd = undefined;
+  page.current = 1;
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const paginationChange = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  const { filingDateRange, ...rest } = searchForm;
+  const params = { ...rest, current: page.current, size: page.size };
+  delete params.filingDateRange;
+  return lawsuitListPage(params)
+    .then((res) => {
+      tableLoading.value = false;
+      tableData.value = res.records || res.data?.records || [];
+      total.value = res.total ?? res.data?.total ?? 0;
+    })
+    .catch(() => {
+      tableLoading.value = false;
+    });
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const resetFormData = () => {
+  Object.keys(form).forEach((key) => {
+    form[key] = "";
+  });
+  form.claimAmount = undefined;
+};
+
+const openForm = async (type, row) => {
+  operationType.value = type;
+  resetFormData();
+  fileList.value = [];
+  await Promise.all([initProjects(), initContracts()]);
+  if (type === "add") {
+    dialogVisible.value = true;
+  } else {
+    currentId.value = row.id;
+    getLawsuitDetail({ id: row.id }).then((res) => {
+      const detail = res.data || res;
+      Object.keys(form).forEach((key) => {
+        if (detail[key] !== undefined && detail[key] !== null) {
+          form[key] = detail[key];
+        }
+      });
+      fileList.value = detail.storageBlobVOs || detail.attachmentList || [];
+      dialogVisible.value = true;
+    });
+  }
+};
+
+const submitForm = () => {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (!valid) return;
+    form.storageBlobDTOs = fileList.value;
+    addOrUpdateLawsuit(form).then(() => {
+      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
+      closeDialog();
+      getList();
+    });
+  });
+};
+
+const closeDialog = () => {
+  proxy.resetForm("formRef");
+  dialogVisible.value = false;
+};
+
+const handleDelete = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+    return;
+  }
+  const ids = selectedRows.value.map((item) => item.id);
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delLawsuit(ids).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleRowDelete = (row) => {
+  ElMessageBox.confirm("璇ヨ瘔璁艰褰曞皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "鎻愮ず", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+    .then(() => {
+      delLawsuit([row.id]).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        getList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleExport = () => {
+  proxy.download("/sales/lawsuit/export", { ...searchForm }, "璇夎绠$悊.xlsx");
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  margin-bottom: 10px;
+}
+
+.actions {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+</style>
diff --git a/vite.config.js b/vite.config.js
index 6b267a7..03311be 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -8,7 +8,7 @@
   const { VITE_APP_ENV } = env;
   const baseUrl =
       env.VITE_APP_ENV === "development"
-          ? "http://localhost:7005"
+          ? "http://1.15.17.182:9048"
           : env.VITE_BASE_API;
   const javaUrl =
       env.VITE_APP_ENV === "development"

--
Gitblit v1.9.3