From e2c871b1be0ff8cfa61e55325095ee1c79932ddd Mon Sep 17 00:00:00 2001
From: 张诺 <zhang_12370@163.com>
Date: 星期五, 30 一月 2026 17:01:19 +0800
Subject: [PATCH] tms 开发承运商运费结算模块

---
 src/views/inventoryManagement/procurementManagement/freightSettlement/index.vue |  421 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue      |    2 
 src/api/inventoryManagement/CarrierManagement.js                                |   49 ++++++
 3 files changed, 471 insertions(+), 1 deletions(-)

diff --git a/src/api/inventoryManagement/CarrierManagement.js b/src/api/inventoryManagement/CarrierManagement.js
index 40ec3b8..180a579 100644
--- a/src/api/inventoryManagement/CarrierManagement.js
+++ b/src/api/inventoryManagement/CarrierManagement.js
@@ -153,4 +153,53 @@
         url: `/fakeWarehousing/deliveryTrack/${ids}`,
         method: "delete",
     });
+}
+
+// 杩愯垂缁撶畻鍗曠鐞�
+// 鍒嗛〉鏌ヨ杩愯垂缁撶畻鍗曞垪琛�
+// /fakeWarehousing/freightSettlement/list
+export const getFreightSettlementPage = (params) => {
+    return request({
+        url: "/fakeWarehousing/freightSettlement/list",
+        method: "get",
+        params,
+    });
+}
+
+// 鏍规嵁缁撶畻ID鏌ヨ
+// /fakeWarehousing/freightSettlement/{id}
+export const getFreightSettlementDetail = (id) => {
+    return request({
+        url: `/fakeWarehousing/freightSettlement/${id}`,
+        method: "get",
+    });
+}
+
+// 鏂板杩愯垂缁撶畻鍗�
+// /fakeWarehousing/freightSettlement/
+export const addFreightSettlement = (data) => {
+    return request({
+        url: "/fakeWarehousing/freightSettlement",
+        method: "post",
+        data,
+    });
+}
+
+// 淇敼杩愯垂缁撶畻鍗�
+// /fakeWarehousing/freightSettlement/
+export const updateFreightSettlement = (data) => {
+    return request({
+        url: "/fakeWarehousing/freightSettlement",
+        method: "put",
+        data,
+    });
+}
+
+// 鍒犻櫎杩愯垂缁撶畻鍗�
+// /fakeWarehousing/freightSettlement/{ids}
+export const deleteFreightSettlement = (ids) => {
+    return request({
+        url: `/fakeWarehousing/freightSettlement/${ids}`,
+        method: "delete",
+    });
 }
