已添加12个文件
3819 ■■■■■ 文件已修改
src/api/inventoryManagement/CarrierManagement.js 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/DeliveryTrackingManagement/index.vue 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/freightSettlement/index.vue 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/paymentHistory/index.vue 512 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue 684 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Modal/UploadModal.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/index.vue 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/indexOld.vue 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementLedger/fileList.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/procurementManagement/procurementLedger/index.vue 528 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/CarrierManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,205 @@
import request from "@/utils/request";
// æŸ¥è¯¢å…¥åº“信息列表
export const getStockInPage = (params) => {
    return request({
        url: "/stockin/listPage",
        method: "get",
        params,
    });
};
// åˆ†é¡µæŸ¥è¯¢æ‰¿è¿åˆåŒåˆ—表
// /fakeWarehousing/list
export const getCarrierContractPage = (params) => {
    return request({
        url: "/fakeWarehousing/list",
        method: "get",
        params,
    });
};
// æ ¹æ®ID查询承运合同详情
// /fakeWarehousing/{id}
export const getCarrierContractDetail = (id) => {
    return request({
        url: `/fakeWarehousing/${id}`,
        method: "get",
    });
};
// æ–°å¢žæ‰¿è¿åˆåŒ
// /fakeWarehousing/
export const addCarrierContract = (data) => {
    return request({
        url: "/fakeWarehousing",
        method: "post",
        data,
    });
};
// ä¿®æ”¹æ‰¿è¿åˆåŒ
// /fakeWarehousing/
export const updateCarrierContract = (data) => {
    return request({
        url: "/fakeWarehousing",
        method: "put",
        data,
    });
}
// æ‰¿è¿åˆåŒç®¡ç†-删除ids
// /fakeWarehousing/{ids}
export const deleteCarrierContract = (ids) => {
    return request({
        url: `/fakeWarehousing/${ids}`,
        method: "delete",
    });
};
// åˆ†é¡µæŸ¥è¯¢æ‰¿è¿è®¢å•列表
// /fakeWarehousing/order/list
export const getCarrierOrderPage = (params) => {
    return request({
        url: "/fakeWarehousing/order/list",
        method: "get",
        params,
    });
};
// æ ¹æ®ID查询承运订单详情
// /fakeWarehousing/order/{id}
export const getCarrierOrderDetail = (id) => {
    return request({
        url: `/fakeWarehousing/order/${id}`,
        method: "get",
    });
};
// æ–°å¢žæ‰¿è¿è®¢å•
// /fakeWarehousing/order/
export const addCarrierOrder = (data) => {
    return request({
        url: "/fakeWarehousing/order",
        method: "post",
        data,
    });
};
// ä¿®æ”¹æ‰¿è¿è®¢å•
// /fakeWarehousing/order/
export const updateCarrierOrder = (data) => {
    return request({
        url: "/fakeWarehousing/order",
        method: "put",
        data,
    });
}
// åˆ é™¤æ‰¿è¿è®¢å•
// /fakeWarehousing/order/{ids}
export const deleteCarrierOrder = (ids) => {
    return request({
        url: `/fakeWarehousing/order/${ids}`,
        method: "delete",
    });
};
// æ–°å¢žå‘货跟踪记录
// /fakeWarehousing/deliveryTrack/
export const addDeliveryTrack = (data) => {
    return request({
        url: "/fakeWarehousing/deliveryTrack",
        method: "post",
        data,
    });
}
// å‘货跟踪管理接口
// åˆ†é¡µæŸ¥è¯¢å‘货跟踪列表
// /fakeWarehousing/deliveryTrack/list
export const getDeliveryTrackPage = (params) => {
    return request({
        url: "/fakeWarehousing/deliveryTrack/list",
        method: "get",
        params,
    });
}
// æ ¹æ®ID查询发货跟踪详情
// /fakeWarehousing/deliveryTrack/{id}
export const getDeliveryTrackDetail = (id) => {
    return request({
        url: `/fakeWarehousing/deliveryTrack/${id}`,
        method: "get",
    });
}
// ä¿®æ”¹å‘货跟踪记录
// /fakeWarehousing/deliveryTrack/
export const updateDeliveryTrack = (data) => {
    return request({
        url: "/fakeWarehousing/deliveryTrack",
        method: "put",
        data,
    });
}
// åˆ é™¤å‘货跟踪记录
// /fakeWarehousing/deliveryTrack/{ids}
export const deleteDeliveryTrack = (ids) => {
    return request({
        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",
    });
}
src/views/inventoryManagement/procurementManagement/DeliveryTrackingManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
<template>
  <div class="app-container">
    <el-form :model="searchForm" :inline="true">
      <el-form-item label="关键字">
        <el-input
          v-model="searchForm.keyword"
          style="width: 240px"
          placeholder="订单号/承运商/车牌"
          clearable
          :prefix-icon="Search"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="发货日期">
        <el-date-picker
          v-model="searchForm.shipDateRange"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          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">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="false"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      >
        <template #statusSlot="{ row }">
          <el-tag :type="trackStatusTagType(row.trackStatus)">
            {{ trackStatusText(row.trackStatus) }}
          </el-tag>
        </template>
      </PIMTable>
    </div>
    <!-- è½¨è¿¹æŸ¥çœ‹å¼¹çª— -->
    <el-dialog
      v-model="trackVisible"
      title="在途轨迹查询"
      width="760px"
      :close-on-click-modal="false"
      destroy-on-close
    >
      <div style="margin-bottom: 10px; color: #606266">
        <div>订单号:{{ currentRow.orderCode }}</div>
        <div>承运商:{{ currentRow.carrierName }} 车牌:{{ currentRow.vehicleNo }}</div>
        <div>发货时间:{{ currentRow.shipTime || "-" }}</div>
      </div>
      <el-empty v-if="trackLoading" description="加载轨迹中..." />
      <el-empty v-else-if="trackList.length === 0" description="暂无轨迹" />
      <el-timeline v-else>
        <el-timeline-item
          v-for="item in trackList"
          :key="item.id"
          :timestamp="item.time"
          :type="item.type"
          :hollow="false"
        >
          <div style="font-weight: 600">{{ item.title }}</div>
          <div style="color: #909399">{{ item.remark }}</div>
        </el-timeline-item>
      </el-timeline>
      <template #footer>
        <el-button @click="trackVisible = 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 PIMTable from "@/components/PIMTable/PIMTable.vue";
