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/paymentHistory/index.vue |  512 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 512 insertions(+), 0 deletions(-)

diff --git a/src/views/inventoryManagement/procurementManagement/paymentHistory/index.vue b/src/views/inventoryManagement/procurementManagement/paymentHistory/index.vue
new file mode 100644
index 0000000..2fb870d
--- /dev/null
+++ b/src/views/inventoryManagement/procurementManagement/paymentHistory/index.vue
@@ -0,0 +1,512 @@
+<template>
+  <div class="app-container">
+    <el-form :model="searchForm" :inline="true">
+      <el-form-item label="鎵胯繍鍟�">
+        <el-input
+          v-model="searchForm.carrierName"
+          style="width: 240px"
+          placeholder="杈撳叆鎵胯繍鍟嗗悕绉�"
+          clearable
+          :prefix-icon="Search"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="鍚堝悓缂栧彿">
+        <el-input
+          v-model="searchForm.contractCode"
+          style="width: 240px"
+          placeholder="杈撳叆鍚堝悓缂栧彿"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="鍚堝悓鐘舵��">
+        <el-select
+          v-model="searchForm.contractStatus"
+          style="width: 160px"
+          placeholder="鍏ㄩ儴"
+          clearable
+        >
+          <el-option label="鏈夋晥" :value="1" />
+          <el-option label="鏃犳晥" :value="0" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="鍚堝悓鏃堕棿">
+        <el-date-picker
+          v-model="searchForm.timeRange"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          format="YYYY-MM-DD"
+          type="datetimerange"
+          start-placeholder="寮�濮嬫椂闂�"
+          end-placeholder="缁撴潫鏃堕棿"
+          clearable
+          @change="changeDateRange"
+          @clear="clearRange"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
+          鎼滅储
+        </el-button>
+        <el-button @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="table_list">
+      <div style="display: flex; justify-content: flex-end; margin-bottom: 12px">
+        <el-button type="primary" @click="openCreate">鍒涘缓鍚堝悓</el-button>
+      </div>
+
+      <PIMTable
+        rowKey="id"
+        :column="tableColumn"
+        :tableData="tableData"
+        :page="page"
+        :isSelection="false"
+        @selection-change="handleSelectionChange"
+        :tableLoading="tableLoading"
+        @pagination="pagination"
+        :total="page.total"
+      >
+        <template #statusSlot="{ row }">
+          <el-tag :type="row.contractStatus === 1 ? 'success' : 'info'">
+            {{ row.contractStatus === 1 ? '鏈夋晥' : '鏃犳晥' }}
+          </el-tag>
+        </template>
+        <template #versionSlot="{ row }">
+          <el-tag type="warning">v{{ row.version }}</el-tag>
+        </template>
+      </PIMTable>
+    </div>
+
+    <!-- 鍒涘缓/缁存姢(缂栬緫)寮圭獥 -->
+    <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="contractCode">
+              <el-input v-model="editForm.contractCode" placeholder="濡� HT-2026-0001" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍚堝悓鍚嶇О" prop="contractName">
+              <el-input v-model="editForm.contractName" placeholder="濡� 閿�鍞悎鍚�" />
+            </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="startTime">
+              <el-date-picker
+                v-model="editForm.startTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                format="YYYY-MM-DD HH:mm:ss"
+                type="datetime"
+                placeholder="璇烽�夋嫨"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="鍚堝悓缁撴潫鏃堕棿" prop="endTime">
+              <el-date-picker
+                v-model="editForm.endTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                format="YYYY-MM-DD HH:mm:ss"
+                type="datetime"
+                placeholder="鍙��"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="12">
+            <el-form-item label="鍚堝悓鐘舵��" prop="contractStatus">
+              <el-select v-model="editForm.contractStatus" placeholder="璇烽�夋嫨" style="width: 100%">
+                <el-option label="鏈夋晥" :value="1" />
+                <el-option label="鏃犳晥" :value="0" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="remark">
+              <el-input v-model="editForm.remark" type="textarea" :rows="2" placeholder="鍙��" />
+            </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.contractCode }}</el-descriptions-item>
+        <el-descriptions-item label="鎵胯繍鍟�">{{ viewRow.carrierName }}</el-descriptions-item>
+        <el-descriptions-item label="鐘舵��">{{ viewRow.contractStatus === 1 ? '鏈夋晥' : '鏃犳晥' }}</el-descriptions-item>
+        <el-descriptions-item label="寮�濮嬫椂闂�">{{ viewRow.startTime }}</el-descriptions-item>
+        <el-descriptions-item label="缁撴潫鏃堕棿">{{ viewRow.endTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="鍒涘缓鏃堕棿">{{ viewRow.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="淇敼鏃堕棿">{{ viewRow.updateTime }}</el-descriptions-item>
+        <el-descriptions-item label="澶囨敞" :span="2">{{ viewRow.remark || '-' }}</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 useFormData from "@/hooks/useFormData";
+import dayjs from "dayjs";
+import PIMTable from "@/components/PIMTable/PIMTable.vue";
+import {
+  getCarrierContractPage,
+  getCarrierContractDetail,
+  addCarrierContract,
+  updateCarrierContract,
+  deleteCarrierContract,
+} from "@/api/inventoryManagement/CarrierManagement";
+
+// ------------------ 鏁版嵁閫傞厤锛堝吋瀹瑰悗绔繑鍥炵粨鏋勶級 ------------------
+const normalizeRow = (raw = {}) => {
+  // 鍚庣瀛楁锛歩d/contractCode/carrierId/carrierName/contractName/contractStatus/startTime/endTime/remark/createUser/createTime/updateUser/updateTime/tenantId
+  return {
+    id: raw.id,
+    contractCode: raw.contractCode,
+    carrierId: raw.carrierId,
+    carrierName: raw.carrierName,
+    contractName: raw.contractName,
+    contractStatus: raw.contractStatus,
+    startTime: raw.startTime,
+    endTime: raw.endTime,
+    remark: raw.remark,
+    createUser: raw.createUser,
+    createTime: raw.createTime,
+    updateUser: raw.updateUser,
+    updateTime: raw.updateTime,
+    tenantId: raw.tenantId,
+    // version 闈炲悗绔瓧娈碉細椤甸潰灞曠ず鍏滃簳
+    version: raw.version ?? 1,
+  };
+};
+
+// ------------------ 椤甸潰鐘舵�� ------------------
+const tableData = ref([]);
+const selectedRows = ref([]);
+const tableLoading = ref(false);
+
+const page = reactive({
+  current: 1,
+  size: 100,
+  total: 0,
+  layout: "total, sizes, prev, pager, next, jumper",
+});
+
+const { form: searchForm } = useFormData({
+  carrierName: "",
+  contractCode: "",
+  contractName: "",
+  contractStatus: "",
+  timeRange: [],
+  startTime: undefined,
+  endTime: undefined,
+});
+
+const tableColumn = ref([
+  { label: "鍚堝悓缂栧彿", prop: "contractCode", width: 160 },
+  { label: "鍚堝悓鍚嶇О", prop: "contractName", width: 200 },
+  { label: "鎵胯繍鍟�", prop: "carrierName", width: 200 },
+  { label: "鐘舵��", prop: "contractStatus", dataType: "slot", slot: "statusSlot", width: 90 },
+  { label: "寮�濮嬫椂闂�", prop: "startTime", width: 170 },
+  { label: "缁撴潫鏃堕棿", prop: "endTime", width: 170 },
+  { label: "淇敼鏃堕棿", prop: "updateTime", width: 170 },
+  {
+    label: "鎿嶄綔",
+    prop: "action",
+    dataType: "action",
+    fixed: "right",
+    width: 140,
+    operation: [
+      { name: "鏌ョ湅", clickFun: (row) => openView(row) },
+      { name: "缂栬緫", clickFun: (row) => openMaintain(row) },
+      { name: "鍒犻櫎", clickFun: (row) => handleDelete(row) },
+    ],
+  },
+]);
+
+const getList = async () => {
+  tableLoading.value = true;
+  try {
+    const { timeRange, ...rest } = searchForm;
+    const params = {
+      ...rest,
+      current: page.current,
+      size: page.size,
+    };
+
+    const res = await getCarrierContractPage(params);
+    const data = res?.data ?? res;
+    const records = data?.records ?? data?.rows ?? data?.list ?? [];
+    const total = data?.total ?? data?.count ?? 0;
+
+    tableData.value = (records || []).map(normalizeRow);
+    page.total = total;
+  } catch (e) {
+    ElMessage.error(e?.message || e?.msg || "鍔犺浇澶辫触");
+  } finally {
+    tableLoading.value = false;
+  }
+};
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  searchForm.carrierName = "";
+  searchForm.contractCode = "";
+  searchForm.contractName = "";
+  searchForm.contractStatus = "";
+  searchForm.timeRange = [];
+  searchForm.startTime = undefined;
+  searchForm.endTime = undefined;
+  page.current = 1;
+  getList();
+};
+
+const pagination = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const handleSelectionChange = (selection) => {
+  selectedRows.value = selection;
+};
+
+const changeDateRange = (date) => {
+  if (date && date.length === 2) {
+    searchForm.startTime = date[0];
+    searchForm.endTime = date[1];
+  } else {
+    searchForm.startTime = undefined;
+    searchForm.endTime = undefined;
+  }
+  getList();
+};
+
+const clearRange = () => {
+  searchForm.timeRange = [];
+  searchForm.startTime = undefined;
+  searchForm.endTime = undefined;
+  getList();
+};
+
+// ------------------ 寮圭獥锛氭煡鐪�/缁存姢 ------------------
+const viewVisible = ref(false);
+const viewRow = reactive({});
+
+const openView = async (row) => {
+  // 鍏堝睍绀鸿鏁版嵁
+  Object.assign(viewRow, row);
+  viewVisible.value = true;
+
+  // 鍐嶆媺璇︽儏琛ュ叏
+  if (!row?.id) return;
+  try {
+    const res = await getCarrierContractDetail(row.id);
+    const detail = res?.data ?? res;
+    Object.assign(viewRow, normalizeRow(detail));
+  } catch {
+    // 璇︽儏澶辫触涓嶉樆鏂煡鐪�
+  }
+};
+
+const editVisible = ref(false);
+const editMode = ref("create"); // create | maintain
+const editFormRef = ref();
+const saving = ref(false);
+const editForm = reactive({
+  id: undefined,
+  contractCode: "",
+  carrierId: undefined,
+  carrierName: "",
+  contractName: "",
+  contractStatus: 1,
+  startTime: "",
+  endTime: "",
+  remark: "",
+});
+
+const rules = {
+  contractCode: [{ required: true, message: "璇疯緭鍏ュ悎鍚岀紪鍙�", trigger: "blur" }],
+  carrierName: [{ required: true, message: "璇疯緭鍏ユ壙杩愬晢鍚嶇О", trigger: "blur" }],
+  contractName: [{ required: true, message: "璇疯緭鍏ュ悎鍚屽悕绉�", trigger: "blur" }],
+  contractStatus: [{ required: true, message: "璇烽�夋嫨鍚堝悓鐘舵��", trigger: "change" }],
+  startTime: [{ required: true, message: "璇烽�夋嫨鍚堝悓寮�濮嬫椂闂�", trigger: "change" }],
+  endTime:[{ required: true, message: "璇烽�夋嫨鍚堝悓寮�濮嬫椂闂�", trigger: "change" }]
+};
+
+const openCreate = () => {
+  editMode.value = "create";
+  Object.assign(editForm, {
+    id: undefined,
+    contractCode: "",
+    carrierId: undefined,
+    carrierName: "",
+    contractName: "",
+    contractStatus: 1,
+    startTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+    endTime: "",
+    remark: "",
+  });
+  editVisible.value = true;
+  queueMicrotask(() => editFormRef.value?.clearValidate?.());
+};
+
+const openMaintain = async (row) => {
+  editMode.value = "maintain";
+  Object.assign(editForm, {
+    id: row.id,
+    contractCode: row.contractCode,
+    carrierId: row.carrierId,
+    carrierName: row.carrierName,
+    contractName: row.contractName,
+    contractStatus: row.contractStatus,
+    startTime: row.startTime,
+    endTime: row.endTime,
+    remark: row.remark,
+  });
+
+  // 鎷夎鎯咃紝閬垮厤鍒楄〃瀛楁涓嶅叏
+  if (row?.id) {
+    try {
+      const res = await getCarrierContractDetail(row.id);
+      const detail = res?.data ?? res;
+      const d = normalizeRow(detail);
+      Object.assign(editForm, {
+        id: d.id,
+        contractCode: d.contractCode,
+        carrierId: d.carrierId,
+        carrierName: d.carrierName,
+        contractName: d.contractName,
+        contractStatus: d.contractStatus,
+        startTime: d.startTime,
+        endTime: d.endTime,
+        remark: d.remark,
+      });
+    } catch {
+      // ignore
+    }
+  }
+
+  editVisible.value = true;
+  queueMicrotask(() => editFormRef.value?.clearValidate?.());
+};
+
+const submitEdit = async () => {
+  saving.value = true;
+  try {
+    await editFormRef.value?.validate?.();
+
+    const payload = {
+      id: editForm.id,
+      contractCode: editForm.contractCode,
+      carrierId: editForm.carrierId,
+      carrierName: editForm.carrierName,
+      contractName: editForm.contractName,
+      contractStatus: editForm.contractStatus,
+      startTime: editForm.startTime,
+      endTime: editForm.endTime,
+      remark: editForm.remark,
+    };
+
+    if (editMode.value === "create") {
+      await addCarrierContract(payload);
+      ElMessage.success("鍒涘缓鎴愬姛");
+      page.current = 1;
+    } else {
+      await updateCarrierContract(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.contractCode}銆戯紵`, "鎻愮ず", {
+    confirmButtonText: "鍒犻櫎",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  });
+
+  tableLoading.value = true;
+  try {
+    await deleteCarrierContract(row.id);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+
+    // 鍒犻櫎鍚庤嫢褰撳墠椤垫病鏁版嵁锛屽洖閫�涓�椤�
+    const res = await getCarrierContractPage({
+      ...searchForm,
+      current: page.current,
+      size: page.size,
+    });
+    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">
+.table_list {
+  margin-top: unset;
+}
+</style>

--
Gitblit v1.9.3