天津-豹鸣
1.招投标管理、合同管理、诉讼管理、宽带与通信管理、房屋租赁管理前端页面开发
已修改1个文件
已添加10个文件
3137 ■■■■■ 文件已修改
src/api/collaborativeApproval/broadbandCommunication.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/collaborativeApproval/houseLeaseManagement.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/biddingManagement.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/contractManagement.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/lawsuitManagement.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/broadbandCommunication/index.vue 481 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/houseLeaseManagement/index.vue 638 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/biddingManagement/index.vue 524 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/contractManagement/index.vue 805 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/lawsuitManagement/index.vue 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,
  });
}
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,
  });
}
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,
  });
}
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,
  });
}
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,
  });
}
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="正常" 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: "季付", 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;
};
// é¢„警状态:正常 / å³å°†åˆ°æœŸï¼ˆ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: "正常", 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>
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="正常" 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;
};
// é¢„警状态:正常 / å³å°†åˆ°æœŸï¼ˆ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: "正常", 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>
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>
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>
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>
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"