import {
  getDeliveryTrackPage,
  getDeliveryTrackDetail,
  deleteDeliveryTrack,
} from "@/api/inventoryManagement/CarrierManagement";
// ------------------ è½¨è¿¹ï¼ˆæš‚时假数据:等后端轨迹接口字段确定后替换) ------------------
const pad2 = (n) => String(n).padStart(2, "0");
const formatDateTime = (d = new Date()) => {
  const yyyy = d.getFullYear();
  const mm = pad2(d.getMonth() + 1);
  const dd = pad2(d.getDate());
  const hh = pad2(d.getHours());
  const mi = pad2(d.getMinutes());
  const ss = pad2(d.getSeconds());
  return `${yyyy}-${mm}-${dd} ${hh}:${mi}:${ss}`;
};
const trackStatusText = (s) => {
  const map = { 0: "待发车", 1: "运输中", 2: "已到达", 3: "异常" };
  return map[s] ?? "-";
};
const trackStatusTagType = (s) => {
  const map = { 0: "info", 1: "warning", 2: "success", 3: "danger" };
  return map[s] ?? "info";
};
const buildMockTrack = (row) => {
  const now = Date.now();
  const isArrived = Number(row.trackStatus) === 2;
  const isAbnormal = Number(row.trackStatus) === 3;
  const base = [
    {
      id: 1,
      time: formatDateTime(new Date(now - 1000 * 60 * 60 * 18)),
      title: "已创建发货单",
      location: row.origin || "-",
      remark: "已完成装车",
      type: "primary",
    },
    {
      id: 2,
      time: formatDateTime(new Date(now - 1000 * 60 * 60 * 12)),
      title: "车辆已发车",
      location: row.origin || "-",
      remark: "从仓库出发",
      type: "success",
    },
    {
      id: 3,
      time: formatDateTime(new Date(now - 1000 * 60 * 60 * 6)),
      title: isAbnormal ? "运输异常" : "途经节点",
      location: "中转站",
      remark: isAbnormal ? "车辆故障,等待处理" : "正常",
      type: isAbnormal ? "danger" : "warning",
    },
  ];
  if (isArrived) {
    base.push({
      id: 4,
      time: formatDateTime(new Date(now - 1000 * 60 * 60)),
      title: "已到达",
      location: row.destination || "-",
      remark: "已签收",
      type: "success",
    });
  }
  return base;
};
// ------------------ åˆ—表查询(来自后端 /fakeWarehousing/deliveryTrack/list) ------------------
// åŽç«¯æ ·ä¾‹å­—段:
// id, orderId, orderCode, carrierName, shipTime, vehicleNo, driverName, driverPhone, remark,
// createTime, updateTime, createUser, updateUser, tenantId
const normalizeRow = (raw = {}) => {
  const id = raw.id ?? raw.trackId ?? raw.deliveryTrackId;
  return {
    id,
    orderId: raw.orderId ?? null,
    orderCode: raw.orderCode ?? raw.orderNo ?? "-",
    carrierName: raw.carrierName ?? raw.carrier ?? "-",
    shipTime: raw.shipTime ?? raw.shipDate ?? "-",
    vehicleNo: raw.vehicleNo ?? raw.carNo ?? "-",
    driverName: raw.driverName ?? raw.driver ?? "-",
    driverPhone: raw.driverPhone ?? raw.driverTel ?? "-",
    remark: raw.remark ?? "",
    createTime: raw.createTime ?? "-",
    updateTime: raw.updateTime ?? raw.modifyTime ?? "-",
  };
};
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
  layout: "total, sizes, prev, pager, next, jumper",
});
const searchForm = reactive({
  keyword: "",
  trackStatus: "",
  shipDateRange: [],
  shipDateStart: undefined,
  shipDateEnd: undefined,
});
const tableColumn = ref([
  { label: "订单号", prop: "orderCode", width: 180 },
  { label: "承运商", prop: "carrierName", width: 180 },
  { label: "车牌号", prop: "vehicleNo", width: 130 },
  { label: "发货时间", prop: "shipTime", width: 170 },
  { label: "司机", prop: "driverName", width: 120 },
  { label: "司机电话", prop: "driverPhone", width: 140 },
  { label: "更新时间", prop: "updateTime", width: 170 },
  {
    label: "操作",
    prop: "action",
    dataType: "action",
    fixed: "right",
    width: 120,
    operation: [
      { name: "查看轨迹", clickFun: (row) => openTrack(row) },
      { name: "删除", clickFun: (row) => handleDelete(row) },
    ],
  },
]);
const buildListParams = () => {
  return {
    current: page.current,
    size: page.size,
    keyword: searchForm.keyword || undefined,
    orderCode: searchForm.keyword || undefined,
    carrierName: searchForm.keyword || undefined,
    vehicleNo: searchForm.keyword || undefined,
    trackStatus:
      searchForm.trackStatus === "" || searchForm.trackStatus === null || searchForm.trackStatus === undefined
        ? undefined
        : searchForm.trackStatus,
    startDate: searchForm.shipDateStart || undefined,
    endDate: searchForm.shipDateEnd || undefined,
  };
};
const getList = async () => {
  tableLoading.value = true;
  try {
    const res = await getDeliveryTrackPage(buildListParams());
    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.keyword = "";
  searchForm.trackStatus = "";
  searchForm.shipDateRange = [];
  searchForm.shipDateStart = undefined;
  searchForm.shipDateEnd = undefined;
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const changeDateRange = (date) => {
  if (date && date.length === 2) {
    searchForm.shipDateStart = date[0];
    searchForm.shipDateEnd = date[1];
  } else {
    searchForm.shipDateStart = undefined;
    searchForm.shipDateEnd = undefined;
  }
  getList();
};
const clearRange = () => {
  searchForm.shipDateRange = [];
  searchForm.shipDateStart = undefined;
  searchForm.shipDateEnd = undefined;
  getList();
};
// ------------------ è½¨è¿¹å¼¹çª—(详情调用后端 /fakeWarehousing/deliveryTrack/{id} ï¼‰ ------------------
const trackVisible = ref(false);
const trackLoading = ref(false);
const trackList = ref([]);
const currentRow = reactive({});
const openTrack = async (row) => {
  Object.assign(currentRow, row);
  trackVisible.value = true;
  trackLoading.value = true;
  try {
    const res = await getDeliveryTrackDetail(row.id);
    const detail = res?.data ?? res;
    Object.assign(currentRow, normalizeRow(detail));
    // è½¨è¿¹æ˜Žç»†å­—段尚未确定:暂用模拟轨迹展示
    trackList.value = buildMockTrack(currentRow);
  } catch (e) {
    ElMessage.error(e?.message || e?.msg || "轨迹加载失败");
    trackList.value = buildMockTrack(currentRow);
  } finally {
    trackLoading.value = false;
  }
};
// åˆ é™¤ï¼ˆæ‰¹é‡ ids åŽç«¯ä¹Ÿæ”¯æŒï¼šè¿™é‡Œå•条删除)
const handleDelete = async (row) => {
  await ElMessageBox.confirm(`确认删除发货记录【${row.orderCode || "-"}】?`, "提示", {
    confirmButtonText: "删除",
    cancelButtonText: "取消",
    type: "warning",
  });
  tableLoading.value = true;
  try {
    await deleteDeliveryTrack(row.id);
    ElMessage.success("删除成功");
    // åˆ é™¤åŽå¦‚果当前页为空则回退
    const res = await getDeliveryTrackPage(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;
  }
};
// é¢„留:新增/修改接口已接入(后续加弹窗即可)
// addDeliveryTrack / updateDeliveryTrack
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.table_list {
  margin-top: unset;
}
</style>
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>
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 = {}) => {
  // åŽç«¯å­—段:id/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>
src/views/inventoryManagement/procurementManagement/paymentOrder/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,684 @@
<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.keyword"
          style="width: 240px"
          placeholder="订单号/承运商/发货地/收货地"
          clearable
          :prefix-icon="Search"
          @keyup.enter="handleQuery"
        />
        <span class="search_title">状态:</span>
        <el-select
          v-model="searchForm.orderStatus"
          style="width: 160px"
          clearable
          placeholder="全部"
        >
          <el-option label="待发货" :value="1" />
          <el-option label="已发货" :value="2" />
        </el-select>
        <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="orderCode"
              width="160"
              show-overflow-tooltip
            />
            <el-table-column
              label="承运商"
              prop="carrierName"
              width="180"
              show-overflow-tooltip
            />
            <el-table-column
              label="发货地"
              prop="origin"
              width="160"
              show-overflow-tooltip
            />
            <el-table-column
              label="收货地"
              prop="destination"
              width="160"
              show-overflow-tooltip
            />
            <el-table-column
              label="重量(kg)"
              prop="weight"
              width="110"
              align="right"
            />
            <el-table-column
              label="体积(m³)"
              prop="volume"
              width="110"
              align="right"
            />
            <el-table-column
              label="预计费用(元)"
              prop="estimatedFee"
              width="140"
              align="right"
            >
              <template #default="{ row }">{{ toMoney(row.estimatedFee) }}</template>
            </el-table-column>
            <el-table-column label="状态" prop="orderStatus" width="110">
              <template #default="{ row }">
                <el-tag :type="statusTagType(row.orderStatus)">
                  {{ statusText(row.orderStatus) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="更新时间" prop="updateTime" width="170" />
            <el-table-column fixed="right" label="操作" width="260" align="center">
              <template #default="scope">
                <el-button
                  link
                  type="success"
                  size="small"
                  :disabled="scope.row.orderStatus !== 1"
                  @click.stop="openDelivery(scope.row)"
                  >发货</el-button
                >
                <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="orderCode">
              <el-input
                v-model="editForm.orderCode"
                placeholder="如 TO-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="origin">
              <el-input v-model="editForm.origin" placeholder="请输入发货地" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="收货地" prop="destination">
              <el-input
                v-model="editForm.destination"
                placeholder="请输入收货地"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="重量(kg)" prop="weight">
              <el-input-number
                v-model="editForm.weight"
                :min="0"
                :precision="2"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="体积(m³)" prop="volume">
              <el-input-number
                v-model="editForm.volume"
                :min="0"
                :precision="3"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预计费用(元)" prop="estimatedFee">
              <el-input-number
                v-model="editForm.estimatedFee"
                :min="0"
                :precision="2"
                style="width: 100%"
              />
            </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.orderCode }}</el-descriptions-item>
        <el-descriptions-item label="状态">{{ statusText(viewRow.orderStatus) }}</el-descriptions-item>
        <el-descriptions-item label="承运商">{{ viewRow.carrierName }}</el-descriptions-item>
        <el-descriptions-item label="发货地">{{ viewRow.origin }}</el-descriptions-item>
        <el-descriptions-item label="收货地">{{ viewRow.destination }}</el-descriptions-item>
        <el-descriptions-item label="重量(kg)">{{ viewRow.weight }}</el-descriptions-item>
        <el-descriptions-item label="体积(m³)">{{ viewRow.volume }}</el-descriptions-item>
        <el-descriptions-item label="预计费用(元)">{{ toMoney(viewRow.estimatedFee) }}</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>
    <!-- å‘货弹窗(暂用假提交,等后端接口) -->
    <el-dialog
      v-model="deliveryVisible"
      title="发货"
      width="720px"
      :close-on-click-modal="false"
      destroy-on-close
    >
      <el-form ref="deliveryFormRef" :model="deliveryForm" :rules="deliveryRules" label-width="120px">
        <el-row :gutter="12">
          <el-col :span="24">
            <el-alert
              type="info"
              :closable="false"
              :title="`订单号:${deliveryForm.orderCode || '-'}(承运商:${deliveryForm.carrierName || '-'})`"
              style="margin-bottom: 12px"
            />
          </el-col>
          <el-col :span="12">
            <el-form-item label="发货时间" prop="shipTime">
              <el-date-picker
                v-model="deliveryForm.shipTime"
                type="datetime"
                value-format="YYYY-MM-DD HH:mm:ss"
                format="YYYY-MM-DD HH:mm:ss"
                placeholder="请选择"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="车牌号" prop="vehicleNo">
              <el-input v-model="deliveryForm.vehicleNo" placeholder="如 æ²ªA12345" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="司机姓名" prop="driverName">
              <el-input v-model="deliveryForm.driverName" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="司机电话" prop="driverPhone">
              <el-input v-model="deliveryForm.driverPhone" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input v-model="deliveryForm.remark" type="textarea" :rows="2" placeholder="可选" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <el-button @click="deliveryVisible = false">取消</el-button>
        <el-button type="primary" :loading="deliverySubmitting" @click="submitDelivery">确定发货</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 {
  getCarrierOrderPage,
  getCarrierOrderDetail,
  addCarrierOrder,
  updateCarrierOrder,
  deleteCarrierOrder, addDeliveryTrack,
} 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 statusText = (s) => {
  const map = {
    0: "草稿",
    1: "待发货",
    2: "已发货",
  };
  return map[s] ?? "-";
};
const statusTagType = (s) => {
  const map = {
    0: "info",
    1: "success",
    2: "warning",
    3: "success",
    4: "danger",
  };
  return map[s] ?? "info";
};
// é€‚配后端返回字段(你后端字段未最终确定时先做兜底映射)
const normalizeRow = (raw = {}) => {
  return {
    orderId: raw.id ?? raw.orderId,
    orderCode: raw.orderCode ?? raw.code,
    carrierId: raw.carrierId,
    carrierName: raw.carrierName,
    origin: raw.origin ?? raw.startAddress ?? raw.fromAddress,
    destination: raw.destination ?? raw.endAddress ?? raw.toAddress,
    weight: raw.weight ?? 0,
    volume: raw.volume ?? 0,
    estimatedFee: raw.estimatedFee ?? raw.fee ?? 0,
    orderStatus: raw.orderStatus ?? raw.status ?? 0,
    remark: raw.remark ?? "",
    createTime: raw.createTime,
    updateTime: raw.updateTime,
  };
};
// ------------------ é¡µé¢çŠ¶æ€ ------------------
const searchForm = reactive({
  keyword: "",
  orderStatus: "",
});
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,
    keyword: searchForm.keyword || undefined,
    orderCode: searchForm.keyword || undefined,
    carrierName: searchForm.keyword || undefined,
    origin: searchForm.keyword || undefined,
    destination: searchForm.keyword || undefined,
    orderStatus:
      searchForm.orderStatus === "" || searchForm.orderStatus === null || searchForm.orderStatus === undefined
        ? undefined
        : searchForm.orderStatus,
  };
};
const getList = async () => {
  tableLoading.value = true;
  try {
    const res = await getCarrierOrderPage(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 handleQuery = () => {
  page.current = 1;
  getList();
};
const resetQuery = () => {
  searchForm.keyword = "";
  searchForm.orderStatus = "";
  page.current = 1;
  getList();
};
const paginationSearch = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
// ------------------ åˆ›å»º/编辑/查看/删除 ------------------
const editVisible = ref(false);
const viewVisible = ref(false);
const editMode = ref("create");
const saving = ref(false);
const editFormRef = ref();
const editForm = reactive({
  orderId: undefined,
  orderCode: "",
  carrierId: undefined,
  carrierName: "",
  origin: "",
  destination: "",
  weight: 0,
  volume: 0,
  estimatedFee: 0,
  orderStatus: 0,
  remark: "",
});
const rules = {
  orderCode: [{ required: true, message: "请输入承运订单号", trigger: "blur" }],
  carrierName: [{ required: true, message: "请输入承运商名称", trigger: "blur" }],
  origin: [{ required: true, message: "请输入发货地", trigger: "blur" }],
  destination: [{ required: true, message: "请输入收货地", trigger: "blur" }],
};
const openCreate = () => {
  editMode.value = "create";
  Object.assign(editForm, {
    orderId: undefined,
    orderCode: `TO-${dayjs().format("YYYYMMDD")}-${String(Math.floor(Math.random() * 9000 + 1000))}`,
    carrierId: undefined,
    carrierName: "",
    origin: "",
    destination: "",
    weight: 0,
    volume: 0,
    estimatedFee: 0,
    orderStatus: 0,
    remark: "",
  });
  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 getCarrierOrderDetail(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 getCarrierOrderDetail(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,
      orderCode: editForm.orderCode,
      carrierId: editForm.carrierId,
      carrierName: editForm.carrierName,
      origin: editForm.origin,
      destination: editForm.destination,
      weight: editForm.weight,
      volume: editForm.volume,
      estimatedFee: editForm.estimatedFee,
      orderStatus: editForm.orderStatus,
      remark: editForm.remark,
    };
    if (editMode.value === "create") {
      await addCarrierOrder(payload);
      ElMessage.success("创建成功");
      page.current = 1;
    } else {
      await updateCarrierOrder(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.orderCode}】?`, "提示", {
    confirmButtonText: "删除",
    cancelButtonText: "取消",
    type: "warning",
  });
  tableLoading.value = true;
  try {
    await deleteCarrierOrder(row.id);
    ElMessage.success("删除成功");
    // åˆ é™¤åŽå¦‚果当前页无数据则回退
    const res = await getCarrierOrderPage(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;
  }
};
// ------------------ å‘货(暂用假提交,等后端接口) ------------------
const deliveryVisible = ref(false);
const deliverySubmitting = ref(false);
const deliveryFormRef = ref();
const deliveryForm = reactive({
  orderId: undefined,
  orderCode: "",
  carrierName: "",
  shipTime: "",
  vehicleNo: "",
  driverName: "",
  driverPhone: "",
  remark: "",
});
const deliveryRules = {
  shipTime: [{ required: true, message: "请选择发货时间", trigger: "change" }],
  vehicleNo: [{ required: true, message: "请输入车牌号", trigger: "blur" }],
};
const openDelivery = (row) => {
  console.log(row)
  Object.assign(deliveryForm, {
    orderId: row.id || row.orderId,
    orderCode: row.orderCode,
    carrierName: row.carrierName,
    shipTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    vehicleNo: "",
    driverName: "",
    driverPhone: "",
    remark: "",
  });
  deliveryVisible.value = true;
  queueMicrotask(() => deliveryFormRef.value?.clearValidate?.());
};
const submitDelivery = async () => {
  deliverySubmitting.value = true;
  try {
    await deliveryFormRef.value?.validate?.();
    console.log(deliveryForm)
    let res = await addDeliveryTrack(deliveryForm)
    if(res?.data){
      ElMessage.success("提交成功");
    }
    deliveryVisible.value = false;
    await getList();
  } catch (e) {
    ElMessage.error(e?.message || e?.msg || "提交失败");
  } finally {
    deliverySubmitting.value = false;
  }
};
onMounted(() => {
  getList();
});
</script>
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,196 @@
<template>
    <el-form :model="form">
        <el-row :gutter="20">
            <el-col :span="12">
                <el-form-item label="采购合同号:">
                    <el-tag size="large">{{ form.purchaseContractNumber }}</el-tag>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="销售合同号:">
                    <el-text>{{ form.salesContractNo }}</el-text>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="含税单价(元):">
                    <el-text type="primary">{{ form.taxInclusiveUnitPrice }}</el-text>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="创建时间:">
                    <el-text>{{ form.createdAt }}</el-text>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="发票号:">
                    <el-input v-model="form.invoiceNumber" />
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="来票数:">
                    <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2"/>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="本次来票金额(元):">
                    <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2"/>
                </el-form-item>
            </el-col>
            <el-col :span="12">
                <el-form-item label="未来票数:">
                    <el-text type="success">{{ form.futureTickets }}</el-text>
                </el-form-item>
            </el-col>
        </el-row>
    </el-form>
</template>
<script setup>
import useFormData from "@/hooks/useFormData";
import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
const { proxy } = getCurrentInstance()
defineOptions({
    name: "来票台账表单",
});
const temFutureTickets = ref(0) // åˆå§‹æœªæ¥ç¥¨æ•°
const initialTicketsNum = ref(0) // åˆå§‹æ¥ç¥¨æ•°
const initialTicketsAmount = ref(0) // åˆå§‹æ¥ç¥¨é‡‘额
const quantity = ref(0) // æ€»æ•°é‡
const { form, resetForm } = useFormData({
    id: undefined,
    purchaseContractNumber: undefined, // é‡‡è´­åˆåŒå·
    salesContractNo: undefined, // é”€å”®åˆåŒå·
    createdAt: undefined, // åˆ›å»ºæ—¶é—´
    invoiceNumber: undefined, // å‘票号
    ticketsNum: undefined, // æ¥ç¥¨æ•°
    ticketsAmount: undefined, // æ¥ç¥¨é‡‘额
    taxInclusiveUnitPrice: undefined, // å«ç¨Žå•ä»·
    ticketRegistrationId: undefined, // å«ç¨Žå•ä»·
});
const load = async (id) => {
    const { code, data } = await getProductRecordById({ id });
    if (code === 200) {
        form.id = data.id;
        form.purchaseContractNumber = data.purchaseContractNumber;
        form.salesContractNo = data.salesContractNo;
        form.createdAt = data.createdAt;
        form.invoiceNumber = data.invoiceNumber;
        form.ticketsNum = data.ticketsNum;
        form.ticketsAmount = data.ticketsAmount ? Number(data.ticketsAmount).toFixed(2) : 0;
        form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
        form.futureTickets = data.futureTickets;
        temFutureTickets.value = data.futureTickets;
        initialTicketsNum.value = data.ticketsNum || 0;
        initialTicketsAmount.value = data.ticketsAmount || 0;
        form.ticketRegistrationId = data.ticketRegistrationId;
        // èŽ·å–æ€»æ•°é‡ï¼Œå¦‚æžœæ•°æ®ä¸­æœ‰ quantity å­—段则使用,否则使用来票数+未来票数
        quantity.value = data.quantity || (Number(data.ticketsNum || 0) + Number(data.futureTickets || 0));
    }
};
const inputTicketsNum = (val) => {
    // ç¡®ä¿å«ç¨Žå•价存在且不为零
    if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
        proxy.$modal.msgWarning("含税单价不能为零或未定义");
        return;
    }
    const newTicketsNum = Number(form.ticketsNum) || 0;
    const currentTicketsNum = Number(initialTicketsNum.value) || 0;
    // è®¡ç®—新增的来票数
    const addedTicketsNum = newTicketsNum - currentTicketsNum;
    // è®¡ç®—新的未来票数 = åˆå§‹æœªæ¥ç¥¨æ•° - æ–°å¢žçš„æ¥ç¥¨æ•°
    const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
    // éªŒè¯ï¼šæ–°çš„æ¥ç¥¨æ•° + æ–°çš„æœªæ¥ç¥¨æ•° â‰¤ quantity
    if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
        proxy.$modal.msgWarning(`来票数+未来票数不能大于总数量(${quantity.value})`);
        // é™åˆ¶æ¥ç¥¨æ•°ï¼Œä½¿å…¶æ»¡è¶³ï¼šæ¥ç¥¨æ•° + æœªæ¥ç¥¨æ•° â‰¤ quantity
        // æœ€å¤§æ¥ç¥¨æ•° = quantity - åˆå§‹æœªæ¥ç¥¨æ•° + åˆå§‹æ¥ç¥¨æ•°
        const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
        form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
        // é‡æ–°è®¡ç®—
        const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
        const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
        form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
        const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
        form.ticketsAmount = Number(ticketsAmount.toFixed(2));
        return;
    }
    // æ£€æŸ¥æ–°å¢žçš„æ¥ç¥¨æ•°æ˜¯å¦å¤§äºŽåˆå§‹æœªæ¥ç¥¨æ•°
    if (addedTicketsNum > Number(temFutureTickets.value)) {
        proxy.$modal.msgWarning("新增开票数不得大于未开票数");
        form.ticketsNum = Number(initialTicketsNum.value) + Number(temFutureTickets.value);
    }
    // ç¡®ä¿æ‰€æœ‰æ•°å€¼éƒ½è½¬æ¢ä¸ºæ•°å­—类型进行计算
    const finalTicketsNum = Number(form.ticketsNum) || 0;
    const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
    const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
    const ticketsAmount = finalTicketsNum * Number(form.taxInclusiveUnitPrice);
    form.futureTickets = Number(finalFutureTickets.toFixed(2));
    form.ticketsAmount = Number(ticketsAmount.toFixed(2));
};
const inputTicketsAmount = (val) => {
    // ç¡®ä¿å«ç¨Žå•价存在且不为零
    if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
        proxy.$modal.msgWarning("含税单价不能为零或未定义");
        return;
    }
    const newTicketsAmount = Number(val) || 0;
    // è®¡ç®—新的来票数
    const newTicketsNum = newTicketsAmount / Number(form.taxInclusiveUnitPrice);
    const currentTicketsNum = Number(initialTicketsNum.value) || 0;
    // è®¡ç®—新增的来票数
    const addedTicketsNum = newTicketsNum - currentTicketsNum;
    // è®¡ç®—新的未来票数 = åˆå§‹æœªæ¥ç¥¨æ•° - æ–°å¢žçš„æ¥ç¥¨æ•°
    const newFutureTickets = Number(temFutureTickets.value) - addedTicketsNum;
    // éªŒè¯ï¼šæ–°çš„æ¥ç¥¨æ•° + æ–°çš„æœªæ¥ç¥¨æ•° â‰¤ quantity
    if (newTicketsNum + newFutureTickets > Number(quantity.value)) {
        proxy.$modal.msgWarning(`来票数+未来票数不能大于总数量(${quantity.value})`);
        // é™åˆ¶æ¥ç¥¨æ•°ï¼Œä½¿å…¶æ»¡è¶³ï¼šæ¥ç¥¨æ•° + æœªæ¥ç¥¨æ•° â‰¤ quantity
        const maxTicketsNum = Number(quantity.value) - Number(temFutureTickets.value) + Number(initialTicketsNum.value);
        form.ticketsNum = Math.max(0, Math.min(maxTicketsNum, newTicketsNum));
        form.ticketsAmount = Number((form.ticketsNum * Number(form.taxInclusiveUnitPrice)).toFixed(2));
        const recalculatedAddedTicketsNum = Number(form.ticketsNum) - Number(initialTicketsNum.value);
        const recalculatedFutureTickets = Number(temFutureTickets.value) - recalculatedAddedTicketsNum;
        form.futureTickets = Number(recalculatedFutureTickets.toFixed(2));
        return;
    }
    // æ£€æŸ¥æ–°å¢žçš„æ¥ç¥¨é‡‘额是否大于初始未来票数对应的金额
    const maxAddedAmount = Number(temFutureTickets.value * form.taxInclusiveUnitPrice);
    if (addedTicketsNum > 0 && addedTicketsNum * Number(form.taxInclusiveUnitPrice) > maxAddedAmount) {
        proxy.$modal.msgWarning("新增来票金额不得大于未开票金额");
        form.ticketsAmount = Number((initialTicketsAmount.value + maxAddedAmount).toFixed(2));
        form.ticketsNum = Number((currentTicketsNum + Number(temFutureTickets.value)).toFixed(2));
        form.futureTickets = 0;
        return;
    }
    // ç¡®ä¿æ‰€æœ‰æ•°å€¼éƒ½è½¬æ¢ä¸ºæ•°å­—类型进行计算
    const finalTicketsNum = Number(newTicketsNum.toFixed(2));
    const finalAddedTicketsNum = finalTicketsNum - Number(initialTicketsNum.value);
    const finalFutureTickets = Number(temFutureTickets.value) - finalAddedTicketsNum;
    form.ticketsNum = finalTicketsNum;
    form.futureTickets = Number(finalFutureTickets.toFixed(2));
};
defineExpose({
    load,
    form,
    resetForm,
});
</script>
<style lang="scss" scoped></style>
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
<template>
  <el-dialog :title="modalOptions.title" v-model="visible" @close="close">
    <EditForm ref="editFormRef" />
    <template #footer>
            <el-button type="primary" :loading="loading" @click="sendForm">
                {{ modalOptions.confirmText }}
            </el-button>
      <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import { useModal } from "@/hooks/useModal";
import EditForm from "../Form/EditForm.vue";
import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger";
import { ElMessage } from "element-plus";
defineOptions({
  name: "来票台账编辑",
});
const emits = defineEmits(["success"]);
const saleLedgerProjectId = ref('')
const editFormRef = ref();
const {
  id,
  visible,
  loading,
  openModal,
  modalOptions,
  handleConfirm,
  closeModal,
} = useModal({ title: "来票台账" });
const open = async (row) => {
  openModal(row.id);
    saleLedgerProjectId.value = row.saleLedgerProjectId;
  await nextTick();
  editFormRef.value.load(row.id);
};
const close = () => {
  editFormRef.value.resetForm();
  closeModal();
};
const sendForm = async () => {
  const form = editFormRef.value.form;
    form.saleLedgerProjectId = saleLedgerProjectId.value;
  const { code } = await updateRegistration(form);
  if (code === 200) {
    emits("success");
    ElMessage({ message: "操作成功", type: "success" });
    close();
  }
};
defineExpose({
  open,
});
</script>
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/Modal/UploadModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
<template>
  <el-dialog v-model="upload.open" :title="upload.title" :width="500">
    <FileUpload
      ref="fileUploadRef"
      accept=".xlsx, .xls, .pdf"
      :headers="upload.headers"
      :autoUpload="true"
      :action="upload.url"
      :disabled="upload.isUploading"
      :showTip="false"
      :limit="10"
      @success="handleFileSuccess"
      @remove="removeFile"
    />
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
        <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { reactive } from "vue";
import { getToken } from "@/utils/auth.js";
import { FileUpload } from "@/components/Upload";
import { ElMessage } from "element-plus";
import { ref } from "vue";
import useFormData from "@/hooks/useFormData";
defineOptions({
  name: "来票台账附件补充",
});
const { form, resetForm } = useFormData({
  id: undefined,
  tempFileIds: [],
});
const emits = defineEmits(["uploadSuccess"]);
const fileUploadRef = ref();
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
});
// ç‚¹å‡»å¯¼å…¥
const handleImport = (id) => {
  form.id = id;
  upload.open = true;
  upload.title = "来票台账附件补充";
};
const submitFileForm = () => {
  emits("uploadSuccess", form);
  resetForm();
  upload.open = false;
  // æ¸…空文件列表
  fileUploadRef.value.fileList = [];
};
const handleFileSuccess = (response) => {
  if (response.code == 200) {
        form.tempFileIds.push(response.data.tempId);
        console.log('form',form);
    ElMessage({ message: "导入成功", type: "success" });
  } else {
    ElMessage({ message: response.msg, type: "error" });
  }
};
const removeFile = (file) => {
  const { tempId } = file.response.data;
  form.tempFileIds = form.tempFileIds.filter((item) => item !== tempId);
};
defineExpose({
  handleImport,
});
</script>
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="采购合同号">
        <el-input
          v-model="filters.purchaseContractNumber"
          style="width: 240px"
          placeholder="请输入"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="供应商">
        <el-input
          v-model="filters.supplierName"
          style="width: 240px"
          placeholder="请输入"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="来票日期">
        <el-date-picker
          style="width: 240px"
          v-model="filters.createdAt"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          clearable
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters"> é‡ç½® </el-button>
        <el-button @click="handleOut">导出</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableLoading="loading"
        :tableData="dataList"
        :isSelection="true"
        height="calc(100vh - 19.5em)"
        :isShowSummary="true"
        :summaryMethod="summarizeMainTable"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #commonFilesRef="{ row }">
          <el-dropdown @command="(command) => handleCommand(command, row)">
            <el-button link :icon="Files" type="danger"> é™„ä»¶ </el-button>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item
                  v-if="row.commonFiles.length !== 0"
                  :icon="Download"
                  command="download"
                >
                  ä¸‹è½½
                </el-dropdown-item>
                <el-dropdown-item :icon="Upload" command="upload">
                  ä¸Šä¼ 
                </el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </template>
        <template #operation="{ row }">
          <el-button
            type="primary"
            text
            @click="openEdit(row)"
                        :disabled="row.issUerId !== userStore.id"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
                        :disabled="row.issUerId !== userStore.id"
            @click="handleDelete(row)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
    <UploadModal ref="modalRef" @uploadSuccess="uploadSuccess"></UploadModal>
    <EditModal ref="editmodalRef" @success="getTableData"></EditModal>
  </div>
</template>
<script setup>
import { ref, getCurrentInstance } from "vue";
import { usePaginationApi } from "@/hooks/usePaginationApi";
import {
  Files,
  Download,
  Search,
  Upload,
  EditPen,
} from "@element-plus/icons-vue";
import {
    delRegistration,
    productRecordPage,
    productUploadFile,
} from "@/api/procurementManagement/procurementInvoiceLedger.js";
import { onMounted } from "vue";
import { ElMessageBox } from "element-plus";
import UploadModal from "./Modal/UploadModal.vue";
import EditModal from "./Modal/EditModal.vue";
import useUserStore from "@/store/modules/user.js";
import {delInvoiceLedgerByRegProductId} from "@/api/salesManagement/invoiceLedger.js";
const userStore = useUserStore();
defineOptions({
  name: "来票台账",
});
const modalRef = ref();
const editmodalRef = ref();
const { proxy } = getCurrentInstance();
const multipleVal = ref([]);
const {
  loading,
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  productRecordPage,
  {
    purchaseContractNumber: undefined, // é‡‡è´­åˆåŒå·
    supplierName: undefined, // ä¾›åº”商
    createdAt: [], // æ¥ç¥¨æ—¥æœŸ
  },
  [
    {
      label: "采购合同号",
      prop: "purchaseContractNumber",
      width: 150,
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: 150,
    },
    {
      label: "项目名称",
      prop: "projectName",
      width: 240,
    },
    {
      label: "供应商名称",
      prop: "supplierName",
      width: 240,
    },
    {
      label: "产品大类",
      prop: "productCategory",
      width: 150,
    },
    {
      label: "规格型号",
      prop: "specificationModel",
      width: 150,
    },
    {
      label: "发票号",
      prop: "invoiceNumber",
      width: 200,
    },
    {
      label: "合同金额(元)",
      prop: "taxInclusiveTotalPrice",
      width: 200,
      formatData: (cell) => {
        return cell ? parseFloat(cell).toFixed(2) : 0;
      },
    },
    {
      label: "本次来票数",
      prop: "ticketsNum",
      width: 110,
    },
    {
      label: "来票日期",
      prop: "createdAt",
      width: 110,
    },
    {
      label: "来票金额(元)",
      prop: "ticketsAmount",
      width: 200,
      formatData: (cell) => {
        return cell ? parseFloat(cell).toFixed(2) : 0;
      },
    },
    {
      label: "不含税金额",
      prop: "unTicketsPrice",
      width: 200,
      formatData: (cell) => {
        return cell ? parseFloat(cell).toFixed(2) : 0;
      },
    },
    {
      label: "增值税",
      prop: "invoiceAmount",
      width: 200,
    },
    {
      label: "录入人",
      prop: "issUer",
      width: 200,
    },
    {
      label: "附件",
      align: "center",
      prop: "commonFiles",
      dataType: "slot",
            fixed: "right",
      slot: "commonFilesRef",
      width: 120,
    },
    {
      fixed: "right",
      width: 150,
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
    },
  ],
  {},
  {
    createdAt: (aim) => ({
      createdAtStart: aim ? aim[0] : undefined,
      createdAtEnd: aim ? aim[1] : undefined,
    }),
  }
);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveTotalPrice",
      "ticketsAmount",
      "unTicketsPrice",
      "invoiceAmount",
    ],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
const handleSelectionChange = (val) => {
  multipleVal.value = val;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/purchase/registration/export", {}, "来票登记.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
const handleFiles = (fileList) => {
  fileList.forEach((e) => {
    proxy.$download.name(e.url);
  });
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
    pagination.pageSize = limit;
  onCurrentChange(page);
};
const handleCommand = (command, row) => {
  switch (command) {
    case "download":
      handleFiles(row.commonFiles);
      break;
    case "upload":
      console.log(row.commonFiles);
      openUoload(row.ticketRegistrationId);
      break;
  }
};
const openUoload = (id) => {
  modalRef.value.handleImport(id);
};
const openEdit = (row) => {
  editmodalRef.value.open(row);
};
// ä¸Šä¼ æˆåŠŸåŽåšä»€ä¹ˆ
const uploadSuccess = async (data) => {
  const { code } = await productUploadFile({
    ticketRegistrationId: data.id,
    tempFileIds: data.tempFileIds,
  });
  if (code === 200) {
    proxy.$modal.msgSuccess("提交成功");
    getTableData();
  }
};
// åˆ é™¤
const handleDelete = (row) => {
    let ids = [];
    ids.push(row.id);
    ElMessageBox.confirm("该开票台账将被删除,是否确认删除", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            loading.value = true;
            delRegistration(ids).then((res) => {
                getTableData();
            });
            loading.value = false;
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.tagBox {
  margin-top: 4px;
}
</style>
src/views/inventoryManagement/procurementManagement/procurementInvoiceLedger/indexOld.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,313 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">采购合同号:</span>
        <el-input
          v-model="searchForm.purchaseContractNumber"
          style="width: 240px"
          placeholder="请输入"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <span class="search_title" style="margin-left: 10px">供应商:</span>
        <el-input
          v-model="searchForm.supplierName"
          style="width: 240px"
          placeholder="请输入"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
        <span class="search_title" style="margin-left: 10px">来票日期:</span>
        <el-date-picker
          style="width: 240px"
          v-model="searchForm.issueDate"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          clearable
          @change="changeDateRange"
          @clear="clearRange"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
          >搜索</el-button
        >
      </div>
      <div>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        :expand-row-keys="expandedRowKeys"
        :row-key="(row) => row.id"
        show-summary
        :summary-method="summarizeMainTable"
        @expand-change="expandChange"
        height="calc(100vh - 18.5em)"
        stripe
      >
        <el-table-column align="center" label="序号" type="index" width="55" />
        <el-table-column type="expand">
          <template #default="props">
            <el-table
              :data="props.row.children"
              border
              show-summary
              :summary-method="summarizeChildrenTable"
              stripe
            >
              <el-table-column
                align="center"
                label="序号"
                type="index"
                width="60"
              />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column
                label="含税单价(元)"
                prop="taxInclusiveUnitPrice"
                :formatter="formattedNumber"
              />
              <el-table-column
                label="含税总价(元)"
                prop="taxInclusiveTotalPrice"
                :formatter="formattedNumber"
              />
              <el-table-column
                label="不含税总价(元)"
                prop="taxExclusiveTotalPrice"
                :formatter="formattedNumber"
              />
              <el-table-column label="本次来票数" prop="ticketsNum" />
              <el-table-column
                label="本次来票金额(元)"
                prop="ticketsAmount"
                :formatter="formattedNumber"
              />
              <el-table-column label="未来票数" prop="futureTickets" />
              <el-table-column
                label="未来票金额(元)"
                prop="futureTicketsAmount"
                :formatter="formattedNumber"
              />
            </el-table>
          </template>
        </el-table-column>
        <el-table-column
          label="采购合同号"
          prop="purchaseContractNumber"
          show-overflow-tooltip
        />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          show-overflow-tooltip
        />
        <el-table-column
          label="供应商名称"
          prop="supplierName"
          show-overflow-tooltip
        />
        <el-table-column
          label="发票号"
          prop="invoiceNumber"
          show-overflow-tooltip
        />
        <el-table-column
          label="合同金额(元)"
          prop="invoiceAmount"
          show-overflow-tooltip
          :formatter="formattedNumber"
        />
        <el-table-column label="开票人" prop="issUer" show-overflow-tooltip />
        <el-table-column
          label="开票日期"
          prop="issueDate"
          show-overflow-tooltip
        />
      </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>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import {
  invoiceListPage,
  productRecordList,
} from "@/api/procurementManagement/procurementInvoiceLedger.js";
import dayjs from "dayjs";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const data = reactive({
  searchForm: {
    purchaseContractNumber: "",
    supplierName: "",
    issueDate: [
      dayjs().startOf("month").format("YYYY-MM-DD"),
      dayjs().endOf("month").format("YYYY-MM-DD"),
    ],
    issueDateStart: dayjs().startOf("month").format("YYYY-MM-DD"),
    issueDateEnd: dayjs().endOf("month").format("YYYY-MM-DD"),
  },
  form: {
    issueDate: "", // å¼€ç¥¨æ—¥æœŸ
    purchaseLedgerId: "",
    purchaseLedgerNo: "",
    issUerId: "", // å¼€ç¥¨äººid
    issUer: "", // å¼€ç¥¨äººå§“名
  },
  rules: {
    purchaseLedgerId: [
      { required: true, message: "请选择", trigger: "change" },
    ],
  },
});
const { searchForm } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { issueDate, ...rest } = searchForm.value;
  invoiceListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.records;
      tableData.value.map((item) => {
        item.children = [];
      });
      total.value = res.total;
      expandedRowKeys.value = [];
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      productRecordList({ id: row.id }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res;
        }
        expandedRowKeys.value.push(row.id);
      });
    } catch (error) {
      console.log(error);
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ["invoiceAmount"], {
    ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
  });
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
      "ticketsNum",
      "ticketsAmount",
      "futureTickets",
      "futureTicketsAmount",
    ],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/purchase/registration/export", {}, "来票登记.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
const changeDateRange = (date) => {
  if (date) {
    searchForm.value.receiptPaymentDateStart = date[0];
    searchForm.value.receiptPaymentDateEnd = date[1];
    getList();
  }
};
const clearRange = () => {
  searchForm.value.issueDate = [];
  searchForm.value.issueDateStart = undefined;
  searchForm.value.issueDateEnd = undefined;
  getList();
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/procurementManagement/procurementLedger/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="150" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
          <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import filePreview from '@/components/filePreview/index.vue'
import { delCommonFile } from '@/api/publicApi/commonFile.js'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
// åˆ é™¤é™„ä»¶
const handleDelete = (row) => {
  ElMessageBox.confirm(`确认删除附件"${row.name}"吗?`, '删除确认', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    delCommonFile([row.id]).then(() => {
      ElMessage.success('删除成功')
      // ä»Žåˆ—表中移除已删除的附件
      const index = tableData.value.findIndex(item => item.id === row.id)
      if (index !== -1) {
        tableData.value.splice(index, 1)
      }
    }).catch(() => {
      ElMessage.error('删除失败')
    })
  }).catch(() => {
    proxy.$modal.msg('已取消删除')
  })
}
defineExpose({
  open
})
</script>
<style></style>
src/views/inventoryManagement/procurementManagement/procurementLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,528 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">承运商档案:</span>
        <el-input
            v-model="searchForm.supplierName"
            style="width: 240px"
            placeholder="输入承运商名称搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')"
        >新增承运商</el-button
        >
        <el-button @click="handleOut">导出</el-button>
        <el-button type="info" plain icon="Upload" @click="handleImport"
        >导入</el-button
        >
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
    </div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增承运商信息' : '编辑承运商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
          :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="承运商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                  v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input
                  v-model="form.companyAddress"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input
                  v-model="form.companyPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="开户行:" prop="bankAccountName">
              <el-input
                  v-model="form.bankAccountName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="账号:" prop="bankAccountNum">
              <el-input
                  v-model="form.bankAccountNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactUserName">
              <el-input
                  v-model="form.contactUserName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactUserPhone">
              <el-input
                  v-model="form.contactUserPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainUserId">
              <el-select
                  v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                    v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintainTime">
              <el-date-picker
                  style="width: 100%"
                  v-model="form.maintainTime"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
                  disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ‰¿è¿å•†å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog
        :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
          :action="upload.url + '?updateSupport=' + upload.updateSupport"
          :disabled="upload.isUploading"
          :on-progress="handleFileUploadProgress"
          :on-success="handleFileSuccess"
          :auto-upload="false"
          drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
            <!-- <el-link
              type="primary"
              :underline="false"
              style="font-size: 12px; vertical-align: baseline"
              @click="importTemplate"
              >下载模板</el-link
            > -->
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { delSupplier } from "@/api/basicData/supplierManageFile.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  addSupplier,
  getSupplier,
  listSupplier,
  updateSupplier,
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const tableColumn = ref([
  {
    label: "承运商名称",
    prop: "supplierName",
    width: 250,
  },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
  },
  {
    label: "公司地址",
    prop: "companyAddress",
    width: 220,
  },
  {
    label: "联系方式",
    prop: "companyPhone",
    width:150
  },
  {
    label: "开户行",
    prop: "bankAccountName",
    width: 220,
  },
  {
    label: "账号",
    prop: "bankAccountNum",
    width: 220,
  },
  {
    label: "联系人",
    prop: "contactUserName",
  },
  {
    label: "联系电话",
    prop: "contactUserPhone",
    width: 150,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
  },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
        disabled: (row) => {
          return row.maintainUserName !== userStore.nickName
        }
      },
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "",
  },
  form: {
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
  },
  rules: {
    supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNum: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountName: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountNum: [{ required: true, message: "请输入", trigger: "blur" }],
    contactUserName: [{ required: false, message: "请输入", trigger: "blur" }],
    contactUserPhone: [{ required: false, message: "请输入", trigger: "blur" }],
    maintainUserId: [{ required: false, message: "请选择", trigger: "change" }],
    maintainTime: [{ required: false, message: "请选择", trigger: "change" }],
  },
});
const { searchForm, form, rules } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  console.log(upload.url + '?updateSupport=' + upload.updateSupport)
  proxy.$refs["uploadRef"].submit();
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
  });
};
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(承运商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(承运商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 1,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "承运商导入";
  upload.open = true;
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.open = false;
  upload.isUploading = false;
  proxy.$refs["uploadRef"].handleRemove(file);
  getList();
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      form.value = { ...res.data };
    });
  }
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
      }
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", {}, "承运商档案.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
    if (unauthorizedData.length > 0) {
      proxy.$modal.msgWarning("不可删除他人维护的数据");
      return;
    }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
            .then((res) => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .finally(() => {
              tableLoading.value = false;
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss"></style>