\ No newline at end of file
diff --git a/src/views/inventoryManagement/procurementManagement/freightSettlement/index.vue b/src/views/inventoryManagement/procurementManagement/freightSettlement/index.vue
new file mode 100644
index 0000000..54f685f
--- /dev/null
+++ b/src/views/inventoryManagement/procurementManagement/freightSettlement/index.vue
@@ -0,0 +1,421 @@
+<template>
+  <div class="app-container">
+    <!-- 鎼滅储鏍� -->
+    <div class="search_form" style="display: flex; justify-content: space-between; gap: 12px; align-items: flex-start;">
+      <div style="display: flex; align-items: center; flex-wrap: wrap; gap: 10px;">
+        <span class="search_title">缁撶畻鍗曞彿:</span>
+        <el-input
+          v-model="searchForm.settleNo"
+          style="width: 240px"
+          placeholder="缁撶畻鍗曞彿"
+          clearable
+          :prefix-icon="Search"
+          @keyup.enter="handleQuery"
+        />
+        <span class="search_title">鎵胯繍鍟�:</span>
+        <el-input
+            v-model="searchForm.carrierName"
+            style="width: 240px"
+            placeholder="鎵胯繍鍟�"
+            clearable
+            :prefix-icon="Search"
+            @keyup.enter="handleQuery"
+        />
+        <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+        <el-button @click="resetQuery">閲嶇疆</el-button>
+      </div>
+
+      <div>
+        <el-button type="primary" @click="openCreate">鍒涘缓缁撶畻鍗�</el-button>
+      </div>
+    </div>
+
+    <!-- 琛ㄦ牸 -->
+    <el-row :gutter="20">
+      <el-col :span="24">
+        <div class="table_list">
+          <el-table
+            border
+            v-loading="tableLoading"
+            :data="tableData"
+            :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+            height="calc(100vh - 18.5em)"
+            :highlight-current-row="true"
+            style="width: 100%"
+            stripe
+            tooltip-effect="dark"
+            class="lims-table"
+          >
+            <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+            <el-table-column label="缁撶畻鍗曞彿" prop="settleNo" width="180" show-overflow-tooltip />
+            <el-table-column label="鎵胯繍鍟�" prop="carrierName" width="200" show-overflow-tooltip />
+            <el-table-column label="鍙戠エ鍙风爜" prop="invoiceNo" width="160" show-overflow-tooltip />
+            <el-table-column label="鍙戠エ閲戦(鍏�)" prop="invoiceAmt" width="140" align="right">
+              <template #default="{ row }">{{ toMoney(row.invoiceAmt) }}</template>
+            </el-table-column>
+            <el-table-column label="寮�绁ㄦ棩鏈�" prop="invoiceDate" width="120" />
+            <el-table-column label="鏇存柊鏃堕棿" prop="updateTime" width="170" />
+
+            <el-table-column fixed="right" label="鎿嶄綔" width="200" align="center">
+              <template #default="scope">
+                <el-button link type="primary" size="small" @click.stop="openView(scope.row)">鏌ョ湅</el-button>
+                <el-button link type="primary" size="small" @click.stop="openEdit(scope.row)">缂栬緫</el-button>
+                <el-button link type="danger" size="small" @click.stop="handleDelete(scope.row)">鍒犻櫎</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="total > 0"
+            @pagination="paginationSearch"
+            :total="total"
+            :layout="page.layout"
+            :page="page.current"
+            :limit="page.size"
+          />
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 鍒涘缓/缂栬緫寮圭獥 -->
+    <el-dialog
+      v-model="editVisible"
+      :title="editMode === 'create' ? '鍒涘缓缁撶畻鍗�' : '缂栬緫缁撶畻鍗�'"
+      width="720px"
+      :close-on-click-modal="false"
+      destroy-on-close
+    >
+      <el-form ref="editFormRef" :model="editForm" :rules="rules" label-width="120px">
+        <el-row :gutter="12">
+          <el-col :span="12">
+            <el-form-item label="缁撶畻鍗曞彿" prop="settleNo">
+              <el-input v-model="editForm.settleNo" placeholder="濡� FS-20260130-0001" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎵胯繍鍟嗗悕绉�" prop="carrierName">
+              <el-input v-model="editForm.carrierName" placeholder="璇疯緭鍏ユ壙杩愬晢鍚嶇О" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+              <el-input v-model="editForm.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍙戠エ閲戦(鍏�)" prop="invoiceAmt">
+              <el-input-number v-model="editForm.invoiceAmt" :min="0" :precision="2" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+              <el-date-picker
+                v-model="editForm.invoiceDate"
+                value-format="YYYY-MM-DD"
+                format="YYYY-MM-DD"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                clearable
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <el-button @click="editVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="saving" @click="submitEdit">纭畾</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 鏌ョ湅寮圭獥 -->
+    <el-dialog v-model="viewVisible" title="缁撶畻鍗曚俊鎭煡鐪�" width="720px" destroy-on-close>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="缁撶畻鍗曞彿">{{ viewRow.settleNo }}</el-descriptions-item>
+        <el-descriptions-item label="鎵胯繍鍟�">{{ viewRow.carrierName }}</el-descriptions-item>
+        <el-descriptions-item label="鍙戠エ鍙风爜">{{ viewRow.invoiceNo }}</el-descriptions-item>
+        <el-descriptions-item label="鍙戠エ閲戦(鍏�)">{{ toMoney(viewRow.invoiceAmt) }}</el-descriptions-item>
+        <el-descriptions-item label="寮�绁ㄦ棩鏈�">{{ viewRow.invoiceDate }}</el-descriptions-item>
+        <el-descriptions-item label="鍒涘缓鏃堕棿">{{ viewRow.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="鏇存柊鏃堕棿">{{ viewRow.updateTime }}</el-descriptions-item>
+      </el-descriptions>
+      <template #footer>
+        <el-button @click="viewVisible = false">鍏抽棴</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import pagination from "@/components/PIMTable/Pagination.vue";
+import dayjs from "dayjs";
+import {
+  getFreightSettlementPage,
+  getFreightSettlementDetail,
+  addFreightSettlement,
+  updateFreightSettlement,
+  deleteFreightSettlement,
+} from "@/api/inventoryManagement/CarrierManagement";
+
+const toMoney = (v) => {
+  if (v === undefined || v === null || v === "") return "0.00";
+  const n = Number(v);
+  return Number.isFinite(n) ? n.toFixed(2) : "0.00";
+};
+
+// 閫傞厤鍚庣瀛楁
+const normalizeRow = (raw = {}) => {
+  return {
+    id: raw.id,
+    settleNo: raw.settleNo,
+    carrierName: raw.carrierName,
+    invoiceNo: raw.invoiceNo,
+    invoiceAmt: raw.invoiceAmt,
+    invoiceDate: raw.invoiceDate,
+    createTime: raw.createTime,
+    updateTime: raw.updateTime,
+  };
+};
+
+// ------------------ 椤甸潰鐘舵�� ------------------
+const searchForm = reactive({
+  keyword: "",
+  invoiceDateRange: [],
+  invoiceDateStart: undefined,
+  invoiceDateEnd: undefined,
+});
+
+const page = reactive({
+  current: 1,
+  size: 10,
+  layout: "total, sizes, prev, pager, next, jumper",
+});
+
+const total = ref(0);
+const tableData = ref([]);
+const tableLoading = ref(false);
+
+const buildListParams = () => {
+  return {
+    current: page.current,
+    size: page.size,
+    settleNo: searchForm.settleNo || undefined,
+    carrierName: searchForm.carrierName || undefined,
+  };
+};
+
+const changeDateRange = (date) => {
+  if (date && date.length === 2) {
+    searchForm.invoiceDateStart = date[0];
+    searchForm.invoiceDateEnd = date[1];
+  } else {
+    searchForm.invoiceDateStart = undefined;
+    searchForm.invoiceDateEnd = undefined;
+  }
+  getList();
+};
+
+const clearRange = () => {
+  searchForm.invoiceDateRange = [];
+  getList();
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  searchForm.keyword = "";
+  searchForm.invoiceDateRange = [];
+  searchForm.invoiceDateStart = undefined;
+  searchForm.invoiceDateEnd = undefined;
+  page.current = 1;
+  getList();
+};
+
+const paginationSearch = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = async () => {
+  tableLoading.value = true;
+  try {
+    const res = await getFreightSettlementPage(buildListParams());
+    const data = res?.data ?? res;
+    const records = data?.records ?? data?.rows ?? data?.list ?? [];
+    const t = data?.total ?? data?.count ?? 0;
+
+    tableData.value = (records || []).map(normalizeRow);
+    total.value = t;
+  } catch (e) {
+    ElMessage.error(e?.message || e?.msg || "鍔犺浇澶辫触");
+  } finally {
+    tableLoading.value = false;
+  }
+};
+
+// ------------------ 鍒涘缓/缂栬緫/鏌ョ湅/鍒犻櫎 ------------------
+const editVisible = ref(false);
+const viewVisible = ref(false);
+const editMode = ref("create");
+const saving = ref(false);
+const editFormRef = ref();
+
+const editForm = reactive({
+  id: undefined,
+  settleNo: "",
+  carrierName: "",
+  invoiceNo: "",
+  invoiceAmt: 0,
+  invoiceDate: "",
+});
+
+const rules = {
+  settleNo: [{ required: true, message: "璇疯緭鍏ョ粨绠楀崟鍙�", trigger: "blur" }],
+  carrierName: [{ required: true, message: "璇疯緭鍏ユ壙杩愬晢鍚嶇О", trigger: "blur" }],
+  invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
+  invoiceAmt: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄩ噾棰�", trigger: "change" },
+    {validator: (rule, value, callback) => {if (value <= 0) {callback(new Error("鍙戠エ閲戦蹇呴』澶т簬0"));} else {callback();}}, trigger: "change",}],
+  invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
+};
+
+const openCreate = () => {
+  editMode.value = "create";
+  Object.assign(editForm, {
+    id: undefined,
+    settleNo: `FS-${dayjs().format("YYYYMMDD")}-${String(Math.floor(Math.random() * 9000 + 1000))}`,
+    carrierName: "",
+    invoiceNo: "",
+    invoiceAmt: 0,
+    invoiceDate: dayjs().format("YYYY-MM-DD"),
+  });
+  editVisible.value = true;
+  queueMicrotask(() => editFormRef.value?.clearValidate?.());
+};
+
+const openEdit = async (row) => {
+  editMode.value = "edit";
+  Object.assign(editForm, { ...row });
+
+  if (row?.id) {
+    try {
+      const res = await getFreightSettlementDetail(row.id);
+      const detail = res?.data ?? res;
+      Object.assign(editForm, normalizeRow(detail));
+    } catch {
+      // ignore
+    }
+  }
+
+  editVisible.value = true;
+  queueMicrotask(() => editFormRef.value?.clearValidate?.());
+};
+
+const viewRow = reactive({});
+const openView = async (row) => {
+  Object.assign(viewRow, row);
+  viewVisible.value = true;
+
+  if (row?.id) {
+    try {
+      const res = await getFreightSettlementDetail(row.id);
+      const detail = res?.data ?? res;
+      Object.assign(viewRow, normalizeRow(detail));
+    } catch {
+      // ignore
+    }
+  }
+};
+
+const submitEdit = async () => {
+  saving.value = true;
+  try {
+    await editFormRef.value?.validate?.();
+
+    const payload = {
+      id: editForm.id,
+      settleNo: editForm.settleNo,
+      carrierName: editForm.carrierName,
+      invoiceNo: editForm.invoiceNo,
+      invoiceAmt: editForm.invoiceAmt,
+      invoiceDate: editForm.invoiceDate,
+    };
+
+    if (editMode.value === "create") {
+      await addFreightSettlement(payload);
+      ElMessage.success("鍒涘缓鎴愬姛");
+      page.current = 1;
+    } else {
+      await updateFreightSettlement(payload);
+      ElMessage.success("鏇存柊鎴愬姛");
+    }
+
+    editVisible.value = false;
+    await getList();
+  } catch (e) {
+    if (e?.message) ElMessage.error(e.message);
+  } finally {
+    saving.value = false;
+  }
+};
+
+const handleDelete = async (row) => {
+  await ElMessageBox.confirm(`纭鍒犻櫎缁撶畻鍗曘��${row.settleNo}銆戯紵`, "鎻愮ず", {
+    confirmButtonText: "鍒犻櫎",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  });
+
+  tableLoading.value = true;
+  try {
+    await deleteFreightSettlement(row.id);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+
+    // 鍒犻櫎鍚庡鏋滃綋鍓嶉〉鏃犳暟鎹垯鍥為��
+    const res = await getFreightSettlementPage(buildListParams());
+    const data = res?.data ?? res;
+    const records = data?.records ?? data?.rows ?? data?.list ?? [];
+    if ((records || []).length === 0 && page.current > 1) {
+      page.current -= 1;
+    }
+
+    await getList();
+  } catch (e) {
+    ElMessage.error(e?.message || e?.msg || "鍒犻櫎澶辫触");
+  } finally {
+    tableLoading.value = false;
+  }
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped lang="scss">
+.el-pagination {
+  width: 100%;
+  height: 55px;
+  display: flex;
+  justify-content: flex-end;
+  float: right;
+  flex-direction: row;
+  align-items: center;
+  background: #fff;
+  margin: -20px 0 0 0;
+  padding: 0 20px;
+}
+
+.pagination-container {
+  margin-top: 0;
+}
+</style>
diff --git a/src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue b/src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue
index 2e8f994..3da8125 100644
--- a/src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue
+++ b/src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue
@@ -17,7 +17,7 @@
           gap: 10px;
         "
       >
-        <span class="search_title">鍏抽敭瀛�:</span>
+        <span class="search_title">鍏抽敭瀛楁煡璇�:</span>
         <el-input
           v-model="searchForm.keyword"
           style="width: 240px"

--
Gitblit v1.9.3