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