天津-豹鸣
1.招投标管理、合同管理、诉讼管理、宽带与通信管理、房屋租赁管理前端页面开发
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // 宽带ä¸é信管ç页颿¥å£ |
| | | 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, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // æ¿å±ç§èµç®¡ç页颿¥å£ |
| | | 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, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // æææ 管ç页颿¥å£ |
| | | 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, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // åå管ç页颿¥å£ |
| | | 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, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // è¯è®¼ç®¡ç页颿¥å£ |
| | | 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, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | 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" |