zouyu
2026-01-10 8bc17a9ea84a6af0b7d01e451c702f404a3ff895
Merge branch 'dev_tide' into dev_tide_cbsglxt

# Conflicts:
# .env.production
# index.html
# src/store/modules/user.js
# src/views/contractor/evaluateTemplate/index.vue
# src/views/environmentAccess/accessManagement/index.vue
# src/views/environmentAccess/intelligentInspectionManagement/index.vue
# src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
# src/views/environmentAccess/vehicleInformationCollection/index.vue
# src/views/financialManagement/expenseManagement/index.vue
# src/views/financialManagement/financialStatements/index.vue
# src/views/index.vue
# src/views/inventoryManagement/dispatchLog/index.vue
# src/views/inventoryManagement/issueManagement/index.vue
# src/views/inventoryManagement/receiptManagement/index.vue
# src/views/inventoryManagement/stockManagement/index.vue
# src/views/inventoryManagement/stockWarning/index.vue
# src/views/procurementManagement/paymentEntry/index.vue
# src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
# src/views/procurementManagement/procurementLedger/index.vue
# src/views/productionManagement/operationScheduling/components/formDia.vue
# src/views/productionManagement/operationScheduling/index.vue
# src/views/productionManagement/productionCosting/index.vue
# src/views/productionManagement/productionDispatching/components/formDia.vue
# src/views/productionManagement/productionDispatching/index.vue
# src/views/productionManagement/productionOrder/index.vue
# src/views/productionManagement/productionReporting/index.vue
# src/views/salesManagement/receiptPayment/index.vue
# src/views/salesManagement/receiptPaymentHistory/index.vue
# src/views/salesManagement/salesLedger/index.vue
# src/views/tideLogin.vue
# vite.config.js
已添加36个文件
已修改8个文件
15676 ■■■■■ 文件已修改
index.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockIn.js 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockManage.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockOut.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockReport.js 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockWarning.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lavorissce/ledger.js 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/accessManagement/index.vue 347 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/intelligentInspectionManagement/index.vue 581 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue 633 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/environmentAccess/vehicleInformationCollection/index.vue 577 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/accounting/index.vue 547 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/expenseManagement/index.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/financialStatements/index.vue 507 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/inventoryAccounting/index.vue 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 1361 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/index.vue 371 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/index.vue 309 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/issueManagement/index.vue 337 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDia.vue 401 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/index.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 387 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockReport/index.vue 713 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockWarning/index.vue 943 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentEntry/index.vue 575 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 1131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/components/formDia.vue 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/operationScheduling/index.vue 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/components/formDia.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionDispatching/index.vue 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 374 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/index.vue 401 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 640 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentHistory/index.vue 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 1018 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -7,7 +7,7 @@
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/favicon.ico">
  <title>承包商管理系统</title>
  <title>%VITE_APP_TITLE%</title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
src/api/inventoryManagement/stockIn.js
@@ -9,6 +9,41 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“信息列表
export const getStockInPageByProduction = (params) => {
    return request({
        url: "/stockin/listPageByProduction",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询自定义入库信息列表
export const getStockInPageByCustom = (params) => {
    return request({
        url: "/stockmanagement/listPageByCustom",
        method: "get",
        params,
    });
};
// å…¥åº“管理-查询自定义入库信息列表
export const getInPageByCustom = (params) => {
    return request({
        url: "/stockin/listPageByCustom",
        method: "get",
        params,
    });
};
// å‡ºåº“台账-查询生产出库信息列表
export const getStockInPageByProduct = (params) => {
    return request({
        url: "/stockmanagement/listPageByProduct",
        method: "get",
        params,
    });
};
// ä¿®æ”¹å…¥åº“存信息
export const updateStockIn = (data) => {
    return request({
@@ -26,6 +61,14 @@
        data,
    });
};
// ä¿®æ”¹ææ–™åº“存信息
export const updateManagementByCustom = (data) => {
    return request({
        url: "/stockin/updateManagementByCustom ",
        method: "post",
        data,
    });
};
// æ–°å¢žå•†å“å…¥åº“信息
export function addSutockIn(data) {
@@ -36,6 +79,32 @@
    })
}
// æ–°å¢žè‡ªå®šä¹‰å…¥åº“信息
export function addStockInCustom(data) {
    return request({
        url: '/stockin/addCustom',
        method: 'post',
        data: data
    })
}
// ç¼–辑自定义入库信息
export function updateStockInCustom(data) {
    return request({
        url: '/stockin/updateCustom',
        method: 'post',
        data: data
    })
}
// ç¼–辑成品入库信息
export function updateProduct(data) {
    return request({
        url: '/stockin/update',
        method: 'post',
        data: data
    })
}
// åˆ é™¤å…¥åº“信息
export function delStockIn(ids) {
    return request({
@@ -45,6 +114,15 @@
    })
}
// åˆ é™¤è‡ªå®šä¹‰å…¥åº“信息
export function delStockInCustom(ids) {
    return request({
        url: '/stockin/delteCustom',
        method: 'post',
        data: ids
    })
}
// å¯¼å‡ºå…¥åº“信息
export function exportStockIn(query) {
    return request({
src/api/inventoryManagement/stockManage.js
@@ -9,6 +9,24 @@
    });
};
// æŸ¥è¯¢ç”Ÿäº§å…¥åº“库存信息列表
export const getStockManagePageByProduction = (params) => {
    return request({
        url: "/stockin/listPageCopyByProduction",
        method: "get",
        params,
    });
};
// æŸ¥è¯¢è‡ªå®šä¹‰å…¥åº“库存信息列表
export const getStockManagePageByCustom = (params) => {
    return request({
        url: "/stockin/listPageCopyByCustom",
        method: "get",
        params,
    });
};
// ä¿®æ”¹åº“存信息
export const updateStockManage = (data) => {
@@ -38,7 +56,7 @@
    })
}
//出库接口
// å‡ºåº“管理-领用接口
export const stockOut = (data) => {
    return request({
        url: '/stockmanagement/stockout',
src/api/inventoryManagement/stockOut.js
@@ -1,6 +1,6 @@
import request from "@/utils/request";
//查询出库列表
// å‡ºåº“台账-采购出库查询出库列表
export const getStockOutPage = (params) => {
    return request({
        url: "/stockmanagement/listPage",
@@ -35,13 +35,3 @@
        data: ids
    })
}
//导出出库信息
export const exportStockOut = (query) => {
    return request({
        url: '/stockmanagement/export',
        method: 'get',
        params: query,
        responseType: 'blob'
    })
}
src/api/inventoryManagement/stockReport.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
import request from "@/utils/request";
// èŽ·å–åº“å­˜æ—¥æŠ¥ç»Ÿè®¡
export const getStockDailyReport = (params) => {
    return request({
        url: "/stockin/getReportList",
        method: "get",
        params,
    });
};
// èŽ·å–åº“å­˜æœˆæŠ¥ç»Ÿè®¡
export const getStockMonthlyReport = (params) => {
    return request({
        url: "/stockin/getReportList",
        method: "get",
        params,
    });
};
// èŽ·å–ä½œä¸šæŠ¥è¡¨ç»Ÿè®¡
export const getWorkReport = (params) => {
    return request({
        url: "/stockin/getReportList",
        method: "get",
        params,
    });
};
// èŽ·å–åº“å­˜è¿›å‡ºå­˜ç»Ÿè®¡
export const getStockInOutReport = (params) => {
    return request({
        url: "/stockin/getReportList",
        method: "get",
        params,
    });
};
// å¯¼å‡ºåº“存报表
export const exportStockReport = (params) => {
    return request({
        url: "/stockin/exportCopy",
        method: "post",
        params,
        responseType: 'blob'
    });
};
// èŽ·å–åº“å­˜è¶‹åŠ¿æ•°æ®
export const getStockTrendData = (params) => {
    return request({
        url: "/stockreport/trend",
        method: "get",
        params,
    });
};
src/api/inventoryManagement/stockWarning.js
@@ -1,11 +1,14 @@
import request from "@/utils/request";
// æŸ¥è¯¢å‚¨æ°”罐预警列表
export const getStockWarningPage = (params) => {
export const getStockWarningPage = (page, params) => {
    return request({
        url: "/gasTankWarning/listPage",
        method: "get",
        params,
        params: {
            ...page,
            ...params
        },
    });
};
@@ -14,7 +17,7 @@
    return request({
        url: "/gasTankWarning/add",
        method: "post",
        data,
        data: data,
    });
};
@@ -22,8 +25,8 @@
export const updateStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/update",
        method: "put",
        data,
        method: "post",
        data: data,
    });
};
@@ -32,7 +35,7 @@
    return request({
        url: "/gasTankWarning/delete",
        method: "delete",
        data: { ids },
        data: ids,
    });
};
src/api/lavorissce/ledger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function listPage(query) {
    return request({
        url: '/lavorIssue/listPage',
        method: 'get',
        params: query
    })
}
// åˆ†é¡µæŸ¥è¯¢
export function statistics(params) {
    return request({
        url: '/lavorIssue/statistics',
        method: 'get',
        params
    })
}
export function statisticsList(params) {
    return request({
        url: '/lavorIssue/statisticsList',
        method: 'get',
        params
    })
}
// æ·»åŠ 
export function add(data) {
    return request({
        url: '/lavorIssue/add',
        method: 'post',
        data
    })
}
// ä¿®æ”¹
export function update(data) {
    return request({
        url: '/lavorIssue/update',
        method: 'post',
        data
    })
}
// åˆ é™¤
export function deleteLedger(data) {
    return request({
        url: '/lavorIssue/delete',
        method: 'delete',
        data
    })
}
src/api/productionManagement/productionOrder.js
@@ -4,7 +4,7 @@
// åˆ†é¡µæŸ¥è¯¢
export function schedulingListPage(query) {
  return request({
    url: "/salesLedger/scheduling/listPage",
    url: "/productionOrder/listPage",
    method: "get",
    params: query,
  });
@@ -16,4 +16,29 @@
    method: "post",
    data: query,
  });
}
// æ–°å¢žç”Ÿäº§è®¢å•
export function addProductionOrder(query) {
  return request({
    url: "/productionOrder/addProductionOrder",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹ç”Ÿäº§è®¢å•
export function updateProductionOrder(query) {
  return request({
    url: "/productionOrder/updateProductionOrder",
    method: "post",
    data: query,
  });
}
// åˆ é™¤ç”Ÿäº§è®¢å•
export function deleteProductionOrder(query) {
  return request({
    url: "/productionOrder/deleteProductionOrder",
    method: "delete",
    data: query,
  });
}
src/utils/index.js
@@ -396,3 +396,16 @@
export function isEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}
/**
 * èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
 * @returns {string} æ ¼å¼åŒ–的日期字符串
 */
export 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}`;
}
src/views/environmentAccess/accessManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,347 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="门禁名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入门禁名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="门禁状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择门禁状态"
          clearable
          @change="getTableData"
        >
          <el-option label="正常" value="1"></el-option>
          <el-option label="异常" value="0"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="区域">
        <el-input
          v-model="filters.area"
          style="width: 240px"
          placeholder="请输入区域"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button type="danger" icon="Delete" :disabled="multipleList.length <= 0" @click="batchDelete">批量删除</el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- æ–°å¢žç¼–辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="formData" label-width="100px">
        <el-form-item label="门禁名称" required>
          <el-input v-model="formData.name" placeholder="请输入门禁名称"></el-input>
        </el-form-item>
        <el-form-item label="区域" required>
          <el-input v-model="formData.area" placeholder="请输入区域"></el-input>
        </el-form-item>
        <el-form-item label="位置" required>
          <el-input v-model="formData.location" placeholder="请输入具体位置"></el-input>
        </el-form-item>
        <el-form-item label="状态" required>
          <el-select v-model="formData.status" placeholder="请选择状态">
            <el-option label="正常" value="1"></el-option>
            <el-option label="异常" value="0"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="描述">
          <el-input v-model="formData.description" type="textarea" rows="3" placeholder="请输入描述信息"></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="saveData">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import { Search, Plus, Delete } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import dayjs from 'dayjs';
// å®šä¹‰å‡æ•°æ®
const mockData = [
  { id: 1, name: '东门门禁', area: '生产区', location: '东门入口', status: '1', lastUpdate: '2025-12-30 08:30:00', description: '主要用于员工上下班通行' },
  { id: 2, name: '西门门禁', area: '仓储区', location: '西门入口', status: '1', lastUpdate: '2025-12-30 09:15:00', description: '主要用于物流车辆通行' },
  { id: 3, name: '南门门禁', area: '办公区', location: '南门入口', status: '0', lastUpdate: '2025-12-30 10:20:00', description: '主要用于访客通行,当前故障' },
  { id: 4, name: '北门门禁', area: '生产区', location: '北门入口', status: '1', lastUpdate: '2025-12-30 11:45:00', description: '主要用于原材料运输车辆通行' },
  { id: 5, name: '中控室门禁', area: '中控区', location: '中控室门口', status: '1', lastUpdate: '2025-12-30 13:20:00', description: '仅限授权人员进入' },
];
// å“åº”式数据
const filters = reactive({
  name: '',
  status: '',
  area: ''
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0
});
const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref('新增门禁');
const formData = reactive({
  id: '',
  name: '',
  area: '',
  location: '',
  status: '1',
  description: ''
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: '门禁名称',
    align: 'center',
    prop: 'name',
  },
  {
    label: '区域',
    align: 'center',
    prop: 'area',
  },
  {
    label: '位置',
    align: 'center',
    prop: 'location',
  },
  {
    label: '状态',
    align: 'center',
    prop: 'status',
    formatter: (row) => {
      return row.status === '1' ? '<el-tag type="success">正常</el-tag>' : '<el-tag type="danger">异常</el-tag>';
    }
  },
  {
    label: '最后更新',
    align: 'center',
    prop: 'lastUpdate',
  },
  {
    label: '描述',
    align: 'center',
    prop: 'description',
  },
  {
    dataType: 'action',
    label: '操作',
    align: 'center',
    fixed: 'right',
    width: 140,
    operation: [
      {
        name: '编辑',
        type: 'text',
        clickFun: (row) => {
          edit(row);
        },
      },
      {
        name: '删除',
        type: 'text',
        clickFun: (row) => {
          deleteRow(row.id);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockData.filter(item => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const areaMatch = !filters.area || item.area.includes(filters.area);
    return nameMatch && statusMatch && areaMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = '';
  filters.status = '';
  filters.area = '';
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// å¤šé€‰å¤„理
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
// æ–°å¢ž
const add = () => {
  dialogTitle.value = '新增门禁';
  formData.id = '';
  formData.name = '';
  formData.area = '';
  formData.location = '';
  formData.status = '1';
  formData.description = '';
  dialogVisible.value = true;
};
// ç¼–辑
const edit = (row) => {
  dialogTitle.value = '编辑门禁';
  formData.id = row.id;
  formData.name = row.name;
  formData.area = row.area;
  formData.location = row.location;
  formData.status = row.status;
  formData.description = row.description;
  dialogVisible.value = true;
};
// ä¿å­˜æ•°æ®
const saveData = () => {
  if (!formData.name || !formData.area || !formData.location) {
    ElMessage.warning('请填写必填字段');
    return;
  }
  const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
  if (formData.id) {
    // ç¼–辑
    const index = mockData.findIndex(item => item.id === formData.id);
    if (index !== -1) {
      mockData[index] = {
        ...formData,
        lastUpdate: currentTime
      };
      ElMessage.success('编辑成功');
    }
  } else {
    // æ–°å¢ž
    const newId = Math.max(...mockData.map(item => item.id), 0) + 1;
    mockData.unshift({
      ...formData,
      id: newId,
      lastUpdate: currentTime
    });
    ElMessage.success('新增成功');
  }
  dialogVisible.value = false;
  getTableData();
};
// åˆ é™¤
const deleteRow = (id) => {
  ElMessageBox.confirm('此操作将永久删除该门禁记录, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    const index = mockData.findIndex(item => item.id === id);
    if (index !== -1) {
      mockData.splice(index, 1);
      ElMessage.success('删除成功');
      getTableData();
    }
  }).catch(() => {});
};
// æ‰¹é‡åˆ é™¤
const batchDelete = () => {
  if (multipleList.value.length === 0) {
    ElMessage.warning('请选择要删除的数据');
    return;
  }
  ElMessageBox.confirm('此操作将永久删除所选门禁记录, æ˜¯å¦ç»§ç»­?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    const ids = multipleList.value.map(item => item.id);
    mockData.forEach((item, index) => {
      if (ids.includes(item.id)) {
        mockData.splice(index, 1);
      }
    });
    ElMessage.success('删除成功');
    getTableData();
    multipleList.value = [];
  }).catch(() => {});
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/environmentAccess/intelligentInspectionManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,581 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="巡检名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入巡检名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="巡检状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择巡检状态"
          clearable
          @change="getTableData"
        >
          <el-option label="待执行" value="0"></el-option>
          <el-option label="执行中" value="1"></el-option>
          <el-option label="已完成" value="2"></el-option>
          <el-option label="已取消" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="巡检类型">
        <el-select
          v-model="filters.type"
          style="width: 240px"
          placeholder="请选择巡检类型"
          clearable
          @change="getTableData"
        >
          <el-option label="定期巡检" value="0"></el-option>
          <el-option label="临时巡检" value="1"></el-option>
          <el-option label="故障巡检" value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="batchDelete"
            >批量删除</el-button
          >
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- æ–°å¢žç¼–辑弹窗 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form :model="formData" label-width="100px">
        <el-form-item label="巡检名称" required>
          <el-input
            v-model="formData.name"
            placeholder="请输入巡检名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="巡检类型" required>
          <el-select v-model="formData.type" placeholder="请选择巡检类型">
            <el-option label="定期巡检" value="0"></el-option>
            <el-option label="临时巡检" value="1"></el-option>
            <el-option label="故障巡检" value="2"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="巡检区域" required>
          <el-input
            v-model="formData.area"
            placeholder="请输入巡检区域"
          ></el-input>
        </el-form-item>
        <el-form-item label="巡检人员" required>
          <el-input
            v-model="formData.inspector"
            placeholder="请输入巡检人员"
          ></el-input>
        </el-form-item>
        <el-form-item label="计划开始时间" required>
          <el-date-picker
            v-model="formData.planStartTime"
            type="datetime"
            placeholder="请选择计划开始时间"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="计划结束时间" required>
          <el-date-picker
            v-model="formData.planEndTime"
            type="datetime"
            placeholder="请选择计划结束时间"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="巡检内容">
          <el-input
            v-model="formData.content"
            type="textarea"
            rows="3"
            placeholder="请输入巡检内容"
          ></el-input>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="saveData">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { Search, Plus, Delete } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import dayjs from "dayjs";
// å®šä¹‰å‡æ•°æ®
const mockData = [
  {
    id: 1,
    name: "东门区域日常巡检",
    type: "0",
    status: "2",
    planStartTime: "2025-12-30 08:00:00",
    planEndTime: "2025-12-30 09:00:00",
    actualStartTime: "2025-12-30 08:05:00",
    actualEndTime: "2025-12-30 08:55:00",
    inspector: "张三",
    area: "东门区域",
    content: "检查门禁设备运行状态、环境监测设备数据",
  },
  {
    id: 2,
    name: "西门区域临时巡检",
    type: "1",
    status: "2",
    planStartTime: "2025-12-30 10:00:00",
    planEndTime: "2025-12-30 11:00:00",
    actualStartTime: "2025-12-30 10:00:00",
    actualEndTime: "2025-12-30 10:45:00",
    inspector: "李四",
    area: "西门区域",
    content: "检查异常报警设备",
  },
  {
    id: 3,
    name: "南门门禁故障巡检",
    type: "2",
    status: "1",
    planStartTime: "2025-12-30 13:00:00",
    planEndTime: "2025-12-30 14:00:00",
    actualStartTime: "2025-12-30 13:10:00",
    actualEndTime: "",
    inspector: "王五",
    area: "南门区域",
    content: "修复门禁故障",
  },
  {
    id: 4,
    name: "中控室定期巡检",
    type: "0",
    status: "0",
    planStartTime: "2025-12-31 09:00:00",
    planEndTime: "2025-12-31 10:00:00",
    actualStartTime: "",
    actualEndTime: "",
    inspector: "赵六",
    area: "中控区域",
    content: "检查监控设备、服务器运行状态",
  },
  {
    id: 5,
    name: "北门区域日常巡检",
    type: "0",
    status: "2",
    planStartTime: "2025-12-30 15:00:00",
    planEndTime: "2025-12-30 16:00:00",
    actualStartTime: "2025-12-30 15:00:00",
    actualEndTime: "2025-12-30 15:50:00",
    inspector: "张三",
    area: "北门区域",
    content: "检查车辆识别设备、道闸运行状态",
  },
];
// å“åº”式数据
const filters = reactive({
  name: "",
  status: "",
  type: "",
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const multipleList = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref("新增巡检");
const formData = reactive({
  id: "",
  name: "",
  type: "0",
  status: "0",
  planStartTime: "",
  planEndTime: "",
  actualStartTime: "",
  actualEndTime: "",
  inspector: "",
  area: "",
  content: "",
});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  '0': '<el-tag type="warning">待执行</el-tag>',
  '1': '<el-tag type="primary">执行中</el-tag>',
  '2': '<el-tag type="success">已完成</el-tag>',
  '3': '<el-tag type="danger">已取消</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "定期巡检",
  1: "临时巡检",
  2: "故障巡检",
};
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "巡检名称",
    align: "center",
    prop: "name",
  },
  {
    label: "巡检类型",
    align: "center",
    prop: "type",
    formatter: (row) => {
      return typeMap[row.type];
    },
  },
  {
    label: "巡检状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    label: "计划开始时间",
    align: "center",
    prop: "planStartTime",
  },
  {
    label: "计划结束时间",
    align: "center",
    prop: "planEndTime",
  },
  {
    label: "实际开始时间",
    align: "center",
    prop: "actualStartTime",
    formatter: (row) => {
      return row.actualStartTime || "-";
    },
  },
  {
    label: "实际结束时间",
    align: "center",
    prop: "actualEndTime",
    formatter: (row) => {
      return row.actualEndTime || "-";
    },
  },
  {
    label: "巡检人员",
    align: "center",
    prop: "inspector",
  },
  {
    label: "巡检区域",
    align: "center",
    prop: "area",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 180,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          edit(row);
        },
      },
      {
        name: "开始执行",
        type: "text",
        clickFun: (row) => {
          startInspection(row);
        },
        visible: (row) => {
          return row.status === "0";
        },
      },
      {
        name: "完成巡检",
        type: "text",
        clickFun: (row) => {
          completeInspection(row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
      {
        name: "取消巡检",
        type: "text",
        clickFun: (row) => {
          cancelInspection(row);
        },
        visible: (row) => {
          return ["0", "1"].includes(row.status);
        },
      },
      {
        name: "删除",
        type: "text",
        clickFun: (row) => {
          deleteRow(row.id);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockData.filter((item) => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const typeMatch = !filters.type || item.type === filters.type;
    return nameMatch && statusMatch && typeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = "";
  filters.status = "";
  filters.type = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// å¤šé€‰å¤„理
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
// æ–°å¢ž
const add = () => {
  dialogTitle.value = "新增巡检";
  formData.id = "";
  formData.name = "";
  formData.type = "0";
  formData.status = "0";
  formData.planStartTime = "";
  formData.planEndTime = "";
  formData.actualStartTime = "";
  formData.actualEndTime = "";
  formData.inspector = "";
  formData.area = "";
  formData.content = "";
  dialogVisible.value = true;
};
// ç¼–辑
const edit = (row) => {
  dialogTitle.value = "编辑巡检";
  formData.id = row.id;
  formData.name = row.name;
  formData.type = row.type;
  formData.status = row.status;
  formData.planStartTime = row.planStartTime;
  formData.planEndTime = row.planEndTime;
  formData.actualStartTime = row.actualStartTime;
  formData.actualEndTime = row.actualEndTime;
  formData.inspector = row.inspector;
  formData.area = row.area;
  formData.content = row.content;
  dialogVisible.value = true;
};
// ä¿å­˜æ•°æ®
const saveData = () => {
  if (
    !formData.name ||
    !formData.planStartTime ||
    !formData.planEndTime ||
    !formData.inspector ||
    !formData.area
  ) {
    ElMessage.warning("请填写必填字段");
    return;
  }
  if (formData.id) {
    // ç¼–辑
    const index = mockData.findIndex((item) => item.id === formData.id);
    if (index !== -1) {
      mockData[index] = {
        ...mockData[index],
        ...formData,
      };
      ElMessage.success("编辑成功");
    }
  } else {
    // æ–°å¢ž
    const newId = Math.max(...mockData.map((item) => item.id), 0) + 1;
    mockData.unshift({
      ...formData,
      id: newId,
    });
    ElMessage.success("新增成功");
  }
  dialogVisible.value = false;
  getTableData();
};
// å¼€å§‹å·¡æ£€
const startInspection = (row) => {
  const index = mockData.findIndex((item) => item.id === row.id);
  if (index !== -1) {
    mockData[index].status = "1";
    mockData[index].actualStartTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    ElMessage.success("巡检已开始");
    getTableData();
  }
};
// å®Œæˆå·¡æ£€
const completeInspection = (row) => {
  const index = mockData.findIndex((item) => item.id === row.id);
  if (index !== -1) {
    mockData[index].status = "2";
    mockData[index].actualEndTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
    ElMessage.success("巡检已完成");
    getTableData();
  }
};
// å–消巡检
const cancelInspection = (row) => {
  ElMessageBox.confirm("确定要取消该巡检吗?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const index = mockData.findIndex((item) => item.id === row.id);
      if (index !== -1) {
        mockData[index].status = "3";
        ElMessage.success("巡检已取消");
        getTableData();
      }
    })
    .catch(() => {});
};
// åˆ é™¤
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该巡检记录, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const index = mockData.findIndex((item) => item.id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
        ElMessage.success("删除成功");
        getTableData();
      }
    })
    .catch(() => {});
};
// æ‰¹é‡åˆ é™¤
const batchDelete = () => {
  if (multipleList.value.length === 0) {
    ElMessage.warning("请选择要删除的数据");
    return;
  }
  ElMessageBox.confirm("此操作将永久删除所选巡检记录, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const ids = multipleList.value.map((item) => item.id);
      mockData.forEach((item, index) => {
        if (ids.includes(item.id)) {
          mockData.splice(index, 1);
        }
      });
      ElMessage.success("删除成功");
      getTableData();
      multipleList.value = [];
    })
    .catch(() => {});
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/environmentAccess/remoteMonitoringOfEquipment/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,633 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="设备名称">
        <el-input
          v-model="filters.name"
          style="width: 240px"
          placeholder="请输入设备名称"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="设备状态">
        <el-select
          v-model="filters.status"
          style="width: 240px"
          placeholder="请选择设备状态"
          clearable
          @change="getTableData"
        >
          <el-option label="在线" value="1"></el-option>
          <el-option label="离线" value="0"></el-option>
          <el-option label="故障" value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="设备类型">
        <el-select
          v-model="filters.type"
          style="width: 240px"
          placeholder="请选择设备类型"
          clearable
          @change="getTableData"
        >
          <el-option label="门禁设备" value="0"></el-option>
          <el-option label="环境监测设备" value="1"></el-option>
          <el-option label="视频监控设备" value="2"></el-option>
          <el-option label="智能道闸" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="monitor-container">
      <div class="monitor-stats">
        <div class="stat-card">
          <div class="stat-title">在线设备</div>
          <div class="stat-value online">{{ onlineCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">离线设备</div>
          <div class="stat-value offline">{{ offlineCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">故障设备</div>
          <div class="stat-value error">{{ errorCount }}</div>
        </div>
        <div class="stat-card">
          <div class="stat-title">设备总数</div>
          <div class="stat-value total">{{ totalCount }}</div>
        </div>
      </div>
      <div class="table_list">
        <PIMTable
          rowKey="id"
          :column="columns"
          :tableData="dataList"
          :page="{
            current: pagination.currentPage,
            size: pagination.pageSize,
            total: pagination.total,
          }"
          @pagination="changePage"
        >
        </PIMTable>
      </div>
    </div>
    <!-- è®¾å¤‡è¯¦æƒ…弹窗 -->
    <el-dialog
      v-model="detailVisible"
      :title="`设备详情 - ${selectedDevice.name}`"
      width="800px"
    >
      <div v-if="selectedDevice" class="device-detail">
        <div class="detail-section">
          <h3>基本信息</h3>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="设备名称">{{
              selectedDevice.name
            }}</el-descriptions-item>
            <el-descriptions-item label="设备类型">{{
              typeMap[selectedDevice.type]
            }}</el-descriptions-item>
            <el-descriptions-item label="设备状态">{{
              statusMap[selectedDevice.status]
            }}</el-descriptions-item>
            <el-descriptions-item label="设备编号">{{
              selectedDevice.code
            }}</el-descriptions-item>
            <el-descriptions-item label="安装位置">{{
              selectedDevice.location
            }}</el-descriptions-item>
            <el-descriptions-item label="安装时间">{{
              selectedDevice.installTime
            }}</el-descriptions-item>
            <el-descriptions-item label="最后维护时间">{{
              selectedDevice.maintainTime
            }}</el-descriptions-item>
            <el-descriptions-item label="责任人">{{
              selectedDevice.responsiblePerson
            }}</el-descriptions-item>
          </el-descriptions>
        </div>
        <div class="detail-section">
          <h3>实时数据</h3>
          <div class="realtime-data">
            <div
              v-for="data in selectedDevice.realtimeData"
              :key="data.name"
              class="data-item"
            >
              <div class="data-name">{{ data.name }}</div>
              <div class="data-value">{{ data.value }}{{ data.unit }}</div>
              <div class="data-status" :class="data.status">
                {{ data.status === "normal" ? "正常" : "异常" }}
              </div>
            </div>
          </div>
        </div>
        <div class="detail-section">
          <h3>控制操作</h3>
          <div class="control-buttons">
            <el-button
              type="primary"
              @click="controlDevice('restart')"
              :disabled="selectedDevice.status !== '1'"
              >重启设备</el-button
            >
            <el-button
              type="warning"
              @click="controlDevice('reset')"
              :disabled="selectedDevice.status !== '1'"
              >重置设备</el-button
            >
            <el-button
              type="danger"
              @click="controlDevice('shutdown')"
              :disabled="selectedDevice.status !== '1'"
              >关闭设备</el-button
            >
            <el-button
              type="success"
              @click="controlDevice('update')"
              :disabled="selectedDevice.status !== '1'"
              >固件更新</el-button
            >
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="detailVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import {
  Search
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
// å®šä¹‰å‡æ•°æ®
const mockDevices = [
  {
    id: 1,
    name: "东门门禁控制器",
    code: "DEV-20250001",
    type: "0",
    status: "1",
    location: "东门入口",
    installTime: "2025-01-15",
    maintainTime: "2025-12-20",
    responsiblePerson: "张三",
    realtimeData: [
      { name: "温度", value: 23.5, unit: "℃", status: "normal" },
      { name: "湿度", value: 45, unit: "%", status: "normal" },
      { name: "运行时长", value: 12560, unit: "h", status: "normal" },
      { name: "门禁开关次数", value: 234, unit: "次/天", status: "normal" },
    ],
  },
  {
    id: 2,
    name: "西门视频监控",
    code: "DEV-20250002",
    type: "2",
    status: "1",
    location: "西门出口",
    installTime: "2025-02-20",
    maintainTime: "2025-11-15",
    responsiblePerson: "李四",
    realtimeData: [
      { name: "视频清晰度", value: 1080, unit: "P", status: "normal" },
      { name: "帧率", value: 25, unit: "fps", status: "normal" },
      { name: "存储空间", value: 78, unit: "%", status: "warning" },
      { name: "网络延迟", value: 12, unit: "ms", status: "normal" },
    ],
  },
  {
    id: 3,
    name: "南门环境监测仪",
    code: "DEV-20250003",
    type: "1",
    status: "2",
    location: "南门广场",
    installTime: "2025-03-10",
    maintainTime: "2025-10-05",
    responsiblePerson: "王五",
    realtimeData: [
      { name: "PM2.5", value: 85, unit: "μg/m³", status: "error" },
      { name: "PM10", value: 120, unit: "μg/m³", status: "error" },
      { name: "噪声", value: 65, unit: "dB", status: "normal" },
      { name: "风速", value: 1.2, unit: "m/s", status: "normal" },
    ],
  },
  {
    id: 4,
    name: "北门智能道闸",
    code: "DEV-20250004",
    type: "3",
    status: "0",
    location: "北门入口",
    installTime: "2025-04-05",
    maintainTime: "2025-09-20",
    responsiblePerson: "赵六",
    realtimeData: [
      { name: "开关次数", value: 156, unit: "次/天", status: "normal" },
      { name: "运行电压", value: 220, unit: "V", status: "normal" },
      { name: "运行电流", value: 5.2, unit: "A", status: "normal" },
      { name: "故障代码", value: "E001", unit: "", status: "error" },
    ],
  },
  {
    id: 5,
    name: "中控室服务器",
    code: "DEV-20250005",
    type: "1",
    status: "1",
    location: "中控室",
    installTime: "2025-05-15",
    maintainTime: "2025-12-01",
    responsiblePerson: "孙七",
    realtimeData: [
      { name: "CPU使用率", value: 45, unit: "%", status: "normal" },
      { name: "内存使用率", value: 68, unit: "%", status: "warning" },
      { name: "磁盘使用率", value: 82, unit: "%", status: "warning" },
      { name: "网络流量", value: 125, unit: "Mbps", status: "normal" },
    ],
  },
];
// å“åº”式数据
const filters = reactive({
  name: "",
  status: "",
  type: "",
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const detailVisible = ref(false);
const selectedDevice = ref({});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  0: '<el-tag type="warning">离线</el-tag>',
  1: '<el-tag type="success">在线</el-tag>',
  2: '<el-tag type="danger">故障</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "门禁设备",
  1: "环境监测设备",
  2: "视频监控设备",
  3: "智能道闸",
};
// è®¾å¤‡æ•°é‡ç»Ÿè®¡
const onlineCount = computed(() => {
  return mockDevices.filter((device) => device.status === "1").length;
});
const offlineCount = computed(() => {
  return mockDevices.filter((device) => device.status === "0").length;
});
const errorCount = computed(() => {
  return mockDevices.filter((device) => device.status === "2").length;
});
const totalCount = computed(() => {
  return mockDevices.length;
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "设备名称",
    align: "center",
    prop: "name",
  },
  {
    label: "设备编号",
    align: "center",
    prop: "code",
  },
  {
    label: "设备类型",
    align: "center",
    prop: "type",
    formatter: (row) => {
      return typeMap[row.type];
    },
  },
  {
    label: "设备状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    label: "安装位置",
    align: "center",
    prop: "location",
  },
  {
    label: "责任人",
    align: "center",
    prop: "responsiblePerson",
  },
  {
    label: "最后维护时间",
    align: "center",
    prop: "maintainTime",
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 140,
    operation: [
      {
        name: "查看详情",
        type: "text",
        clickFun: (row) => {
          viewDetail(row);
        },
      },
      {
        name: "实时监控",
        type: "text",
        clickFun: (row) => {
          realtimeMonitor(row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
      {
        name: "重启设备",
        type: "text",
        clickFun: (row) => {
          controlDevice("restart", row);
        },
        visible: (row) => {
          return row.status === "1";
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockDevices.filter((item) => {
    const nameMatch = !filters.name || item.name.includes(filters.name);
    const statusMatch = !filters.status || item.status === filters.status;
    const typeMatch = !filters.type || item.type === filters.type;
    return nameMatch && statusMatch && typeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.name = "";
  filters.status = "";
  filters.type = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// æŸ¥çœ‹è®¾å¤‡è¯¦æƒ…
const viewDetail = (row) => {
  selectedDevice.value = row;
  detailVisible.value = true;
};
// å®žæ—¶ç›‘控
const realtimeMonitor = (row) => {
  ElMessage.info(`正在跳转到${row.name}的实时监控页面...`);
  // è¿™é‡Œå¯ä»¥è·³è½¬åˆ°å®žæ—¶ç›‘控页面或打开监控窗口
};
// è®¾å¤‡æŽ§åˆ¶
const controlDevice = (action, row = selectedDevice.value) => {
  const actionMap = {
    restart: "重启",
    reset: "重置",
    shutdown: "关闭",
    update: "固件更新",
  };
  ElMessageBox.confirm(
    `确定要对设备 ${row.name} æ‰§è¡Œ${actionMap[action]}操作吗?`,
    "提示",
    {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      ElMessage.success(`设备${actionMap[action]}操作已发送`);
      // æ¨¡æ‹Ÿè®¾å¤‡å“åº”
      setTimeout(() => {
        ElMessage.success(`设备${actionMap[action]}操作执行成功`);
      }, 1000);
    })
    .catch(() => {});
};
// å®šæ—¶æ›´æ–°è®¾å¤‡çŠ¶æ€ï¼ˆæ¨¡æ‹Ÿå®žæ—¶å˜åŒ–ï¼‰
const updateDeviceStatus = () => {
  // éšæœºæ›´æ–°ä¸€äº›è®¾å¤‡çš„状态和实时数据
  mockDevices.forEach((device) => {
    if (Math.random() > 0.8) {
      // 80%概率不更新
      return;
    }
    // éšæœºæ›´æ–°çŠ¶æ€
    const statuses = ["0", "1", "2"];
    device.status = statuses[Math.floor(Math.random() * statuses.length)];
    // æ›´æ–°å®žæ—¶æ•°æ®
    device.realtimeData.forEach((data) => {
      if (data.name === "温度") {
        data.value = (20 + Math.random() * 10).toFixed(1);
        data.status = data.value > 30 ? "error" : "normal";
      } else if (data.name === "湿度") {
        data.value = Math.floor(30 + Math.random() * 50);
        data.status = data.value > 80 ? "error" : "normal";
      } else if (data.name === "CPU使用率") {
        data.value = Math.floor(20 + Math.random() * 60);
        data.status =
          data.value > 80 ? "error" : data.value > 60 ? "warning" : "normal";
      }
    });
  });
  // åˆ·æ–°è¡¨æ ¼æ•°æ®
  getTableData();
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
  // æ¯5秒更新一次设备状态
  const interval = setInterval(updateDeviceStatus, 5000);
  // æ¸…理定时器
  return () => clearInterval(interval);
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.monitor-container {
  .monitor-stats {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
    .stat-card {
      flex: 1;
      background: #fff;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      text-align: center;
      .stat-title {
        font-size: 14px;
        color: #606266;
        margin-bottom: 10px;
      }
      .stat-value {
        font-size: 32px;
        font-weight: bold;
        &.online {
          color: #67c23a;
        }
        &.offline {
          color: #e6a23c;
        }
        &.error {
          color: #f56c6c;
        }
        &.total {
          color: #409eff;
        }
      }
    }
  }
}
.device-detail {
  .detail-section {
    margin-bottom: 20px;
    h3 {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 10px;
      color: #303133;
    }
  }
  .realtime-data {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 15px;
    .data-item {
      background: #f5f7fa;
      padding: 15px;
      border-radius: 8px;
      .data-name {
        font-size: 14px;
        color: #606266;
        margin-bottom: 5px;
      }
      .data-value {
        font-size: 24px;
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .data-status {
        font-size: 12px;
        padding: 2px 8px;
        border-radius: 10px;
        &.normal {
          background: #f0f9eb;
          color: #67c23a;
        }
        &.warning {
          background: #fdf6ec;
          color: #e6a23c;
        }
        &.error {
          background: #fef0f0;
          color: #f56c6c;
        }
      }
    }
  }
  .control-buttons {
    display: flex;
    gap: 10px;
    margin-top: 10px;
  }
}
</style>
src/views/environmentAccess/vehicleInformationCollection/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,577 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="车牌号码">
        <el-input
          v-model="filters.plateNumber"
          style="width: 240px"
          placeholder="请输入车牌号码"
          clearable
          :prefix-icon="Search"
          @change="getTableData"
        />
      </el-form-item>
      <el-form-item label="车辆类型">
        <el-select
          v-model="filters.vehicleType"
          style="width: 240px"
          placeholder="请选择车辆类型"
          clearable
          @change="getTableData"
        >
          <el-option label="小型车" value="0"></el-option>
          <el-option label="中型车" value="1"></el-option>
          <el-option label="大型车" value="2"></el-option>
          <el-option label="特种车" value="3"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="进出方向">
        <el-select
          v-model="filters.direction"
          style="width: 240px"
          placeholder="请选择进出方向"
          clearable
          @change="getTableData"
        >
          <el-option label="进" value="0"></el-option>
          <el-option label="出" value="1"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="进出时间">
        <el-date-picker
          v-model="filters.timeRange"
          type="daterange"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          value-format="YYYY-MM-DD HH:mm:ss"
          format="YYYY-MM-DD HH:mm"
          clearable
          @change="changeTimeRange"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="vehicle-stats">
      <div class="stat-card">
        <div class="stat-title">今日进车数</div>
        <div class="stat-value in">{{ todayInCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">今日出车数</div>
        <div class="stat-value out">{{ todayOutCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">当前在场车辆</div>
        <div class="stat-value present">{{ presentCount }}</div>
      </div>
      <div class="stat-card">
        <div class="stat-title">今日总流量</div>
        <div class="stat-value total">{{ todayTotalCount }}</div>
      </div>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @pagination="changePage"
      >
      </PIMTable>
    </div>
    <!-- è½¦è¾†è¯¦æƒ…弹窗 -->
    <el-dialog
      v-model="detailVisible"
      :title="`车辆详情 - ${selectedVehicle.plateNumber}`"
      width="700px"
    >
      <div v-if="selectedVehicle" class="vehicle-detail">
        <div class="detail-section">
          <h3>基本信息</h3>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="车牌号码">{{
              selectedVehicle.plateNumber
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆类型">{{
              typeMap[selectedVehicle.vehicleType]
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆颜色">{{
              selectedVehicle.color
            }}</el-descriptions-item>
            <el-descriptions-item label="进出方向">{{
              selectedVehicle.direction === "0" ? "进" : "出"
            }}</el-descriptions-item>
            <el-descriptions-item label="进出时间">{{
              selectedVehicle.timestamp
            }}</el-descriptions-item>
            <el-descriptions-item label="通道名称">{{
              selectedVehicle.channelName
            }}</el-descriptions-item>
            <el-descriptions-item label="识别方式">{{
              selectedVehicle.identifyMethod
            }}</el-descriptions-item>
            <el-descriptions-item label="车辆状态">{{
              statusMap[selectedVehicle.status]
            }}</el-descriptions-item>
          </el-descriptions>
        </div>
        <div class="detail-section">
          <h3>车辆图片</h3>
          <div class="vehicle-images">
            <div class="image-item">
              <div class="image-title">正面图片</div>
              <el-image
                :src="selectedVehicle.frontImage"
                fit="cover"
                style="width: 100%; height: 200px"
                :preview-src-list="[
                  selectedVehicle.frontImage,
                  selectedVehicle.sideImage,
                ]"
              ></el-image>
            </div>
            <div class="image-item">
              <div class="image-title">侧面图片</div>
              <el-image
                :src="selectedVehicle.sideImage"
                fit="cover"
                style="width: 100%; height: 200px"
                :preview-src-list="[
                  selectedVehicle.sideImage,
                  selectedVehicle.frontImage,
                ]"
              ></el-image>
            </div>
          </div>
        </div>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="detailVisible = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import dayjs from "dayjs";
// å®šä¹‰å‡æ•°æ®
const mockVehicles = [
  {
    id: 1,
    plateNumber: "京A12345",
    vehicleType: "0",
    color: "白色",
    direction: "0",
    timestamp: "2025-12-31 08:15:30",
    channelName: "东门通道",
    identifyMethod: "车牌识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1024/800/600",
    sideImage: "https://picsum.photos/id/1025/800/600",
    duration: "00:00:00",
    present: true,
  },
  {
    id: 2,
    plateNumber: "沪B67890",
    vehicleType: "1",
    color: "黑色",
    direction: "1",
    timestamp: "2025-12-31 08:10:20",
    channelName: "西门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1026/800/600",
    sideImage: "https://picsum.photos/id/1027/800/600",
    duration: "01:25:30",
    present: false,
  },
  {
    id: 3,
    plateNumber: "粤C54321",
    vehicleType: "0",
    color: "红色",
    direction: "0",
    timestamp: "2025-12-31 08:05:15",
    channelName: "南门通道",
    identifyMethod: "IC卡识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1028/800/600",
    sideImage: "https://picsum.photos/id/1029/800/600",
    duration: "00:05:45",
    present: true,
  },
  {
    id: 4,
    plateNumber: "川D98765",
    vehicleType: "2",
    color: "蓝色",
    direction: "0",
    timestamp: "2025-12-31 08:00:00",
    channelName: "北门通道",
    identifyMethod: "车牌识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1030/800/600",
    sideImage: "https://picsum.photos/id/1031/800/600",
    duration: "00:10:30",
    present: true,
  },
  {
    id: 5,
    plateNumber: "苏E13579",
    vehicleType: "0",
    color: "银色",
    direction: "1",
    timestamp: "2025-12-31 07:55:45",
    channelName: "东门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1032/800/600",
    sideImage: "https://picsum.photos/id/1033/800/600",
    duration: "02:30:15",
    present: false,
  },
  {
    id: 6,
    plateNumber: "浙F24680",
    vehicleType: "3",
    color: "黄色",
    direction: "0",
    timestamp: "2025-12-31 07:50:30",
    channelName: "南门通道",
    identifyMethod: "人工登记",
    status: "0",
    frontImage: "https://picsum.photos/id/1034/800/600",
    sideImage: "https://picsum.photos/id/1035/800/600",
    duration: "00:15:30",
    present: true,
  },
  {
    id: 7,
    plateNumber: "鲁G97531",
    vehicleType: "1",
    color: "绿色",
    direction: "1",
    timestamp: "2025-12-31 07:45:15",
    channelName: "西门通道",
    identifyMethod: "车牌识别",
    status: "1",
    frontImage: "https://picsum.photos/id/1036/800/600",
    sideImage: "https://picsum.photos/id/1037/800/600",
    duration: "01:45:30",
    present: false,
  },
  {
    id: 8,
    plateNumber: "豫H86420",
    vehicleType: "0",
    color: "灰色",
    direction: "0",
    timestamp: "2025-12-31 07:40:00",
    channelName: "北门通道",
    identifyMethod: "IC卡识别",
    status: "0",
    frontImage: "https://picsum.photos/id/1038/800/600",
    sideImage: "https://picsum.photos/id/1039/800/600",
    duration: "00:20:15",
    present: true,
  },
];
// å“åº”式数据
const filters = reactive({
  plateNumber: "",
  vehicleType: "",
  direction: "",
  timeRange: [],
});
const dataList = ref([]);
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
const detailVisible = ref(false);
const selectedVehicle = ref({});
// æ—¶é—´èŒƒå›´
const timeRange = reactive({
  startTime: "",
  endTime: "",
});
// çŠ¶æ€æ˜ å°„
const statusMap = {
  '0': '<el-tag type="success">正常</el-tag>',
  '1': '<el-tag type="info">已离开</el-tag>',
  '2': '<el-tag type="warning">异常</el-tag>',
};
// ç±»åž‹æ˜ å°„
const typeMap = {
  0: "小型车",
  1: "中型车",
  2: "大型车",
  3: "特种车",
};
// ç»Ÿè®¡æ•°æ®
const todayInCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.direction === "0" && vehicle.timestamp.startsWith(today);
  }).length;
});
const todayOutCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.direction === "1" && vehicle.timestamp.startsWith(today);
  }).length;
});
const presentCount = computed(() => {
  return mockVehicles.filter((vehicle) => vehicle.present).length;
});
const todayTotalCount = computed(() => {
  const today = dayjs().format("YYYY-MM-DD");
  return mockVehicles.filter((vehicle) => {
    return vehicle.timestamp.startsWith(today);
  }).length;
});
// è¡¨æ ¼åˆ—配置
const columns = [
  {
    label: "车牌号码",
    align: "center",
    prop: "plateNumber",
  },
  {
    label: "车辆类型",
    align: "center",
    prop: "vehicleType",
    formatter: (row) => {
      return typeMap[row.vehicleType];
    },
  },
  {
    label: "车辆颜色",
    align: "center",
    prop: "color",
  },
  {
    label: "进出方向",
    align: "center",
    prop: "direction",
    formatter: (row) => {
      return row.direction === "0" ? '<el-tag type="success">进</el-tag>' : '<el-tag type="info">出</el-tag>';
    },
  },
  {
    label: "进出时间",
    align: "center",
    prop: "timestamp",
  },
  {
    label: "通道名称",
    align: "center",
    prop: "channelName",
  },
  {
    label: "识别方式",
    align: "center",
    prop: "identifyMethod",
  },
  {
    label: "车辆状态",
    align: "center",
    prop: "status",
    formatter: (row) => {
      return statusMap[row.status];
    },
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 140,
    operation: [
      {
        name: "查看详情",
        type: "text",
        clickFun: (row) => {
          viewDetail(row);
        },
      },
    ],
  },
];
// è¿‡æ»¤åŽçš„æ•°æ®
const filteredData = computed(() => {
  return mockVehicles.filter((item) => {
    const plateMatch =
      !filters.plateNumber || item.plateNumber.includes(filters.plateNumber);
    const typeMatch =
      !filters.vehicleType || item.vehicleType === filters.vehicleType;
    const directionMatch =
      !filters.direction || item.direction === filters.direction;
    const timeMatch =
      !timeRange.startTime ||
      (item.timestamp >= timeRange.startTime &&
        item.timestamp <= timeRange.endTime);
    return plateMatch && typeMatch && directionMatch && timeMatch;
  });
});
// èŽ·å–è¡¨æ ¼æ•°æ®
const getTableData = () => {
  pagination.total = filteredData.value.length;
  const start = (pagination.currentPage - 1) * pagination.pageSize;
  const end = start + pagination.pageSize;
  dataList.value = filteredData.value.slice(start, end);
};
// é‡ç½®è¿‡æ»¤å™¨
const resetFilters = () => {
  filters.plateNumber = "";
  filters.vehicleType = "";
  filters.direction = "";
  filters.timeRange = [];
  timeRange.startTime = "";
  timeRange.endTime = "";
  pagination.currentPage = 1;
  getTableData();
};
// åˆ†é¡µå˜åŒ–
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  getTableData();
};
// æ—¶é—´èŒƒå›´å˜åŒ–
const changeTimeRange = (value) => {
  if (value && value.length === 2) {
    timeRange.startTime = value[0];
    timeRange.endTime = value[1];
  } else {
    timeRange.startTime = "";
    timeRange.endTime = "";
  }
  getTableData();
};
// æŸ¥çœ‹è½¦è¾†è¯¦æƒ…
const viewDetail = (row) => {
  selectedVehicle.value = row;
  detailVisible.value = true;
};
// åˆå§‹åŒ–数据
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.vehicle-stats {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  .stat-card {
    flex: 1;
    background: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    text-align: center;
    .stat-title {
      font-size: 14px;
      color: #606266;
      margin-bottom: 10px;
    }
    .stat-value {
      font-size: 32px;
      font-weight: bold;
      &.in {
        color: #67c23a;
      }
      &.out {
        color: #409eff;
      }
      &.present {
        color: #e6a23c;
      }
      &.total {
        color: #909399;
      }
    }
  }
}
.vehicle-detail {
  .detail-section {
    margin-bottom: 20px;
    h3 {
      font-size: 16px;
      font-weight: bold;
      margin-bottom: 10px;
      color: #303133;
    }
  }
  .vehicle-images {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 15px;
    .image-item {
      background: #f5f7fa;
      padding: 15px;
      border-radius: 8px;
      .image-title {
        font-size: 14px;
        color: #606266;
        margin-bottom: 10px;
        text-align: center;
      }
    }
  }
}
</style>
src/views/financialManagement/accounting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,547 @@
<template>
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œç­›é€‰æ¡ä»¶ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetFilters"
        size="default"
      >
        æŸ¥è¯¢
      </el-button>
    </div>
    <main class="container mx-auto px-4 pb-10">
      <!-- å›ºå®šèµ„产指标卡片 -->
      <div class="grid-container">
        <!-- è®¾å¤‡æ€»æ•° -->
        <el-card class="bg2">
          <p>设备总数</p>
          <h3>
            {{ assetInfo.totalEquipment }}
          </h3>
        </el-card>
        <!-- èµ„产原值 -->
        <el-card class="bg3">
          <p>资产原值</p>
          <h3>
            Â¥{{ assetInfo.totalOriginalValue }}
          </h3>
        </el-card>
        <!-- ç´¯è®¡æŠ˜æ—§ -->
        <el-card class="bg4">
          <p>累计折旧</p>
          <h3>
            Â¥{{ assetInfo.totalDepreciation }}
          </h3>
        </el-card>
        <!-- å‡€å€¼ -->
        <el-card class="bg5">
          <p>净值</p>
          <h3>
            Â¥{{ assetInfo.totalNetValue }}
          </h3>
        </el-card>
      </div>
      <!-- å›ºå®šèµ„产统计图表 -->
      <div class="grid-layout">
        <!-- æŒ‰è®¾å¤‡ç±»åž‹ç»Ÿè®¡ -->
        <el-card style="margin-bottom: 20px;">
          <h2 class="section-title">设备类型分布</h2>
          <div class="echarts">
            <Echarts
                :legend="typeDistributionLegend"
                :chartStyle="chartStylePie"
                :series="typeDistributionSeries"
                :tooltip="pieTooltip"
                style="height: 260px; width: 35%;">
              <div class="chart-num">
                <span style="font-size: 22px;">设备类型</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
              </div>
            </Echarts>
            <Echarts
                ref="chart"
                :chartStyle="chartStyle"
                :grid="grid"
                :legend="lineLegend"
                :series="typeDistributionLineSeries"
                :tooltip="tooltip"
                :xAxis="xAxis"
                :yAxis="yAxis"
                style="height: 260px; width: 64%;"></Echarts>
          </div>
        </el-card>
      </div>
      <!-- è®¾å¤‡å°è´¦è¡¨æ ¼ -->
      <el-card style="margin-bottom: 20px;">
        <el-table
          :data="equipmentList"
          stripe
          style="width: 100%"
          :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
        >
          <el-table-column prop="id" label="资产编号" width="120" />
          <el-table-column prop="deviceName" label="设备名称" width="250" />
          <el-table-column prop="deviceModel" label="型号规格" min-width="150" />
          <el-table-column prop="supplierName" label="供应商" min-width="120" />
          <el-table-column prop="unit" label="单位" width="120" />
          <el-table-column prop="number" label="数量" width="120" />
          <el-table-column prop="originalValue" label="原值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="depreciation" label="累计折旧(元)" width="140">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="netValue" label="净值(元)" width="120">
            <template #default="{ row }">
              Â¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag
                :type="getStatusTagType(row.status)"
                size="small"
              >
                {{ row.status }}
              </el-tag>
            </template>
          </el-table-column>
        </el-table>
        <!-- åˆ†é¡µ -->
        <div class="pagination-container">
          <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="pagination.currentPage"
            :page-sizes="[10, 20, 50, 100]"
            :page-size="pagination.pageSize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="pagination.total"
          />
        </div>
      </el-card>
    </main>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, reactive } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { getLedgerPage } from "@/api/equipmentManagement/ledger";
import dayjs from "dayjs";
// ç­›é€‰æ¡ä»¶
const dateRange = ref(null);
const equipmentType = ref('');
// å›ºå®šèµ„产信息
const assetInfo = ref({
  totalEquipment: 0,
  totalOriginalValue: 0,
  totalDepreciation: 0,
  totalNetValue: 0
});
// è®¾å¤‡åˆ—表
const equipmentList = ref([]);
const pagination = ref({
  currentPage: 1,
  pageSize: 10,
  total: 0
});
// å›¾è¡¨é…ç½®
const chartStyle = {
  width: '100%',
  height: '100%',
  position: 'relative',
};
const grid = {
  left: '3%',
  right: '4%',
  bottom: '3%',
  containLabel: true
};
const lineLegend = {
  show: false,
};
// æŠ˜çº¿å›¾æç¤ºæ¡†
const tooltip = reactive({
  trigger: 'axis',
  axisPointer: {
    type: 'line',
    lineStyle: { color: '#aaa' }
  },
  // è‡ªå®šä¹‰å†…容
  formatter: function (params) {
    if (!params || !params.length) return '';
    const axisLabel = params[0].axisValueLabel || params[0].axisValue || '';
    const rows = params
      .map(p => {
        const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
        return `${colorDot}${p.seriesName}: ${p.value}`;
      })
      .join('<br/>');
    return `<div>${axisLabel}</div><div>${rows}</div>`;
  }
});
const xAxis = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: [],
  },
]);
const yAxis = [
  {
    type: 'value',
    name: '数量/金额', // å·¦ä¾§yè½´
    position: 'left',
    min: 0,
    // åæ ‡è½´åç§°æ ·å¼
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
];
const chartStylePie = {
  width: '100%',
  height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
};
const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // å¯æ ¹æ®å®žé™…调整
// é¥¼å›¾æ•°æ®
const typeDistributionData = ref([]);
const departmentDistributionData = ref([]);
// é¥¼å›¾å›¾ä¾‹
const typeDistributionLegend = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: typeDistributionData.value.map(item => item.name),
  formatter: function(name) {
    const item = typeDistributionData.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.count} å° | ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
// é¥¼å›¾ç³»åˆ—
const typeDistributionSeries = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: typeDistributionData.value,
    color: pieColors
  }
]);
// æŠ˜çº¿å›¾æ•°æ®
const typeDistributionLineSeries = ref([]);
// é¥¼å›¾æç¤ºæ¡†
const pieTooltip = reactive({
  trigger: 'item',
  formatter: function(params) {
    // æ£€æŸ¥æ•°æ®æ˜¯å¦å­˜åœ¨
    if (!params.data) return params.name;
    // æ‹¼æŽ¥å®Œæ•´å†…容
    return `
      <div>
        <div style="color:${params.color};font-size:16px;">●</div>
        <div>${params.name}</div>
        <div>数量:${params.data.count} å°</div>
        <div>金额:${params.data.amount}</div>
      </div>
    `;
  }
});
// é€‰é¡¹æ•°æ®
const equipmentTypeOptions = ref([]);
// èŽ·å–æ•°æ®
const fetchData = async () => {
  try {
    // èŽ·å–å›ºå®šèµ„äº§æ±‡æ€»ä¿¡æ¯
    const assetInfoRes = await getAssetInfo({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
    });
    if (assetInfoRes.code === 200) {
      assetInfo.value = assetInfoRes.data;
    }
    // èŽ·å–è®¾å¤‡åˆ—è¡¨
    const equipmentListRes = await getLedgerPage({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
    });
    if (equipmentListRes.code === 200) {
      equipmentList.value = equipmentListRes.data.records;
      pagination.value.total = equipmentListRes.data.total;
      // æ ¹æ® equipmentList æŒ‰ deviceName è¿›è¡Œåˆ†ç±»ç»Ÿè®¡
      const deviceNameMap = {};
      equipmentList.value.forEach(item => {
        const deviceName = item.deviceName;
        if (!deviceNameMap[deviceName]) {
          deviceNameMap[deviceName] = {
            name: deviceName,
            count: 0,
            totalValue: 0
          };
        }
        deviceNameMap[deviceName].count += item.number || 1; // å‡è®¾ number ä¸ºè®¾å¤‡æ•°é‡
        deviceNameMap[deviceName].totalValue += item.taxIncludingPriceTotal || 0; // ç´¯åŠ å«ç¨Žæ€»ä»·
      });
      // è½¬æ¢ä¸º typeDistributionData æ ¼å¼
      typeDistributionData.value = Object.values(deviceNameMap).map(item => ({
        name: item.name,
        value: item.count,
        count: item.count,
        amount: `Â¥${formatCurrency(item.totalValue)}`
      }));
      // æ›´æ–°x轴数据
      xAxis.value[0].data = typeDistributionData.value.map(item => item.name);
      // æž„建折线图数据
      typeDistributionLineSeries.value = [
        {
          name: '设备数量',
          type: 'line',
          data: typeDistributionData.value.map(item => item.count)
        }
      ];
    }
  } catch (error) {
    console.error('获取固定资产数据失败:', error);
  }
};
// åˆå§‹åŒ–
onMounted(() => {
  // èŽ·å–åˆ—è¡¨æ•°æ®
  fetchData();
});
// æ ¼å¼åŒ–货币
const formatCurrency = (value) => {
  if (!value) return '0.00';
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
// èŽ·å–çŠ¶æ€æ ‡ç­¾ç±»åž‹
const getStatusTagType = (status) => {
  switch (status) {
    case '在用':
      return 'success';
    case '闲置':
      return 'info';
    case '维修中':
      return 'warning';
    case '报废':
      return 'danger';
    default:
      return 'info';
  }
};
// é‡ç½®ç­›é€‰æ¡ä»¶
const resetFilters = () => {
  dateRange.value = null;
  equipmentType.value = '';
  fetchData();
};
// åˆ†é¡µå¤„理
const handleSizeChange = (size) => {
  pagination.value.pageSize = size;
  fetchData();
};
const handleCurrentChange = (page) => {
  pagination.value.currentPage = page;
  fetchData();
};
</script>
<style scoped lang="scss">
/* åŸºç¡€æ ·å¼è¡¥å…… */
:root {
  --el-color-primary: #4f46e5;
}
.el-card {
  position: relative;
  border-radius: 12px;
  padding: 14px 10px 10px 10px;
  box-shadow: 0 2px 8px #eee;
  :deep(.el-card__body) {
    padding: 10px 20px !important;
  }
  &.bg1 {
    background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
  }
  &.bg2 {
    background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
  }
  &.bg3 {
    background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
  }
  &.bg4 {
    background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
  }
  &.bg5 {
    background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
  }
}
.grid-container {
  /* grid å®¹å™¨åŸºç¡€æ ·å¼ */
  display: grid;
  gap: 1rem; /* gap-4 å¯¹åº” 1rem (16px) */
  margin-bottom: 2rem; /* mb-8 å¯¹åº” 2rem (32px) */
  p {
    font-size: 22px;
    margin-top: 0px;
    color: #fff;
  }
  h3 {
    font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;
    margin: 10px 0;
    color: #fff;
  }
}
/* ç§»åŠ¨ç«¯é»˜è®¤æ ·å¼ (grid-cols-1) */
.grid-container {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
/* å°å±å¹•及以上 (sm:grid-cols-2) */
@media (min-width: 640px) {
  .grid-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* å¤§å±å¹•及以上 (lg:grid-cols-5) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
/* å¡ç‰‡æ‚¬åœæ•ˆæžœå¢žå¼º */
.el-card:hover {
  transform: translateY(-2px);
}
.echarts {
  display: flex;
  justify-content: space-between;
}
/* å›¾è¡¨å®¹å™¨æ ·å¼ */
.el-chart {
  width: 100%;
  height: 100%;
}
.section-title {
  position: relative;
  font-size: 18px;
  color: #333;
  padding-left: 10px;
  margin-bottom: 10px;
  font-weight: 700;
}
.section-title::before {
  position: absolute;
  left: 0;
  top: 0px;
  content: '';
  width: 4px;
  height: 18px;
  background-color: #002FA7;
  border-radius: 2px;
}
.chart-num {
  position: absolute;
  z-index: 3;
  top: 92px;
  left: 92px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: center;
}
</style>
src/views/financialManagement/expenseManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,276 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="支出日期:">
        <el-date-picker v-model="filters.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange" />
      </el-form-item>
      <el-form-item label="付款方式:">
        <el-select
                v-model="filters.expenseMethod"
                placeholder="请选择"
                clearable
                style="width: 200px;"
              >
                <el-option
                  v-for="item in checkout_payment"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getTableData">搜索</el-button>
        <el-button @click="resetFilters">重置</el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button @click="handleOut" icon="download">导出</el-button>
          <el-button
            type="danger"
            icon="Delete"
            :disabled="multipleList.length <= 0"
            @click="deleteRow(multipleList.map((item) => item.id))"
          >
            æ‰¹é‡åˆ é™¤
          </el-button>
        </div>
      </div>
      <PIMTable
        rowKey="id"
        isSelection
        :column="columns"
        :tableData="dataList"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
            ç¼–辑
          </el-button>
          <el-button
            type="primary"
            text
            @click="openFilesFormDia(row)"
          >
            é™„ä»¶
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <files-dia ref="filesDia"></files-dia>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
import { onMounted, getCurrentInstance } from "vue";
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import FilesDia from "../revenueManagement/filesDia.vue";
defineOptions({
  name: "支出管理",
});
// è¡¨æ ¼å¤šé€‰æ¡†é€‰ä¸­é¡¹
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const { checkout_payment } = proxy.useDict("checkout_payment");
const { expense_types } = proxy.useDict("expense_types");
const filesDia = ref()
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
  listPage,
  {
    expenseMethod: undefined,
    entryDate: undefined,
  },
  [
    {
      label: "支出日期",
      align: "center",
      prop: "expenseDate",
    },
    {
      label: "支出类型",
      align: "center",
      prop: "expenseType",
      dataType: "tag",
      formatData: (params) => {
        if (expense_types.value.find((m) => m.value == params)) {
          return expense_types.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "供应商名称",
      align: "center",
      prop: "supplierName",
    },
    {
      label: "支出金额",
      align: "center",
      prop: "expenseMoney",
    },
    {
      label: "支出描述",
      align: "center",
      prop: "expenseDescribed",
    },
    {
      label: "付款方式",
      align: "center",
      prop: "expenseMethod",
      dataType: "tag",
      formatData: (params) => {
        if (checkout_payment.value.find((m) => m.value == params)) {
          return checkout_payment.value.find((m) => m.value == params).label;
        } else {
          return null
        }
      },
    },
    {
      label: "发票号码",
      align: "center",
      prop: "invoiceNumber",
    },
    {
      label: "备注",
      align: "center",
      prop: "note",
    },
    {
      label: "录入人",
      align: "center",
      prop: "inputUser",
    },
    {
      label: "录入日期",
      align: "center",
      prop: "inputTime",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
    },
  ]
);
// å¤šé€‰åŽåšä»€ä¹ˆ
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const add = () => {
  modalRef.value.openModal();
};
const edit = (id) => {
  modalRef.value.loadForm(id);
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
    pagination.pageSize = limit;
  onCurrentChange(page);
};
const deleteRow = (id) => {
  ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(async () => {
    const { code } = await delAccountExpense(id);
    if (code == 200) {
      ElMessage({
        type: "success",
        message: "删除成功",
      });
      getTableData();
    }
  });
};
const changeDaterange = (value) => {
  if (value) {
    filters.entryDate = value;
    filters.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    filters.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    filters.entryDate = null;
    filters.entryDateStart = undefined;
    filters.entryDateEnd = undefined;
  }
  getTableData();
};
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download(`/account/accountExpense/export`, {}, "支出台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
  nextTick(() => {
    filesDia.value?.openDialog( row,'支出')
  })
};
onMounted(() => {
  getTableData();
});
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/financialManagement/financialStatements/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,507 @@
 <template>
  <div style="padding: 20px;">
    <!-- é¡µé¢æ ‡é¢˜å’Œæ—¥æœŸç­›é€‰ -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        format="YYYY-MM-DD"
        value-format="YYYY-MM-DD"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        clearable
        @change="handleDateChange"
        class="w-full md:w-auto"
        style="margin-right: 30px;"
      />
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetDateRange"
        size="default"
      >
        é‡ç½®
      </el-button>
    </div>
    <main class="container mx-auto px-4 pb-10">
      <!-- è´¢åŠ¡æŒ‡æ ‡å¡ç‰‡ -->
      <div class="grid-container">
        <!-- æ€»æ”¶å…¥ -->
        <el-card class="bg1">
          <p>总收入</p>
          <h3>
            Â¥{{ pageInfo.totalIncome }}
          </h3>
        </el-card>
        <!-- æ”¶å…¥ç¬”æ•° -->
        <el-card class="bg2">
          <p>收入笔数</p>
          <h3>
            {{ pageInfo.incomeNumber }}
          </h3>
        </el-card>
        <!-- æ€»æ”¯å‡º -->
        <el-card class="bg3">
          <p>总支出</p>
          <h3>
            Â¥{{ pageInfo.totalExpense }}
          </h3>
        </el-card>
        <!-- æ”¯å‡ºç¬”æ•° -->
        <el-card class="bg4">
          <p>支出笔数</p>
          <h3>
            {{ pageInfo.expenseNumber }}
          </h3>
        </el-card>
        <!-- å‡€æ”¶å…¥ -->
        <el-card class="bg5">
          <p>净收入</p>
          <h3>
            Â¥{{ pageInfo.netRevenue }}
          </h3>
        </el-card>
      </div>
      <!-- æ”¶å…¥ç»Ÿè®¡å›¾è¡¨ -->
      <div class="grid-layout">
        <el-card style="margin-bottom: 20px;">
          <h2 class="section-title">收入统计(元)</h2>
          <div class="echarts">
            <Echarts :legend="pieLegend0" :chartStyle="chartStylePie"
                                         :series="materialPieSeries0"
                                         :tooltip="pieTooltip" style="height: 260px;width: 35%;">
                     <div class="chart-num">
                      <span style="font-size: 22px;">收入</span>
                      <span style="font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalIncome }}</span>
                     </div>
                    </Echarts>
            <Echarts ref="chart"
                                 :chartStyle="chartStyle"
                                 :grid="grid"
                                 :legend="lineLegend"
                                 :series="lineSeries0"
                                 :tooltip="tooltip"
                                 :xAxis="xAxis0"
                                 :yAxis="yAxis0"
                                 style="height: 260px;width: 64%;"></Echarts>
          </div>
        </el-card>
        <!-- æ”¯å‡ºç»Ÿè®¡å›¾è¡¨ -->
        <el-card>
          <h2 class="section-title">支出统计(元)</h2>
          <div class="echarts">
            <Echarts ref="chart"
                    :legend="pieLegend1"
                    :chartStyle="chartStylePie"
                                         :series="materialPieSeries1"
                                         :tooltip="pieTooltip"
                     style="height: 260px;width: 35%;">
                     <div class="chart-num">
                      <span style="font-size: 22px;">支出</span>
                      <span style="font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalExpense }}</span>
                     </div></Echarts>
            <Echarts ref="chart"
                                 :chartStyle="chartStyle"
                                 :grid="grid"
                                 :legend="lineLegend"
                                 :series="lineSeries1"
                                 :tooltip="tooltip"
                                 :xAxis="xAxis1"
                                 :yAxis="yAxis1"
                                 style="height: 260px;width: 64%;"></Echarts>
          </div>
        </el-card>
      </div>
    </main>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, reactive } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { reportForms,reportIncome,reportExpense } from "@/api/financialManagement/financialStatements";
import dayjs from "dayjs";
// æ—¥æœŸèŒƒå›´
const dateRange = ref(null);
const chartStyle = {
    width: '100%',
    height: '100%', // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
  position:'relative',
}
const grid = {
    left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
}
const lineLegend = {
    show: false,
}
// æŠ˜çº¿å›¾æç¤ºæ¡†
const tooltip = reactive({
  trigger: 'axis',
  axisPointer: {
    type: 'line',
    lineStyle: { color: '#aaa' }
  },
  // è‡ªå®šä¹‰å†…容
  formatter: function (params) {
    if (!params || !params.length) return ''
    const axisLabel = params[0].axisValueLabel || params[0].axisValue || ''
    const rows = params
      .map(p => {
        const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`
        return `${colorDot}${p.seriesName}: ${p.value}`
      })
      .join('<br/>')
    return `<div>${axisLabel}</div><div>${rows}</div>`
  }
})
const months = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
const lineSeries0 = ref([])
const lineSeries1 = ref([])
const xAxis0 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
  },
]);
const xAxis1 = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: months,
  },
]);
const yAxis0 = [
{
    type: 'value',
    name: '收入统计', // å·¦ä¾§yè½´
    position: 'left',
    min: 0,
    // åæ ‡è½´åç§°æ ·å¼
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
]
const yAxis1 = [
{
    type: 'value',
    name: '支出统计', // å·¦ä¾§yè½´
    position: 'left',
    min: 0,
    // åæ ‡è½´åç§°æ ·å¼
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
]
const chartStylePie = {
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const pieColors = ['#F04864','#FACC14', '#8543E0', '#1890FF', '#13C2C2','#2FC25B']; // å¯æ ¹æ®å®žé™…调整
const pieData0 = ref([]);
const pieData1 = ref([]);
const pieLegend0 = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData0.value.map(item => item.name),
  formatter: function(name) {
    const item = pieData0.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
const pieLegend1 = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: pieData1.value.map(item => item.name),
  formatter: function(name) {
    const item = pieData1.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.percent} ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
const materialPieSeries0 = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: pieData0.value,
    color: pieColors
  }
]);
const materialPieSeries1 = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: pieData1.value,
    color: pieColors
  }
]);
const pieTooltip = reactive({
    trigger: 'item',
  formatter: function(params) {
    // æ£€æŸ¥æ•°æ®æ˜¯å¦å­˜åœ¨
    if (!params.data) return params.name;
    // æ‹¼æŽ¥å®Œæ•´å†…容
    return `
      <div>
        <div style="color:${params.color};font-size:16px;">●</div>
        <div>${params.name}</div>
        <div>占比:${params.data.percent}</div>
        <div>金额:${params.data.amount}</div>
      </div>
    `;
  }
})
const pageInfo = ref({
})
const getData = async () => {
  if (!dateRange.value || !dateRange.value.length) {
    return;
  }
  try {
    const {code,data} = await reportForms({entryDateStart:dateRange.value[0], entryDateEnd:dateRange.value[1]});
    if(code === 200) {
      pageInfo.value = data
      pieData0.value = data.incomeType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
      }))
      pieData1.value = data.expenseType.map(item=>({
        name:item.typeName,
        value:item.account,
        percent:`${item.proportion*100}%`,
        amount:`Â¥${item.account}`
      }))
    }
  } catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportIncome();
    if(code==200){
      lineSeries0.value = data.map(item=>({
        name:item.typeName,
        type: 'line',
        data:item.account.map(item=>Number(item))
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
  try{
    const {code,data} = await reportExpense();
    if(code==200){
      lineSeries1.value = data.map(item=>({
        name:item.typeName,
        type: 'line',
        data:item.account.map(item=>Number(item))
      }))
    }
  }catch (error) {
    console.error('获取财务指标数据失败:', error);
  }
};
// åˆå§‹åŒ–
onMounted(() => {
  // ä¸è®¾ç½®é»˜è®¤æ—¥æœŸï¼Œç”±ç”¨æˆ·æ‰‹åŠ¨é€‰æ‹©
});
// å¤„理日期范围变化
const handleDateChange = (newRange) => {
  dateRange.value = newRange;
  if (newRange && newRange.length === 2) {
    getData()
  }
};
// é‡ç½®æ—¥æœŸèŒƒå›´
const resetDateRange = () => {
  dateRange.value = null;
};
</script>
<style scoped lang="scss">
/* åŸºç¡€æ ·å¼è¡¥å…… */
:root {
  --el-color-primary: #4f46e5;
}
.el-card{
  position: relative;
  border-radius: 12px;
  padding: 14px 10px 10px 10px;
  box-shadow: 0 2px 8px #eee;
  :deep(.el-card__body){
    padding: 10px 20px !important;
  }
  &.bg1{
    background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
  }
  &.bg2{
    background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
  }
  &.bg3{
    background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
  }
  &.bg4{
    background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
  }
  &.bg5{
    background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
  }
}
.grid-container {
  /* grid å®¹å™¨åŸºç¡€æ ·å¼ */
  display: grid;
  gap: 1rem; /* gap-4 å¯¹åº” 1rem (16px) */
  margin-bottom: 2rem; /* mb-8 å¯¹åº” 2rem (32px) */
  p{
    font-size: 22px;
    margin-top: 0px;
    color: #fff;
  }
  h3{
    font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;
    margin: 10px 0;
    color: #fff;
  }
}
/* ç§»åŠ¨ç«¯é»˜è®¤æ ·å¼ (grid-cols-1) */
.grid-container {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
/* å°å±å¹•及以上 (sm:grid-cols-2) */
@media (min-width: 640px) {
  .grid-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* å¤§å±å¹•及以上 (lg:grid-cols-5) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
/* å¡ç‰‡æ‚¬åœæ•ˆæžœå¢žå¼º */
.el-card:hover {
  transform: translateY(-2px);
}
.echarts{
  display: flex;
  justify-content: space-between;
}
/* å›¾è¡¨å®¹å™¨æ ·å¼ */
.el-chart {
  width: 100%;
  height: 100%;
}
.section-title {
    position: relative;
    font-size: 18px;
    color: #333;
    padding-left: 10px;
    margin-bottom: 10px;
    font-weight: 700;
}
.section-title::before {
    position: absolute;
    left: 0;
    top: 0px;
    content: '';
    width: 4px;
    height: 18px;
    background-color: #002FA7;
    border-radius: 2px;
}
.chart-num{
  position: absolute;
  z-index: 3;
  top: 92px;
  left: 92px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
</style>
src/views/financialManagement/inventoryAccounting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,390 @@
<template>
  <div class="inventory-statistics">
    <!-- ç­›é€‰è¡¨å• -->
    <div class="filter-form">
      <el-form :model="filterForm" inline>
<!--        <el-form-item label="时间范围">-->
<!--          <el-date-picker-->
<!--            v-model="filterForm.dateRange"-->
<!--            type="daterange"-->
<!--            range-separator="至"-->
<!--            start-placeholder="开始日期"-->
<!--            end-placeholder="结束日期"-->
<!--          />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="供应商名称">-->
<!--          <el-input v-model="filterForm.supplierName" style="width: 240px" placeholder="请输入" clearable prefix-icon="Search" />-->
<!--        </el-form-item>-->
<!--        <el-form-item label="产品名称">-->
<!--          <el-input v-model="filterForm.productCategory" style="width: 240px" placeholder="请输入" clearable prefix-icon="Search" />-->
<!--        </el-form-item>-->
        <el-form-item>
          <el-button type="primary" @click="handleSearch">查询</el-button>
<!--          <el-button @click="handleReset">重置</el-button>-->
<!--          <el-button type="success" @click="handleExport">导出</el-button>-->
        </el-form-item>
      </el-form>
    </div>
    <!-- ç»Ÿè®¡æ±‡æ€»å¡ç‰‡ -->
    <div class="summary-cards">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="summary-card">
            <div class="summary-item">
              <p class="summary-title">总库存数量</p>
              <p class="summary-value">{{ summaryData.totalInventoryCount }}</p>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="summary-card">
            <div class="summary-item">
              <p class="summary-title">总库存金额</p>
              <p class="summary-value">Â¥{{ summaryData.totalInventoryValue }}</p>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="summary-card">
            <div class="summary-item">
              <p class="summary-title">库存变动数量</p>
              <p class="summary-value">{{ summaryData.inventoryChangeCount }}</p>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="summary-card">
            <div class="summary-item">
              <p class="summary-title">库存变动金额</p>
              <p class="summary-value">Â¥{{ summaryData.inventoryChangeValue }}</p>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="chart-section">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card class="chart-card">
            <template #header>
              <div class="card-header">
                <span>库存分类占比</span>
              </div>
            </template>
            <div id="category-pie-chart" style="height: 400px;"></div>
          </el-card>
        </el-col>
        <el-col :span="12">
          <el-card class="chart-card">
            <template #header>
              <div class="card-header">
                <span>库存金额趋势</span>
              </div>
            </template>
            <div id="amount-trend-chart" style="height: 400px;"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table_list">
      <el-table
        :data="tableData"
        v-loading="loading"
        border
        style="width: 100%"
        :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="供应商名称" prop="supplierName" width="240" show-overflow-tooltip />
        <el-table-column label="产品" prop="productCategory" min-width="100" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" min-width="200" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="inboundNum" width="90" show-overflow-tooltip />
        <el-table-column label="库存数量" prop="inboundNum0" width="90" show-overflow-tooltip />
        <el-table-column label="含税单价" prop="taxInclusiveUnitPrice" width="100" show-overflow-tooltip />
        <el-table-column label="含税总价" prop="taxInclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="税率(%)" prop="taxRate" width="80" show-overflow-tooltip />
        <el-table-column label="不含税总价" prop="taxExclusiveTotalPrice" width="100" show-overflow-tooltip />
        <el-table-column label="入库人" prop="createBy" width="100" 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 { ref, reactive, onMounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import {getStockInPage} from "@/api/inventoryManagement/stockIn.js";
// çŠ¶æ€å˜é‡
const loading = ref(false)
const total = ref(0)
const tableData = ref([])
const summaryData = ref({})
const page = reactive({
  current: 1,
  size: 100,
})
// å›¾è¡¨å®žä¾‹
const categoryPieChart = ref(null)
const amountTrendChart = ref(null)
// ç­›é€‰è¡¨å•
const filterForm = reactive({
  dateRange: [],
  supplierName: '',
  productCategory: ''
})
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  loadData()
}
// åˆå§‹åŒ–数据
onMounted(() => {
  loadSummaryData()
  loadData()
})
// åŠ è½½ç»Ÿè®¡æ±‡æ€»æ•°æ®
const loadSummaryData = () => {
  getStockInChartData().then(res => {
    summaryData.value = res.data
  })
}
// åŠ è½½åº“å­˜æ•°æ®
const loadData = () => {
  loading.value = true
  getStockInPage({ ...filterForm, ...page }).then(res => {
    loading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    console.log('res', res.data.records)
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ¸²æŸ“å›¾è¡¨
    nextTick(() => {
      renderCategoryPieChart()
      renderAmountTrendChart()
    })
  }).catch(() => {
    loading.value = false
  })
}
// æ¸²æŸ“分类占比饼图
const renderCategoryPieChart = () => {
  if (!categoryPieChart.value) {
    categoryPieChart.value = echarts.init(document.getElementById('category-pie-chart'))
  }
  // æ ¹æ® tableData æŒ‰ productCategory åˆ†ç±»å¹¶è®¡ç®— inboundNum0 æ•°é‡æ€»å’Œ
  const categoryMap = tableData.value.reduce((acc, cur) => {
    acc[cur.productCategory] = (acc[cur.productCategory] || 0) + cur.inboundNum0
    return acc
  }, {})
  // å°†åˆ†ç±»ç»“果转换为 ECharts é¥¼å›¾æ‰€éœ€çš„æ•°æ®æ ¼å¼
  const categoryData = Object.entries(categoryMap).map(([name, value]) => ({
    name: name,
    value: value
  }))
  const option = {
    title: {
      text: '库存分类占比',
      left: 'center'
    },
    tooltip: {
      trigger: 'item',
      formatter: '{a} <br/>{b}: {c} ({d}%)'
    },
    legend: {
      orient: 'vertical',
      left: 'left'
    },
    series: [
      {
        name: '库存分类',
        type: 'pie',
        radius: ['40%', '70%'],
        avoidLabelOverlap: false,
        itemStyle: {
          borderRadius: 10,
          borderColor: '#fff',
          borderWidth: 2
        },
        label: {
          show: true,
          formatter: '{b}: {d}%'
        },
        emphasis: {
          label: {
            show: true,
            fontSize: '16',
            fontWeight: 'bold'
          }
        },
        data: categoryData
      }
    ]
  }
  categoryPieChart.value.setOption(option)
}
// æ¸²æŸ“金额趋势折线图
const renderAmountTrendChart = () => {
  if (!amountTrendChart.value) {
    amountTrendChart.value = echarts.init(document.getElementById('amount-trend-chart'))
  }
  // æŒ‰æœˆä»½åˆ†ç»„并计算taxInclusiveTotalPrice总和
  const monthlyAmounts = tableData.value.reduce((acc, cur) => {
    const date = new Date(cur.createTime);
    const month = date.getMonth() + 1;
    // ç¡®ä¿month在1-12范围内
    if (month >= 1 && month <= 12) {
      acc[month] = (acc[month] || 0) + cur.taxInclusiveTotalPrice;
    }
    return acc;
  }, {});
  // ç”Ÿæˆ12个月的数据,缺失月份用0代替
  const amounts = [];
  for (let i = 1; i <= 12; i++) {
    amounts.push(monthlyAmounts[i] || 0);
  }
  const dates = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  const option = {
    title: {
      text: '库存金额趋势',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis',
      formatter: '{b}: Â¥{c}'
    },
    xAxis: {
      type: 'category',
      data: dates
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        formatter: 'Â¥{value}'
      }
    },
    series: [
      {
        name: '库存金额',
        type: 'line',
        data: amounts,
        smooth: true,
        areaStyle: {}
      }
    ]
  }
  amountTrendChart.value.setOption(option)
}
// æŸ¥è¯¢æ“ä½œ
const handleSearch = () => {
  loadData()
}
// é‡ç½®æ“ä½œ
const handleReset = () => {
  filterForm.dateRange = []
  filterForm.supplierName = ''
  filterForm.productCategory = ''
  loadData()
}
// å¯¼å‡ºæ“ä½œ
const handleExport = () => {
  console.log('导出数据')
}
// çª—口大小改变时,重新调整图表大小
window.addEventListener('resize', () => {
  if (categoryPieChart.value) categoryPieChart.value.resize()
  if (amountTrendChart.value) amountTrendChart.value.resize()
})
</script>
<style scoped>
.inventory-statistics {
  padding: 20px;
}
.filter-form {
  margin-bottom: 20px;
}
.summary-cards {
  margin-bottom: 20px;
}
.summary-card {
  text-align: center;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.summary-item {
  width: 100%;
}
.summary-title {
  font-size: 14px;
  color: #606266;
  margin-bottom: 5px;
}
.summary-value {
  font-size: 24px;
  font-weight: bold;
  color: #303133;
}
.summary-value.warning {
  color: #e6a23c;
}
.summary-value.danger {
  color: #f56c6c;
}
.chart-section {
  margin-bottom: 20px;
}
.chart-card {
  height: 460px;
}
.card-header {
  font-weight: bold;
}
.table_list {
  margin-top: 20px;
}
.pagination {
  text-align: right;
  margin-top: 20px;
}
</style>
src/views/index.vue
@@ -1,674 +1,805 @@
<template>
  <div class="dashboard">
    <!-- é¡¶éƒ¨æ¨ªå‘两栏 -->
    <div class="dashboard-top">
      <!-- å·¦ï¼šç³»ç»Ÿæ¦‚览+数据卡片 -->
      <div class="top-left">
        <div class="system-info">
          <div class="section-title">承包商管理系统</div>
          <div style="display: flex; align-items: center; gap: 20px">
            <div class="system-card">
              <div class="system-name">承包商管理系统</div>
              <div class="system-meta">资质审核 Â· åˆåŒç®¡ç† Â· ç»©æ•ˆè¯„ä¼°</div>
            </div>
            <div style="display: flex; align-items: center; gap: 8px">
              <el-icon color="#5053B5" size="22"><Clock /></el-icon>
              <span>当前时间:{{ currentTime }}</span>
            </div>
          </div>
        </div>
        <div class="data-cards">
          <div class="data-card total">
            <div class="data-title">总承包商数</div>
            <div class="data-value">{{ contractorStats.total }}</div>
            <div class="data-desc">
              å·²å®¡æ ¸ {{ contractorStats.approved }} | å¾…审核
              {{ contractorStats.pending }}
            </div>
          </div>
          <div class="data-card pending">
            <div class="data-title">待审核承包商</div>
            <div class="data-value">{{ contractorStats.pending }}</div>
            <div class="data-desc">
              A级 {{ contractorStats.aPending }} | B级
              {{ contractorStats.bPending }} | C级
              {{ contractorStats.cPending }}
            </div>
          </div>
          <div class="data-card today">
            <div class="data-title">本月新增</div>
            <div class="data-value">{{ contractorStats.monthly }}</div>
            <div class="data-desc">
              åŒæ¯” {{ contractorStats.monthly同比 }}% | çŽ¯æ¯”
              {{ contractorStats.monthly环比 }}%
            </div>
          </div>
        </div>
      </div>
      <!-- å³ï¼šå¾…处理报警列表 -->
      <div class="alarm-panel">
        <div class="section-title">待审核承包商</div>
        <ul class="alarm-list" v-if="pendingContractors.length > 0">
          <li v-for="item in pendingContractors" :key="item.id">
            <div
              style="
                display: flex;
                flex-direction: column;
                justify-content: space-between;
                width: 100%;
                gap: 10px;
              "
            >
              <div
                style="
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
                "
              >
                <div class="alarm-title">{{ item.name }} - {{ item.type }}</div>
                <el-tag :type="getContractorLevelType(item.level)"
                  >{{ item.level }}级</el-tag
                >
              </div>
              <div
                style="
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
                "
              >
                <div class="alarm-value">
                  è”系人: {{ item.contact }} | æ³¨å†Œæ—¶é—´: {{ item.registerDate }}
                </div>
                <div class="alarm-time">{{ item.applyDate }}</div>
              </div>
            </div>
          </li>
        </ul>
        <div v-else style="text-align: center; color: #909399; padding: 20px">
          æš‚无待审核承包商
        </div>
      </div>
    </div>
    <!-- ä¸­éƒ¨æ¨ªå‘两栏 -->
    <div class="dashboard-row">
      <div class="main-panel">
        <div class="section-title">承包商增长趋势</div>
        <Echarts
          ref="chart"
          :chartStyle="chartStyle"
          :grid="grid"
          :legend="lineLegend"
          :series="lineSeries"
          :tooltip="tooltipLine"
          :xAxis="xAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
      <div class="main-panel">
        <div class="section-title">承包商资质分布</div>
        <div
          style="
            display: flex;
            align-items: center;
            gap: 20px;
            justify-content: space-evenly;
            height: 300px;
          "
        >
          <div style="width: 50%">
            <Echarts
              ref="chart"
              :legend="pieLegend"
              :chartStyle="chartStylePie"
              :series="levelPieSeries"
              :tooltip="pieTooltip"
            ></Echarts>
          </div>
          <ul class="level-list" style="width: 50%">
            <li v-for="item in levelPieSeries[0].data" :key="item.name">
              <div
                style="
                  display: flex;
                  align-items: center;
                  justify-content: space-between;
                  width: 100%;
                "
              >
                <div class="line" :style="{ color: item.itemStyle.color }">
                  â—{{ item.name }}
                </div>
                <div style="width: 60px">{{ item.value }}å®¶</div>
                <div style="width: 60px">{{ item.rate }}%</div>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <!-- åº•部横向两栏 -->
    <div class="dashboard-row">
      <div class="main-panel">
        <div class="section-title">承包商类型分布</div>
        <Echarts
          ref="chart"
          :color="barColors"
          :chartStyle="chartStyle"
          :grid="grid"
          :series="equipmentBarSeries"
          :tooltip="tooltip"
          :xAxis="equipmentXAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
      <div class="main-panel">
        <div class="section-title">承包商审核时效</div>
        <Echarts
          ref="chart"
          :color="barColors"
          :chartStyle="chartStyle"
          :grid="grid"
          :series="handlingTimeSeries"
          :tooltip="tooltip"
          :xAxis="handlingTimeXAxis"
          :yAxis="yAxis"
          style="height: 300px"
        ></Echarts>
      </div>
    </div>
  </div>
    <div class="dashboard">
        <!-- é¡¶éƒ¨æ¨ªå‘两栏 -->
        <div class="dashboard-top">
            <!-- å·¦ï¼šä¼ä¸šä¿¡æ¯+三大数据卡片(上下排列) -->
            <div class="top-left">
                <div class="company-info">
                    <div class="section-title">登陆信息</div>
                    <div style="display: flex;align-items: center;gap: 20px">
                        <img :src="userStore.avatar" class="avatar" alt=""/>
                        <div class="company-card">
                            <div class="company-name">{{userStore.name}}</div>
                            <div class="company-meta">{{userStore.roleName}}</div>
                        </div>
                        <div style="display: flex;align-items: center;gap: 8px">
                            <el-icon color="#5053B5" size="22"><Clock /></el-icon>
                            <span>登陆日期:{{userStore.currentLoginTime}}</span>
                        </div>
                    </div>
                </div>
<!--                <div class="data-cards">-->
<!--                    <div class="data-card sales">-->
<!--                        <div class="data-title">销售数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">本月销售额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthSaleMoney}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">未开票金额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthSaleHaveMoney}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                        -->
<!--                    </div>-->
<!--                    <div class="data-card purchase">-->
<!--                        <div class="data-title">采购数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">本月采购额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthPurchaseMoney}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">待付款金额/元</div>-->
<!--                                <div class="data-value">{{businessInfo.monthPurchaseHaveMoney}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                    <div class="data-card inventory">-->
<!--                        <div class="data-title">库存数据</div>-->
<!--                        <div class="data-num">-->
<!--                            <div>-->
<!--                                <div class="data-desc">当前库存总量/ä»¶</div>-->
<!--                                <div class="data-value">{{businessInfo.inventoryNum}}</div>-->
<!--                            </div>-->
<!--                            <div>-->
<!--                                <div class="data-desc">今日入库/ä»¶</div>-->
<!--                                <div class="data-value">{{businessInfo.todayInventoryNum}}</div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </div>-->
            </div>
            <!-- å³ï¼šå¾…办事项 -->
<!--            <div class="todo-panel">-->
<!--                <div class="section-title">待办事项</div>-->
<!--                <ul class="todo-list" v-if="todoList.length > 0">-->
<!--                    <li v-for="item in todoList" :key="item.id">-->
<!--                        <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px">-->
<!--                            <div style="display: flex;justify-content: space-between;align-items: center;">-->
<!--                                <div class="todo-title">流程编号:{{item.approveId}}</div>-->
<!--                                <div class="todo-division">申请部门:{{item.approveDeptName}}</div>-->
<!--                                <div class="todo-time">{{item.approveTime}}</div>-->
<!--                            </div>-->
<!--                            <div class="todo-division">审批事由:{{item.approveReason}}</div>-->
<!--                        </div>-->
<!--                    </li>-->
<!--                </ul>-->
<!--                <div v-else style="text-align: center">-->
<!--                    æš‚无数据-->
<!--                </div>-->
<!--            </div>-->
<!--        </div>-->
<!--        -->
<!--        &lt;!&ndash; ä¸­éƒ¨æ¨ªå‘两栏 &ndash;&gt;-->
<!--        <div class="dashboard-row">-->
<!--            <div class="main-panel">-->
<!--                <div class="section-title">客户合同金额分析</div>-->
<!--                <div class="contract-summary">-->
<!--                    <div class="contract-info">-->
<!--                        <img src="../assets/images/khtitle.png" alt="" style="width: 42px"/>-->
<!--                        <div class="contract-card">-->
<!--                            <div class="contract-name">总合同金额(元)</div>-->
<!--                            <div class="contract-meta">-->
<!--                                <div class="main-amount">{{sum}}</div>-->
<!--                                <div>周同比: <span class="up">{{yny}}% </span> æ—¥çŽ¯æ¯”: <span class="up">{{chain}}% </span></div>-->
<!--                            </div>-->
<!--                        </div>-->
<!--                    </div>-->
<!--                </div>-->
<!--                <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px">-->
<!--                    <div>-->
<!--                        <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie"-->
<!--                                         :series="materialPieSeries"-->
<!--                                         :tooltip="pieTooltip"></Echarts>-->
<!--                    </div>-->
<!--                    <ul class="contract-list">-->
<!--                        <li v-for="item in materialPieSeries[0].data" :key="item.name">-->
<!--                            <div style="display: flex;align-items: center;justify-content: space-between;width: 100%">-->
<!--                                <div class="line" :style="{color: item.itemStyle.color}">●{{item.name}}</div>-->
<!--                                <div style="width: 70px">{{item.rate}}%</div>-->
<!--                                <div>ï¿¥{{item.value}}</div>-->
<!--                            </div>-->
<!--                        </li>-->
<!--                    </ul>-->
<!--                </div>-->
<!--            </div>-->
<!--            <div class="main-panel">-->
<!--                <div style="display: flex;justify-content: space-between;">-->
<!--                    <div class="section-title">应收应付统计</div>-->
<!--&lt;!&ndash;                    <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable">&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按周" :value="1" />&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按月" :value="2" />&ndash;&gt;-->
<!--&lt;!&ndash;                        <el-radio-button label="按季度" :value="3" />&ndash;&gt;-->
<!--&lt;!&ndash;                    </el-radio-group>&ndash;&gt;-->
<!--                </div>-->
<!--                <Echarts ref="chart"-->
<!--                                 :color="barColors2"-->
<!--                                 :chartStyle="chartStyle"-->
<!--                                 :grid="grid"-->
<!--                                 :series="barSeries"-->
<!--                                 :tooltip="tooltip"-->
<!--                                 :xAxis="xAxis"-->
<!--                                 :yAxis="yAxis"-->
<!--                                 style="height: 260px"></Echarts>-->
<!--            </div>-->
<!--        </div>-->
        <!-- åº•部横向两栏 -->
<!--        <div class="dashboard-row">-->
<!--            <div class="main-panel">-->
<!--                <div class="section-title">质量统计</div>-->
<!--                <div class="quality-cards">-->
<!--                    <div class="quality-card one">原材料已检测数 <span>{{qualityStatisticsObject.supplierNum}}ä»¶</span></div>-->
<!--                    <div class="quality-card two">过程检验数量 <span>{{qualityStatisticsObject.processNum}}ä»¶</span></div>-->
<!--                    <div class="quality-card three">出厂已检数量 <span>{{qualityStatisticsObject.factoryNum}}ä»¶</span></div>-->
<!--                </div>-->
<!--                <Echarts ref="chart"-->
<!--                                 :chartStyle="chartStyle"-->
<!--                                 :grid="grid"-->
<!--                                 :legend="barLegend"-->
<!--                                 :series="barSeries1"-->
<!--                                 :tooltip="tooltip"-->
<!--                                 :xAxis="xAxis1"-->
<!--                                 :yAxis="yAxis1"-->
<!--                                 style="height: 260px"></Echarts>-->
<!--            </div>-->
<!--            <div class="main-panel">-->
<!--                <div class="section-title">回款与开票分析</div>-->
<!--                <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries"-->
<!--                                 :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;"></Echarts>-->
<!--            </div>-->
        </div>
    </div>
</template>
<script setup>
import { ref, onMounted, reactive, computed } from "vue";
import { ref, onMounted } from 'vue'
import Echarts from "@/components/Echarts/echarts.vue";
import { Clock } from "@element-plus/icons-vue";
import * as echarts from 'echarts';
import useUserStore from "@/store/modules/user.js";
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    getBusiness,
    homeTodos,
    qualityStatistics,
    statisticsReceivablePayable
} from "@/api/viewIndex.js";
// å½“前时间
const currentTime = computed(() => {
  const now = new Date();
  return now.toLocaleString("zh-CN");
});
const userStore = useUserStore()
// æ‰¿åŒ…商统计数据
const contractorStats = reactive({
  total: 156,
  approved: 132,
  pending: 24,
  aPending: 8,
  bPending: 12,
  cPending: 4,
  monthly: 28,
  monthly同比: "+18.5",
  monthly环比: "+5.2",
});
const businessInfo = ref({
    inventoryNum: 0,
    monthPurchaseHaveMoney: 0,
    monthPurchaseMoney: 0,
    monthSaleHaveMoney: 0,
    monthSaleMoney: 0,
    todayInventoryNum: 0,
})
const qualityStatisticsObject = ref({
    supplierNum: 0,
    processNum: 0,
    factoryNum: 0,
})
const sum = ref(0)
const yny = ref(0)
const chain = ref(0)
// å¾…审核承包商列表
const pendingContractors = ref([
  {
    id: 1,
    name: "建筑工程有限公司",
    type: "建筑施工",
    contact: "张三",
    registerDate: "2025-01-15",
    applyDate: "2025-12-16 14:30:23",
    level: "A",
  },
  {
    id: 2,
    name: "机电安装工程公司",
    type: "机电安装",
    contact: "李四",
    registerDate: "2025-03-20",
    applyDate: "2025-12-16 14:28:15",
    level: "B",
  },
  {
    id: 3,
    name: "装饰装修工程公司",
    type: "装饰装修",
    contact: "王五",
    registerDate: "2025-05-10",
    applyDate: "2025-12-16 14:22:18",
    level: "B",
  },
  {
    id: 4,
    name: "绿化工程有限公司",
    type: "园林绿化",
    contact: "赵六",
    registerDate: "2025-08-05",
    applyDate: "2025-12-16 14:18:55",
    level: "C",
  },
]);
// å›¾è¡¨æ ·å¼
const pieLegend = reactive({
    show: false,
})
const barSeries = ref([
    {
        type: 'bar',
        data: [],
        label: {
            show: true,
        }
    },
])
const barSeries1 = ref([
    {
        name: '原材料不合格数',
        type: 'bar',
        barGap: 0,
        emphasis: {
            focus: 'series'
        },
        data: []
    },
    {
        name: '过程不合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        data: []
    },
    {
        name: '出厂不合格数',
        type: 'bar',
        emphasis: {
            focus: 'series'
        },
        data: []
    },
])
const chartStyle = {
  width: "100%",
  height: "100%",
};
    width: '100%',
    height: '100%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const chartStylePie = {
  width: "100%",
  height: "100%",
};
    width: '140%',
    height: '140%' // è®¾ç½®å›¾è¡¨å®¹å™¨çš„高度
}
const grid = {
  left: "3%",
  right: "4%",
  bottom: "3%",
  containLabel: true,
};
// æ‰¿åŒ…商增长趋势 - æŠ˜çº¿å›¾
    left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
}
const barLegend = {
    show: true,
    data: ['原材料不合格数', '过程不合格数', '出厂不合格数']
}
const barLegend1 = {
    show: true,
    data: ['预付账款', '应付账款', '预收账款', '应收账款']
}
const lineLegend = {
  show: true,
  data: ["A级", "B级", "C级"],
};
const tooltipLine = {
  trigger: "axis",
  axisPointer: {
    type: "cross",
  },
};
const xAxis = ref({
  type: "category",
  data: ["12-01", "12-02", "12-03", "12-04", "12-05", "12-06", "12-07"],
});
const yAxis = ref({
  type: "value",
  name: "承包商数量",
});
const lineSeries = ref([
  {
    name: "A级",
    type: "line",
    data: [15, 18, 20, 22, 25, 28, 30],
    itemStyle: {
      color: "#f56c6c",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
  {
    name: "B级",
    type: "line",
    data: [45, 52, 58, 65, 70, 75, 80],
    itemStyle: {
      color: "#e6a23c",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
  {
    name: "C级",
    type: "line",
    data: [30, 35, 40, 45, 50, 55, 60],
    itemStyle: {
      color: "#67c23a",
    },
    lineStyle: {
      width: 2,
    },
    showSymbol: true,
  },
]);
// æ‰¿åŒ…商资质分布 - é¥¼å›¾
const pieLegend = {
  show: false,
};
const pieTooltip = {
  trigger: "item",
  formatter: "{b}: {c}å®¶ ({d}%)",
};
const levelPieSeries = ref([
  {
    type: "pie",
    radius: ["60%", "80%"],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: "#fff",
      borderWidth: 2,
    },
    label: {
      show: false,
    },
    data: [
      {
        name: "A级",
        value: 45,
        rate: 28.85,
        itemStyle: { color: "#f56c6c" },
      },
      {
        name: "B级",
        value: 70,
        rate: 44.87,
        itemStyle: { color: "#e6a23c" },
      },
      {
        name: "C级",
        value: 41,
        rate: 26.28,
        itemStyle: { color: "#67c23a" },
      },
    ],
  },
]);
// æ‰¿åŒ…商类型分布 - æŸ±çж图
const barColors = ["#409eff", "#67c23a", "#e6a23c", "#f56c6c", "#909399"];
    show: true,
    data: ['开票', '回款']
}
const tooltip = {
  trigger: "axis",
  axisPointer: {
    type: "shadow",
  },
};
    trigger: 'axis',
    axisPointer: {
        type: 'shadow'
    }
}
const xAxis = [{
    type: 'value',
}]
const xAxis1 = ref([{
    type: 'category',
    axisTick: { show: false },
    data: []
}])
const yAxis = [{
    type: 'category',
    data: [ '应付账款', '应收账款',]
}]
const yAxis1 = [{
    type: 'value'
}]
const pieTooltip = reactive({
    trigger: 'item',
    formatter: function (params) {
        // åŠ¨æ€ç”Ÿæˆæç¤ºä¿¡æ¯ï¼ŒåŸºäºŽæ•°æ®é¡¹çš„ name å±žæ€§
        const description = params.name === '本月回款金额' ? '本月回款金额' : '应收款金额';
        return `${description} ${formatNumber(params.value)}元 ${params.percent}%`;
    },
    position: 'right'
})
const materialPieSeries = ref([
    {
        type: 'pie',
        radius: ['66%', '90%'],
        avoidLabelOverlap: false,
        itemStyle: {
            borderColor: '#fff',
            borderWidth: 2
        },
        label: {
            show: false
        },
        data: []
    }
])
const lineSeries = ref([
    {
        type: 'line',
        data: [],
        label: {
            show: true
        },
    showSymbol: true, // æ˜¾ç¤ºåœ†ç‚¹
    },
])
const tooltipLine = {
    trigger: 'axis',
}
const yAxis2 = ref([
    {
        type: 'value',
    }
])
const xAxis2 = ref([
    {
        type: 'category',
        data: [],
        axisLabel: {
            interval: 0,
            formatter: function(value) {
                return value.replace(/~/g, '\n');
            },
        }
    }
])
const equipmentXAxis = ref({
  type: "category",
  data: ["建筑施工", "机电安装", "装饰装修", "园林绿化", "其他"],
});
// å¾…办事项
const todoList = ref([])
const radio1 = ref(1)
const equipmentBarSeries = ref([
  {
    name: "承包商数量",
    type: "bar",
    data: [45, 35, 25, 20, 31],
    itemStyle: {
      color: function (params) {
        return barColors[params.dataIndex % barColors.length];
      },
    },
    label: {
      show: true,
      position: "top",
    },
  },
]);
// å›¾è¡¨å¼•用
const barChart = ref(null)
const lineChart = ref(null)
const barColors2 = ['#5181DB', '#D369E0', '#F2CA6D', '#60CCA8']
// æ‰¿åŒ…商审核时效分析 - æŸ±çж图
const handlingTimeXAxis = ref({
  type: "category",
  data: ["0-1天", "1-3天", "3-7天", "7天以上"],
});
const handlingTimeSeries = ref([
  {
    name: "审核数量",
    type: "bar",
    data: [85, 45, 20, 10],
    itemStyle: {
      color: function (params) {
        return barColors[params.dataIndex % barColors.length];
      },
    },
    label: {
      show: true,
      position: "top",
    },
  },
]);
// èŽ·å–æ‰¿åŒ…å•†çº§åˆ«æ ·å¼
const getContractorLevelType = (level) => {
  switch (level) {
    case "A":
      return "danger";
    case "B":
      return "warning";
    case "C":
      return "info";
    default:
      return "info";
  }
};
// éšæœºé¢œè‰²ç”Ÿæˆå‡½æ•°
const getRandomColor = () => {
  return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
}
onMounted(() => {
  // é¡µé¢åŠ è½½å®ŒæˆåŽçš„åˆå§‹åŒ–æ“ä½œ
});
    getBusinessData()
    analysisCustomer()
    todoInfoS()
    statisticsReceivable()
    qualityStatisticsInfo()
    getAmountHalfYearNum()
})
// æ•°æ®ç»Ÿè®¡
const getBusinessData = () => {
    getBusiness().then((res) => {
        businessInfo.value = {...res.data}
    })
}
// åˆåŒé‡‘额
const analysisCustomer = () => {
    analysisCustomerContractAmounts().then((res) => {
        sum.value = res.data.sum
        yny.value = res.data.yny
        chain.value = res.data.chain
    // ä¸ºæ¯ä¸ªæ•°æ®é¡¹åˆ†é…éšæœºé¢œè‰²
    materialPieSeries.value[0].data = res.data.item.map(item => ({
      ...item,
      itemStyle: { color: getRandomColor() }
    }))
    })
}
// å¾…办事项
const todoInfoS = () => {
    homeTodos().then((res) => {
        todoList.value = res.data
    })
}
// åº”付应收统计
const statisticsReceivable = (type) => {
    console.log(type)
    statisticsReceivablePayable({type: radio1.value}).then((res) => {
        barSeries.value[0].data = [
            // { value: res.data.prepayMoney, itemStyle: { color: barColors2[0] } },
            { value: res.data.payableMoney, itemStyle: { color: barColors2[0] } },
            // { value: res.data.advanceMoney, itemStyle: { color: barColors2[2] } },
            { value: res.data.receivableMoney, itemStyle: { color: barColors2[1] } }
        ]
    })
}
// è´¨æ£€ç»Ÿè®¡
const qualityStatisticsInfo = () => {
    qualityStatistics().then((res) => {
        res.data.item.forEach(item => {
            xAxis1.value[0].data.push(item.date)
            barSeries1.value[0].data.push(item.supplierNum)
            barSeries1.value[1].data.push(item.processNum)
            barSeries1.value[2].data.push(item.factoryNum)
        })
        qualityStatisticsObject.value.supplierNum = res.data.supplierNum
        qualityStatisticsObject.value.processNum = res.data.processNum
        qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
}
const getAmountHalfYearNum = async () => {
    const res = await getAmountHalfYear()
    console.log(res)
    const monthName = []
    const receiptAmount = []
    const invoiceAmount = []
    res.data.forEach(item => {
        monthName.push(item.month)
        receiptAmount.push(item.receiptAmount)
        invoiceAmount.push(item.invoiceAmount)
    })
    // æ­£ç¡®å“åº”式赋值:创建新的 xAxis å’Œ series å¯¹è±¡
    xAxis2.value[0].data = monthName
    xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~'));
    lineSeries.value = [
        {
            name: '开票',
            type: 'line',
            data: receiptAmount,
            stack: 'Total',
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(131, 207, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(186, 228, 255, 1)'
                    }
                ])
            },
            itemStyle: {
                color: '#2D99FF',
                borderColor: '#2D99FF'
            },
            emphasis: {
                focus: 'series'
            },
            lineStyle: {
                width: 0
            },
            showSymbol: true,
        },
        {
            name: '回款',
            type: 'line',
            data: invoiceAmount,
            stack: 'Total',
            lineStyle: {
                width: 0
            },
            itemStyle: {
                color: '#83CFFF',
                borderColor: '#83CFFF'
            },
            showSymbol: true,
            areaStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                        offset: 0,
                        color: 'rgba(54, 153, 255, 1)'
                    },
                    {
                        offset: 1,
                        color: 'rgba(89, 169, 254, 1)'
                    }
                ])
            },
            emphasis: {
                focus: 'series'
            },
        }
    ]
}
</script>
<style scoped>
.dashboard {
  background: #f5f7fa;
  min-height: 100vh;
  padding: 20px;
  box-sizing: border-box;
    background: #f5f7fa;
    min-height: 100vh;
    padding: 20px;
    box-sizing: border-box;
}
.dashboard-top {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}
.company-info {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 20px;
    min-width: 0;
    background-color: #EFF2FB; /* ä½¿ç”¨æŒ‡å®šçš„背景颜色 */
    background-image: url("../assets/images/denglu.png");
    background-size: 100% 260%;
    background-position: center;
    background-repeat: no-repeat;
    border-radius: 12px;
    height: 138px;
}
.avatar {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    object-fit: contain;
    background: #fff;
    border: 1px solid #eee;
}
.company-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: relative;
    padding-right: 15px;
}
.system-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 20px;
  min-width: 0;
  background-color: #eff2fb;
  border-radius: 12px;
  height: 138px;
.company-card::after {
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: #C9C5C5;
    border-radius: 2px;
}
.system-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  padding-right: 15px;
.company-name {
    font-weight: 400;
    font-size: 16px;
    color: #161A9A;
}
.system-name {
  font-weight: 600;
  font-size: 18px;
  color: #161a9a;
.company-meta {
    font-weight: 400;
    font-size: 12px;
    color: #818185;
}
.system-meta {
  font-weight: 400;
  font-size: 12px;
  color: #818185;
}
.data-cards {
  display: flex;
  gap: 16px;
  justify-content: flex-start;
  background: #ffffff;
  border-radius: 12px;
  padding: 20px;
    display: flex;
    gap: 16px;
    justify-content: flex-start;
    background: #ffffff;
    border-radius: 12px;
    padding: 20px;
}
.data-card {
  background: #fff;
  border-radius: 12px;
  padding: 16px;
  min-width: 160px;
  box-shadow: 0 2px 8px #eee;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 32%;
  height: 140px;
  transition: all 0.3s ease;
}
.data-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.data-card.total {
  background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
  color: #fff;
}
.data-card.pending {
  background: linear-gradient(135deg, #f56c6c 0%, #f78989 100%);
  color: #fff;
}
.data-card.today {
  background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
  color: #fff;
}
.data-title {
  font-weight: 600;
  font-size: 16px;
  margin-bottom: 12px;
    font-weight: 700;
    font-size: 26px;
    color: #FFFFFF;
}
.data-value {
  font-size: 32px;
  font-weight: bold;
  margin-bottom: 8px;
.data-num {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 20px;
}
.data-card {
    background: #fff;
    border-radius: 12px;
    padding: 14px 10px 10px 10px;
    min-width: 160px;
    box-shadow: 0 2px 8px #eee;
    display: flex;
    flex-direction: column;
    width: 32%;
    height: 140px;
}
.data-card.sales {
    background-image: url("../assets/images/xioashoushuju.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.data-card.purchase {
    background-image: url("../assets/images/caigou.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.data-card.inventory {
    background-image: url("../assets/images/kucun.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.data-desc {
  font-size: 12px;
  opacity: 0.9;
    font-weight: 500;
    font-size: 13px;
    color: #FFFFFF;
}
.data-value {
    font-size: 18px;
    font-weight: 500;
    margin: 10px 0;
    color: #FFFFFF;
}
.top-left {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 60%;
    display: flex;
    flex-direction: column;
    gap: 20px;
    width: 50%;
}
.alarm-panel {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  width: 40%;
.todo-panel {
    background: #fff;
    border-radius: 12px;
    padding: 20px;
    width: 50%;
}
.alarm-list {
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: 14px;
  overflow-y: auto;
  height: 260px;
.todo-list {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 15px;
    overflow-y: auto;
    height: 260px;
}
.alarm-list li {
  border-radius: 8px;
  margin-bottom: 12px;
  padding: 12px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #f8f9fa;
  border-left: 4px solid #409eff;
  transition: all 0.3s ease;
.todo-list li {
    border-radius: 8px;
    margin-bottom: 12px;
    padding: 8px 20px;
    height: 74px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: rgba(225,227,250,0.62);
}
.alarm-list li:hover {
  background: #e9ecef;
  transform: translateX(4px);
.todo-title {
    font-weight: 400;
    font-size: 12px;
    color: #000000;
    position: relative;
}
.alarm-title {
  font-weight: 600;
  font-size: 14px;
  color: #303133;
.todo-title::before {
    content: ''; /* å¿…需,表示这里有一个内容 */
    position: absolute;
    left: -10px; /* å®šä½åˆ°å·¦ä¾§ */
    top: 50%; /* åž‚直居中 */
    transform: translateY(-50%); /* å¾®è°ƒåž‚直居中 */
    width: 6px; /* åœ†çš„直径 */
    height: 6px; /* åœ†çš„直径 */
    background: #498CEB;
    border-radius: 50%; /* è®©å…¶å˜æˆåœ†å½¢ */
}
.alarm-value {
  font-size: 12px;
  color: #606266;
.todo-division {
    font-weight: 400;
    font-size: 12px;
    color: #000000;
}
.alarm-time {
  font-size: 12px;
  color: #909399;
.todo-time {
    font-weight: 400;
    font-size: 12px;
    color: #000000;
}
.todo-meta {
    color: #888;
    font-size: 13px;
}
.dashboard-row {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}
.main-panel {
  background: #fff;
  border-radius: 12px;
  padding: 20px;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
    background: #fff;
    border-radius: 12px;
    padding: 20px;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.section-title {
  position: relative;
  font-size: 18px;
  color: #333;
  padding-left: 10px;
  margin-bottom: 20px;
  font-weight: 700;
    position: relative;
    font-size: 18px;
    color: #333;
    padding-left: 10px;
    margin-bottom: 10px;
    font-weight: 700;
}
.section-title::before {
  position: absolute;
  left: 0;
  top: 4px;
  content: "";
  width: 4px;
  height: 18px;
  background-color: #409eff;
  border-radius: 2px;
    position: absolute;
    left: 0;
    top: 4px;
    content: '';
    width: 4px;
    height: 18px;
    background-color: #002FA7;
    border-radius: 2px;
}
.level-list {
  margin: 0;
  padding: 0;
  list-style: none;
  height: 200px;
  overflow-y: auto;
  width: 100%;
.contract-info {
    display: flex;
    align-items: center;
    gap: 20px;
    height: 90px;
    background: rgba(245,245,245,0.59);
    width: 100%;
    border-radius: 10px;
    padding: 10px 30px;
}
.level-list li {
  margin-bottom: 15px;
  padding: 10px;
  background: #f8f9fa;
  border-radius: 6px;
  transition: all 0.3s ease;
.contract-summary {
    display: flex;
    align-items: center;
    gap: 30px;
}
.level-list li:hover {
  background: #e9ecef;
.contract-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.contract-name {
    font-weight: 400;
    font-size: 14px;
    color: #050505;
}
.contract-meta {
    display: flex;
    align-items: center;
    width: 100%;
    gap: 80px;
}
.main-amount {
    font-size: 24px;
    color: rgba(51,50,50,0.85);
}
.up { color: #e57373; }
.contract-list {
    margin-top: 16px;
    font-size: 14px;
    color: #666;
    list-style: none;
    padding: 0;
    height: 190px;
    overflow-y: auto;
    width: 460px;
}
.line {
  position: relative;
  width: 80px;
  font-weight: 500;
    position: relative;
    width: 230px;
}
</style>
.line::after {
    content: '';
    position: absolute;
    right: 2px;
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: #C9C5C5;
    border-radius: 2px;
}
.contract-list li {
    margin-top: 10px;
}
.quality-cards {
    display: flex;
    gap: 12px;
    margin-bottom: 12px;
}
.quality-card {
    border-radius: 8px;
    padding: 15px 10px 10px 50px;
    font-weight: 400;
    font-size: 12px;
    color: rgba(0,0,0,0.67);
    width: 236px;
    height: 49px;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.quality-card.one {
    background-image: url("../assets/images/yuancailiao.png");
}
.quality-card.two {
    background-image: url("../assets/images/guocheng.png");
}
.quality-card.three {
    background-image: url("../assets/images/chuchang.png");
}
.quality-card span {
    color: #4fc3f7;
    font-weight: bold;
    margin-left: 6px;
}
.chart {
    width: 100%;
    height: 220px;
    margin-top: 10px;
}
</style>
src/views/inventoryManagement/dispatchLog/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,371 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title ml10">发料日期:</span>
                <el-date-picker
                    v-model="searchForm.timeStr"
                    type="date"
                    placeholder="请选择日期"
                    value-format="YYYY-MM-DD"
                    format="YYYY-MM-DD"
                    clearable
                    @change="handleQuery"
                />
                <span class="search_title ml10">产品大类:</span>
                <el-input
                    v-model="searchForm.productCategory"
                    style="width: 240px"
                    placeholder="请输入"
                    clearable
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
            </div>
            <div>
                <el-button @click="handleOut">导出</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <el-table
                :data="tableData"
                border
                v-loading="tableLoading"
                @selection-change="handleSelectionChange"
                :expand-row-keys="expandedRowKeys"
                :row-key="(row) => row.id"
                show-summary
                style="width: 100%"
                :summary-method="summarizeMainTable"
                height="calc(100vh - 18.5em)"
            >
                <el-table-column align="center" type="selection" width="55" />
                <el-table-column align="center" label="序号" type="index" width="60" />
                <el-table-column label="发料日期" prop="createTime" show-overflow-tooltip width="130"/>
                <el-table-column label="批次号" prop="code" width="130" show-overflow-tooltip />
                <el-table-column label="产品大类" prop="productCategory" show-overflow-tooltip />
                <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
                <el-table-column label="单位" prop="unit" show-overflow-tooltip />
                <el-table-column label="物品类型" prop="itemType" show-overflow-tooltip />
                <el-table-column label="发料数量" prop="inboundNum" show-overflow-tooltip />
                <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150"></el-table-column>
                <el-table-column label="总价(元)" prop="taxInclusiveTotalPrice" width="150"></el-table-column>
                <el-table-column label="发料人" prop="createBy" 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, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
import { ElMessageBox } from "element-plus";
import {
    delStockOut,
} from "@/api/inventoryManagement/stockOut.js";
import {
    getStockInPageByCustom,
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
});
const total = ref(0);
// æŸ¥è¯¢è¡¨å•数据
const data = reactive({
    searchForm: {
        supplierName: "",
        customerName: "",
        productCategory:'',
        timeStr: getCurrentDate(),
    },
});
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 params = { ...page }
    params.supplierName = searchForm.value.supplierName
    params.timeStr = searchForm.value.timeStr
    params.productCategory = searchForm.value.productCategory
    // ææ–™å‡ºåº“:调用自定义出库记录接口
    const apiCall = getStockInPageByCustom(params)
    apiCall
        .then((res) => {
            tableLoading.value = false;
            tableData.value = res.data.records;
            tableData.value.map((item) => {
                item.children = [];
                // å‰ç«¯è®¡ç®—总价
                const inboundNum = Number(item.inboundNum) || 0;
                // ææ–™å‡ºåº“:总价 = taxInclusiveUnitPrice Ã— inboundNum
                const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0;
                item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2);
            });
            total.value = res.data.total;
        })
        .catch(() => {
            tableLoading.value = false;
        });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    // è¿‡æ»¤æŽ‰å­æ•°æ®
    selectedRows.value = selection.filter((item) => item.id);
    console.log("selection", selectedRows.value);
};
const expandedRowKeys = ref([]);
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "contractAmount",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
    ]);
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            // æ ¹æ®ä¸åŒçš„ tab ç±»åž‹è°ƒç”¨ä¸åŒçš„导出接口
            const exportUrl = "/stockmanagement/exportTwo"
            proxy.download(exportUrl, {}, "出库台账.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            delStockOut({ids:ids}).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped lang="scss">
.print-preview-dialog {
    .el-dialog__body {
        padding: 0;
        max-height: 80vh;
        overflow-y: auto;
    }
}
.print-preview-container {
    .print-preview-header {
        padding: 15px;
        border-bottom: 1px solid #e4e7ed;
        text-align: center;
        .el-button {
            margin: 0 10px;
        }
    }
    .print-preview-content {
        padding: 20px;
        background-color: #f5f5f5;
        min-height: 400px;
    }
}
.print-page {
    width: 220mm;
    height: 90mm;
    padding: 10mm;
    margin: 0 auto;
    background: white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    margin-bottom: 10px;
    box-sizing: border-box;
}
.delivery-note {
    width: 100%;
    height: 100%;
    font-family: "SimSun", serif;
    font-size: 10px;
    line-height: 1.2;
    display: flex;
    flex-direction: column;
}
.header {
    text-align: center;
    margin-bottom: 8px;
    .company-name {
        font-size: 18px;
        font-weight: bold;
        margin-bottom: 4px;
    }
    .document-title {
        font-size: 16px;
        font-weight: bold;
    }
}
.info-section {
    margin-bottom: 8px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .info-row {
        line-height: 20px;
        .label {
            font-weight: bold;
            width: 60px;
            font-size: 14px;
        }
        .value {
            margin-right: 20px;
            min-width: 80px;
            font-size: 14px;
        }
    }
}
.table-section {
    margin-bottom: 4px;
    flex: 1;
    .product-table {
        width: 100%;
        border-collapse: collapse;
        border: 1px solid #000;
        th, td {
            border: 1px solid #000;
            padding: 6px;
            text-align: center;
            font-size: 14px;
            line-height: 1.4;
        }
        th {
            font-weight: bold;
        }
        .total-label {
            text-align: right;
            font-weight: bold;
        }
        .total-value {
            font-weight: bold;
        }
    }
}
.footer-section {
    .footer-row {
        display: flex;
        margin-bottom: 3px;
        line-height: 20px;
        justify-content: space-between;
        .footer-item {
            display: flex;
            margin-right: 20px;
            .label {
                font-weight: bold;
                width: 80px;
                font-size: 14px;
            }
            .value {
                min-width: 80px;
                font-size: 14px;
            }
            &.address-item {
                .address-value {
                    min-width: 200px;
                }
            }
        }
    }
}
@media print {
    .app-container {
        display: none;
    }
    .print-page {
        box-shadow: none;
        margin: 0;
        padding: 10mm;
        padding-left: 20mm;
        page-break-inside: avoid;
        page-break-after: always;
    }
    .print-page:last-child {
        page-break-after: avoid;
    }
}
</style>
src/views/inventoryManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,309 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">发放季度:</span>
                <el-select
                    style="width: 200px;"
                    @change="handleQuery"
                    v-model="searchForm.season"
                    placeholder="请选择"
                    :clearable="false"
                >
                    <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" />
                </el-select>
                <span class="search_title ml10">员工名称:</span>
                <el-input
                    v-model="searchForm.staffName"
                    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="add" icon="Plus"> æ–°å¢ž </el-button>
                <el-button @click="handleOut" icon="download">导出</el-button>
                <el-button
                    type="danger"
                    icon="Delete"
                    :disabled="multipleList.length <= 0"
                    @click="deleteRow(multipleList.map((item) => item.id))"
                >
                    æ‰¹é‡åˆ é™¤
                </el-button>
            </div>
        </div>
        <div class="table_list">
            <el-table
                ref="tableRef"
                v-loading="tableLoading"
                :data="tableData"
                border
                height="calc(100vh - 21em)"
                :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                style="width: 100%"
                @selection-change="handleSelectionChange"
            >
                <!-- é€‰æ‹©åˆ— -->
                <el-table-column
                    align="center"
                    type="selection"
                    width="55"
                    fixed="left"
                />
                <!-- åºå·åˆ— -->
                <el-table-column
                    align="center"
                    label="序号"
                    type="index"
                    width="60"
                    fixed="left"
                />
                <!-- å›ºå®šåˆ—:姓名 -->
                <el-table-column
                    label="姓名"
                    prop="staffName"
                    width="100"
                    show-overflow-tooltip
                    align="center"
                    fixed="left"
                />
                <!-- å›ºå®šåˆ—:工号 -->
                <el-table-column
                    label="工号"
                    prop="staffNo"
                    width="100"
                    show-overflow-tooltip
                    align="center"
                    fixed="left"
                />
                <!-- åŠ¨æ€åˆ—ï¼šæ ¹æ®å­—å…¸æ¸²æŸ“ -->
                <el-table-column
                    v-for="(dictItem, index) in sys_lavor_issue"
                    :key="dictItem.value"
                    :label="dictItem.label"
                    :prop="dictItem.value"
                    show-overflow-tooltip
                >
                </el-table-column>
                <!-- æ“ä½œåˆ— -->
                <el-table-column
                    label="操作"
                    width="150"
                    align="center"
                    fixed="right"
                >
                    <template #default="scope">
                        <el-button
                            type="primary"
                            link
                            size="small"
                            @click="edit(scope.row)"
                        >
                            ç¼–辑
                        </el-button>
                        <el-button
                            type="danger"
                            link
                            size="small"
                            :disabled="!!scope.row.adoptedDate"
                            @click="adopted(scope.row)"
                        >
                            é¢†ç”¨
                        </el-button>
                    </template>
                </el-table-column>
            </el-table>
            <pagination :total="total" layout="total, sizes, prev, pager, next, jumper"
                                    :page="page.current" :limit="page.size" @pagination="paginationChange" />
        </div>
        <!-- <Modal ref="modalRef" @success="handleQuery"></Modal> -->
        <!-- <files-dia ref="filesDia"></files-dia> -->
    </div>
</template>
<script setup>
import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue'
import dayjs from "dayjs";
// import Modal from "./Modal.vue";
// import FilesDia from "./filesDia.vue";
import Pagination from "@/components/Pagination/index.vue";
import {listPage, deleteLedger, update} from "@/api/lavorissce/ledger.js";
import {ElMessageBox, ElMessage} from "element-plus";
const { proxy } = getCurrentInstance();
import { getCurrentMonth } from "@/utils/util"
const page = ref({
    current: 1,
    size: 100,
})
const total = ref(0)
// å“åº”式数据
const tableRef = ref(null)
const tableData = ref([])
const tableLoading = ref(false)
const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
const data = reactive({
    searchForm: {
        season: "",
        staffName: "",
    },
});
const { searchForm } = toRefs(data);
const modalRef = ref();
// const filesDia = ref();
const multipleList = ref([]);
const jidu = ref([
    {
        value: '1',
        label: '第一季度'
    },
    {
        value: '2',
        label: '第二季度'
    },
    {
        value: '3',
        label: '第三季度'
    },
    {
        value: '4',
        label: '第四季度'
    }
])
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.value.current = 1;
    getList();
};
// èŽ·å–å­—å…¸æ•°æ®
const getList = async () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page.value };
    listPage(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        total.value = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
}
const add = () => {
    modalRef.value.openModal();
};
const edit = (row) => {
    modalRef.value.loadForm(row);
};
const deleteRow = (id) => {
    ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        const { code } = await deleteLedger(id);
        if (code == 200) {
            ElMessage({
                type: "success",
                message: "删除成功",
            });
            await getList();
        }
    });
};
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season}, "劳保台账.xlsx");
        })
        .catch(() => {
            ElMessage.info("已取消");
        });
};
const adopted = (row) => {
    ElMessageBox.confirm("是否确认领用?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        const params = {
            id: row.id,
            adoptedDate: dayjs().format("YYYY-MM-DD")
        }
        const { code } = await update(params);
        if (code == 200) {
            ElMessage({
                type: "success",
                message: "领用成功",
            });
            await getList();
        }
    })
}
// æ‰“开附件弹框
// const openFilesFormDia = (row) => {
//     nextTick(() => {
//         filesDia.value?.openDialog( row,'收入')
//     })
// };
// äº‹ä»¶å¤„理函数
const handleSelectionChange = (selection) => {
    multipleList.value = selection;
}
const paginationChange = (pagination) => {
    page.value.current = pagination.page;
    page.value.size = pagination.limit;
    getList();
}
// ç»„件挂载时加载字典数据
onMounted(() => {
    handleQuery()
})
</script>
<style scoped>
.dynamic-table-container {
    width: 100%;
}
.pagination-container {
    margin-top: 20px;
    display: flex;
    justify-content: flex-end;
}
:deep(.el-table .el-table__header-wrapper th) {
    background-color: #F0F1F5 !important;
    color: #333333;
    font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
    padding: 8px 0;
}
:deep(.el-select) {
    width: 100%;
}
:deep(.el-input) {
    width: 100%;
}
</style>
src/views/inventoryManagement/issueManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,337 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">产品大类:</span>
        <el-input
          v-model="searchForm.productCategory"
          style="width: 240px"
          placeholder="请输入"
          clearable
        />
        <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"
        @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys"
        :row-key="row => row.id"
        show-summary
        style="width: 100%"
        :summary-method="summarizeMainTable"
        height="calc(100vh - 18.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="批次号" prop="code" width="130" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="物品类型" prop="itemType" show-overflow-tooltip />
        <el-table-column label="剩余库存" prop="inboundNum0" width="90" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" width="100" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm(scope.row);">发料</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="40%" @close="closeDia" draggable>
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
            <div>{{getAvailableQuantityText()}}:{{currentRowNum}}</div>
        <el-form-item :label="getQuantityLabel()" prop="salesContractNo">
          <el-input-number :step="0.01" :min="0" :max="currentRowNum" style="width: 100%" v-model="form.inboundQuantity" placeholder="请输入" clearable />
        </el-form-item>
        <el-form-item :label="getDateLabel()" prop="projectName">
          <el-date-picker style="width: 100%" v-model="form.inboundTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
            type="date" placeholder="请选择" clearable />
        </el-form-item>
        <el-form-item :label="getPersonLabel()" prop="entryPerson">
          <el-select v-model="form.nickName"
                                         filterable
                     default-first-option
                     :reserve-keyword="false" placeholder="请选择" clearable>
            <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
          </el-select>
        </el-form-item>
      </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>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import {
    getInPageByCustom
} from "@/api/inventoryManagement/stockIn.js";
import {
    delStockManage,
    stockOut,
} from "@/api/inventoryManagement/stockManage.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const tableData = ref([])
const selectedRows = ref([])
const userList = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
const fileList = ref([])
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const dialogFormVisible = ref(false)
const data = reactive({
  searchForm: {
    inboundQuantity:'',
    inboundTime:'',
    nickName: '',
    userId: '',
    productCategory:'',
    // timeStr: getCurrentDate(),
  },
  form: {
    productrecordId: '',
  },
  rules: {
    inboundTime: [{ required: true, message: "请选择", trigger: "change" }],
    inboundQuantity: [{ required: true, message: "请输入", trigger: "blur" }],
    nickname: [{ required: true, message: "请选择", trigger: "change" }]
  }
})
const { searchForm, form, rules } = 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 params = { ...page }
  params.productCategory = searchForm.value.productCategory
  params.flag = true
  const apiCall = getInPageByCustom(params)
  apiCall.then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
  }).catch(() => {
    tableLoading.value = false
  })
}
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode.label; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
const currentRowId = ref(null) // æ–°å¢žï¼šå­˜å‚¨å½“前操作的行ID
const currentRowNum = ref(0)
const salesLedgerProductId = ref(null);
// æ‰“开弹框
const openForm = async (row) => {
  dialogFormVisible.value = true
  currentRowId.value = row.id
  currentRowNum.value = row.inboundNum0
  salesLedgerProductId.value = row.salesLedgerProductId
  form.value = {}
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  try {
    const userLists = await userListNoPageByTenantId()
    userList.value = userLists.data
    // ææ–™å‡ºåº“默认发料人
    const defaultPersonName = '吴玉梅'
    const defaultPerson = userList.value.find(user => user.nickName === defaultPersonName)
    const defaultUserId = defaultPerson ? defaultPerson.userId : ''
    // åˆå§‹åŒ–表单数据
    form.value = {
      productrecordId: '',
      inboundQuantity: currentRowNum.value, // ä½¿ç”¨currentRowNum作为默认值
      inboundTime: getCurrentDate(), // é»˜è®¤å½“前日期
      nickName: defaultUserId, // æ ¹æ®tab类型设置默认发货人
    }
    console.log('form',form.value)
  } catch (error) {
    console.error('加载用户列表失败:', error)
    // å¦‚果加载失败,使用空值初始化
    form.value = {
      productrecordId: '',
      inboundQuantity: currentRowNum.value,
      inboundTime: getCurrentDate(),
      nickName: '',
    }
  }
}
// æäº¤è¡¨å•
const submitForm = () => {
  let num = Number(form.value.inboundQuantity)
  if(num <= 0 || num > currentRowNum.value){
    return proxy.$modal.msgWarning("请填入有效数字")
  }
  proxy.$refs["formRef"].validate(valid => {
    if (valid && currentRowId.value) {
      const outData = {
        id: currentRowId.value, // åŽŸå§‹è®°å½•ID
        salesLedgerProductId: 0,
        quantity: form.value.inboundQuantity, // å‡ºåº“数量
        time: form.value.inboundTime, // å‡ºåº“æ—¶é—´
        userId: form.value.nickName, // æ“ä½œäºº
        type: 3 // å‡ºåº“类型:自定义/材料
      }
      console.log(outData)
      stockOut(outData).then(res => {
        proxy.$modal.msgSuccess("提交成功")
        closeDia()
        getList()
      }).catch(err => {
        proxy.$modal.msgError("出库失败")
      })
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  dialogFormVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    const exportUrl = "/stockin/exportTwo"
    proxy.download(exportUrl, {}, '出库台账.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
    '选中的内容将被删除,是否确认删除?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    delStockManage(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// æ ¹æ®tab类型获取弹框标题
const getDialogTitle = () => {
  return '新增发料';
};
// æ ¹æ®tab类型获取可出库数量文本
const getAvailableQuantityText = () => {
  return '可发料数量';
};
// æ ¹æ®tab类型获取数量字段标签
const getQuantityLabel = () => {
  return '发料数量:';
};
// æ ¹æ®tab类型获取日期字段标签
const getDateLabel = () => {
  return '发料日期:';
};
// æ ¹æ®tab类型获取人员字段标签
const getPersonLabel = () => {
  return '发料人:';
};
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,401 @@
<template>
  <el-dialog v-model="dialogFormVisible" :title="getDialogTitle()" width="70%"
    @close="closeDia">
    <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
      <el-form-item label="采购订单号" prop="purchaseContractNumber">
        <el-select
          v-model="form.purchaseContractNumber"
          placeholder="请选择采购订单号"
          clearable
          filterable
          :loading="loadingPurchaseOptions"
          @change="handlePurchaseChange"
          :disabled="operationType === 'edit'"
          style="width: 100%"
        >
          <el-option
            v-for="item in purchaseOptions"
            :key="item.purchaseContractNumber"
            :label="formatPurchaseOption(item)"
            :value="item.purchaseContractNumber"
          />
        </el-select>
      </el-form-item>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
        @selection-change="handleSelectionChange"
      >
        <el-table-column align="center" type="selection" width="55" />
        <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" width="70" />
        <!-- <el-table-column label="供应商" prop="supplierName" width="100" /> -->
        <el-table-column label="采购数量" prop="quantity" width="100" />
        <el-table-column label="待入库数量" prop="quantity0" width="100" />
        <el-table-column label="本次入库数量" prop="quantityStock" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.quantityStock" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column label="税率(%)" prop="taxRate" width="120" />
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150">
                    <template #default="scope">
                        <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" :disabled="operationType === 'edit'"/>
                    </template>
                </el-table-column>
        <el-table-column
          label="总价(元)"
                    :formatter="formattedNumber"
          prop="taxInclusiveTotalPrice"
          width="150"
        >
        </el-table-column>
      </el-table>
    </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>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
  updateStockIn,
  addSutockIn,
  selectProductRecordListByPuechaserId
} from "@/api/inventoryManagement/stockIn.js";
import { purchaseListPage } from "@/api/procurementManagement/procurementLedger.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const selectedRows = ref([]) // äº§å“è¡¨æ ¼é€‰ä¸­è¡Œ
const purchaseOptions = ref([])
const loadingPurchaseOptions = ref(false)
const loading = ref(false);
const data = reactive({
  form: {
    id: null,
    purchaseContractNumber: '', // é‡‡è´­è®¢å•号
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    inboundTime: '',        // å…¥åº“æ—¶é—´
    inboundBatch: '',       // å…¥åº“批次
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
    supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
    inboundTime: [{ required: true, message: "请选择入库时间", trigger: "change" }],
    inboundBatch: [{ required: true, message: "请输入入库批次", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// åŠ¨æ€è®¡ç®—å¯¹è¯æ¡†æ ‡é¢˜
const getDialogTitle = () => {
  return operationType.value === 'add' ? '新增入库' : '编辑入库'
}
const formatPurchaseOption = (item = {}) => {
  const contract = item.purchaseContractNumber || '--';
  const supplier = item.supplierName ? ` Â· ${item.supplierName}` : '';
  return `${contract}${supplier}`;
};
const loadPurchaseOptions = async (keyword = '') => {
  try {
    loadingPurchaseOptions.value = true;
    const res = await purchaseListPage({
      current: -1,
      size: -1,
      purchaseContractNumber: keyword,
    });
    const records = res.data?.records || [];
    purchaseOptions.value = records;
    if (
      form.value.purchaseContractNumber &&
      !purchaseOptions.value.find(
        (item) => item.purchaseContractNumber === form.value.purchaseContractNumber
      )
    ) {
      purchaseOptions.value.push({
        purchaseContractNumber: form.value.purchaseContractNumber,
        supplierName: form.value.supplierName,
        supplierId: form.value.supplierId,
      });
    }
  } finally {
    loadingPurchaseOptions.value = false;
  }
};
const handlePurchaseChange = (value) => {
  form.value.purchaseContractNumber = value || '';
  const matched = purchaseOptions.value.find(
    (item) => item.purchaseContractNumber === value
  );
  if (matched) {
    form.value.supplierName = matched.supplierName || form.value.supplierName;
    form.value.supplierId = matched.supplierId || form.value.supplierId;
  }
  if (!value) {
    productList.value = [];
    return;
  }
  fetchProductsByContract();
};
const exceedsAddLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting)) {
    return false;
  }
  return stock > waiting;
};
const exceedsEditLimit = (product) => {
  const stock = Number(product?.quantityStock ?? 0);
  const waiting = Number(product?.quantity0 ?? 0);
  const original = Number(product?.originalQuantityStock ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(waiting) || !Number.isFinite(original)) {
    return false;
  }
  return stock > waiting + original;
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// è®¡ç®—总价
const calculateTotalPrice = (row) => {
  const quantityStock = Number(row?.quantityStock ?? 0);
  const taxInclusiveUnitPrice = Number(row?.taxInclusiveUnitPrice ?? 0);
  if (Number.isFinite(quantityStock) && Number.isFinite(taxInclusiveUnitPrice)) {
    row.taxInclusiveTotalPrice = quantityStock * taxInclusiveUnitPrice;
  } else {
    row.taxInclusiveTotalPrice = 0;
  }
};
const fetchProductsByContract = async () => {
  if (!form.value.purchaseContractNumber) {
    proxy.$modal.msgWarning('请选择合同号')
    return
  }
  try {
    loadingProducts.value = true
    const productRes = await selectProductRecordListByPuechaserId({
      purchaseContractNumber: form.value.purchaseContractNumber
    });
    if (!productRes.data || productRes.data.length === 0) {
      proxy.$modal.msgWarning('该合同下没有产品记录')
      productList.value = [];
      return
    }
    productList.value = productRes.data.map(item => {
      const quantityStock = Number(item?.quantity0 ?? 0);
      const taxInclusiveUnitPrice = Number(item?.taxInclusiveUnitPrice ?? 0);
      return {
        ...item,
        quantityStock,
        taxInclusiveUnitPrice,
        taxInclusiveTotalPrice: quantityStock * taxInclusiveUnitPrice,
        originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? 0),
      };
    })
  } catch (error) {
    console.error('查询产品记录失败:', error)
    proxy.$modal.msgError('查询产品记录失败')
    productList.value = [];
  } finally {
    loadingProducts.value = false
  }
}
const updatePro = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请先选择产品');
    return;
  }
  const target = selectedRows.value[0];
  const stock = Number(target?.quantityStock ?? 0);
  if (!Number.isFinite(stock) || stock <= 0) {
    proxy.$modal.msgWarning('请填写有效的入库数量');
    return;
  }
  if (exceedsEditLimit(target)) {
    proxy.$modal.msgError('本次入库数量不能超过原入库数量与待入库数量之和');
    return;
  }
  const stockInData = {
    id: selectedRows.value[0].recordId,
    quantityStock: Number(selectedRows.value[0].quantityStock),
  };
  await updateStockIn(stockInData)
  proxy.$modal.msgSuccess('修改入库成功')
  closeDia()
  emit('success')
}
const submitForm = async () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请先选择采购合同并选择产品')
    return
  }
  if(operationType.value !== 'add'){
    await updatePro()
    return
  }
  try {
    await proxy.$refs.formRef.validate()
    const invalidProducts = selectedRows.value.filter((product) => {
        const stock = Number(product?.quantityStock ?? 0);
        if (!Number.isFinite(stock) || stock <= 0) {
          return true;
        }
        return exceedsAddLimit(product);
    })
    if (invalidProducts.length > 0) {
      proxy.$modal.msgError('本次入库数量需大于0,且不能超过待入库数量')
      return
    }
    const stockInData = {
      ...form.value,
      inboundTime: formatDateTime(form.value.inboundTime),
      nickName: userStore.nickName,
      details: selectedRows.value.map(product => ({
        id: product.id,
        inboundQuantity: Number(product.quantityStock),
                taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice),
                taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice)
      })),
    };
    loading.value = true
    await addSutockIn(stockInData)
    proxy.$modal.msgSuccess('新增入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  emit('close')
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection.filter(item => item.id);
}
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  selectedRows.value = []
  await loadPurchaseOptions();
  if (type === 'add') {
    form.value = {
      id: null,
      purchaseContractNumber: '',
      supplierId: null,
      supplierName: '',
      inboundTime: '',
      inboundBatch: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    form.value = JSON.parse(JSON.stringify(row))
    try {
      loadingProducts.value = true
      const res = await selectProductRecordListByPuechaserId({
        purchaseContractNumber: form.value.purchaseContractNumber,
        id: row.id
      });
      productList.value = res.data.map(item => ({
        ...item,
        quantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
        taxInclusiveUnitPrice: Number(item?.taxInclusiveUnitPrice ?? 0),
        taxInclusiveTotalPrice: Number(item?.quantityStock ?? 0) * Number(item?.taxInclusiveUnitPrice ?? 0),
        originalQuantityStock: Number(item.quantityStock ?? item.inboundQuantity ?? row.inboundNum ?? 0),
      }))
      selectedRows.value = productList.value
    } catch (error) {
      console.error('加载产品失败:', error)
      proxy.$modal.msgError('加载产品失败')
      productList.value = []
    } finally {
      loadingProducts.value = false
    }
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDiaManual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,310 @@
<template>
  <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">
      <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
        <el-button type="primary" @click="addProductRow">新增</el-button>
      </div>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column label="产品大类" prop="productCategory" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.productCategory" placeholder="请输入产品大类" />
          </template>
        </el-table-column>
        <el-table-column label="规格型号" prop="specificationModel" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.specificationModel" placeholder="请输入规格型号" />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit" width="100">
          <template #default="scope">
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </template>
        </el-table-column>
        <el-table-column label="供应商" prop="supplierName" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.supplierName" placeholder="请输入供应商" />
          </template>
        </el-table-column>
        <el-table-column label="物品类型" prop="itemType" width="150">
          <template #default="scope">
            <el-input v-model="scope.row.itemType" placeholder="请输入物品类型" />
          </template>
        </el-table-column>
        <el-table-column label="入库数量" prop="inboundNum" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column label="入库日期" prop="inboundDate" width="180">
          <template #default="scope">
            <el-date-picker
              v-model="scope.row.inboundDate"
              type="date"
              placeholder="请选择入库日期"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.taxInclusiveUnitPrice" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
           label="总价(元)"
           prop="taxInclusiveTotalPrice"
           width="150"
         >
        </el-table-column>
        <el-table-column label="操作" width="80" v-if="operationType === 'add'">
          <template #default="scope">
            <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </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>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
  addStockInCustom,
  updateStockInCustom,
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const loading = ref(false);
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const taxRateOptions = [
  { label: '1', value: 1 },
  { label: '6', value: 6 },
  { label: '13', value: 13 },
]
const data = reactive({
  form: {
    id: null,
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// æ–°å¢žäº§å“è¡Œ
const addProductRow = () => {
  productList.value.push({
    id: null,
    productCategory: '',
    specificationModel: '',
    unit: '',
    supplierName: form.value.supplierName || '',
    itemType: '',
    inboundNum: 0,
    inboundDate: getCurrentDate(), // é»˜è®¤å½“天日期
    // quantityStock: 0,
    taxInclusiveUnitPrice: 0,
    taxInclusiveTotalPrice: 0,
    taxRate: null,
    taxExclusiveTotalPrice: 0,
  });
};
// åˆ é™¤äº§å“è¡Œ
const removeProductRow = (index) => {
  productList.value.splice(index, 1);
};
// è®¡ç®—总价(根据数量、单价和含税单价)
const calculateTotalPrice = (row) => {
  // è®¡ç®—普通总价:inboundNum * taxInclusiveUnitPrice
  const quantity = Number(row.inboundNum || 0);
  const taxInclusiveUnitPrice = Number(row.taxInclusiveUnitPrice || 0);
  row.taxInclusiveTotalPrice = quantity * taxInclusiveUnitPrice;
  calculateExclusivePrice(row);
};
// è®¡ç®—不含税总价(根据含税总价和税率)
const calculateExclusivePrice = (row) => {
  const taxInclusiveTotalPrice = Number(row.taxInclusiveTotalPrice || 0);
  const taxRate = Number(row.taxRate || 0);
  row.taxExclusiveTotalPrice = taxInclusiveTotalPrice / (1 + taxRate / 100);
};
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    if (!productList.value.length) {
      proxy.$modal.msgError('请至少添加一条产品数据')
      return
    }
    // éªŒè¯è‡ªå®šä¹‰æ·»åŠ çš„æ•°æ®å¿…å¡«å­—æ®µ
    for (let i = 0; i < productList.value.length; i++) {
      const product = productList.value[i];
      if (!product.productCategory || !product.specificationModel || !product.unit) {
        proxy.$modal.msgError(`第${i + 1}行产品数据未填写完整(产品大类、规格型号、单位为必填)`)
        return
      }
      if (!product.itemType) {
        proxy.$modal.msgError(`第${i + 1}行请选择物品类型`)
        return
      }
      if (!product.inboundDate) {
        proxy.$modal.msgError(`第${i + 1}行请选择入库日期`)
        return
      }
      const stock = Number(product?.inboundNum ?? 0);
      if (!Number.isFinite(stock) || stock <= 0) {
        proxy.$modal.msgError(`第${i + 1}行本次入库数量需大于0`)
        return
      }
    }
    const payloadList = productList.value.map(product => ({
      id: product.id ?? null,
            inboundNum: Number(product.inboundNum),
      productCategory: product.productCategory,
      specificationModel: product.specificationModel,
      unit: product.unit,
      supplierName: product.supplierName || form.value.supplierName,
      itemType: product.itemType,
      inboundDate: formatDateTime(product.inboundDate, false),
      taxRate: Number(product.taxRate || 0),
      taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
            taxInclusiveUnitPrice: Number(product.taxInclusiveUnitPrice || 0),
            taxInclusiveTotalPrice: Number(product.taxInclusiveTotalPrice || 0),
    }));
    loading.value = true
    if (operationType.value === 'edit') {
      const editPayload = payloadList[0]
      await updateStockInCustom(editPayload)
    } else {
      await addStockInCustom(payloadList)
    }
    proxy.$modal.msgSuccess(operationType.value === 'edit' ? '编辑自定义入库成功' : '新增自定义入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  productList.value = []
  emit('close')
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  if (type === 'add') {
    form.value = {
      id: null,
      supplierId: null,
      supplierName: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    // ç¼–辑模式:将行数据填充到表格中以支持修改
    form.value = {
      id: row?.id ?? null,
      supplierId: row?.supplierId ?? null,
      supplierName: row?.supplierName ?? '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: row?.remark ?? ''
    }
    productList.value = [{
      id: row?.id ?? null,
      productCategory: row?.productCategory ?? '',
      specificationModel: row?.specificationModel ?? '',
      unit: row?.unit ?? '',
      supplierName: row?.supplierName ?? '',
      itemType: row?.itemType ?? '',
      inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
      inboundDate: row?.inboundDate ?? row?.createTime ?? '',
      taxRate: Number(row?.taxRate ?? 0),
      taxInclusiveUnitPrice: Number(row?.taxInclusiveUnitPrice ?? 0),
      taxInclusiveTotalPrice: Number(row?.taxInclusiveTotalPrice ?? 0),
      taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
    }]
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/components/formDiaProduct.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,300 @@
<template>
  <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">
      <div style="margin-bottom: 10px;" v-if="operationType === 'add'">
        <el-button type="primary" @click="addProductRow">新增</el-button>
      </div>
      <el-table
        :data="productList"
        border
        v-loading="loadingProducts"
      >
        <el-table-column
          align="center"
          label="序号"
          type="index"
          width="60"
        />
        <el-table-column label="产品大类" prop="productCategory" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.productCategory" placeholder="请输入产品大类" />
          </template>
        </el-table-column>
        <el-table-column label="规格型号" prop="specificationModel" width="200">
          <template #default="scope">
            <el-input v-model="scope.row.specificationModel" placeholder="请输入规格型号" />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit" width="100">
          <template #default="scope">
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </template>
        </el-table-column>
        <el-table-column label="入库数量" prop="inboundNum" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.inboundNum" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column label="入库日期" prop="inboundDate" width="180">
          <template #default="scope">
            <el-date-picker
              v-model="scope.row.inboundDate"
              type="date"
              placeholder="请选择入库日期"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              style="width: 100%"
            />
          </template>
        </el-table-column>
        <el-table-column label="单价(元)" prop="unitPrice" width="150">
          <template #default="scope">
            <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.unitPrice" @change="() => calculateTotalPrice(scope.row)" />
          </template>
        </el-table-column>
        <el-table-column
           label="总价(元)"
           prop="totalPrice"
           width="150"
         >
        </el-table-column>
        <el-table-column label="操作" width="80" v-if="operationType === 'add'">
          <template #default="scope">
            <el-button type="danger" size="small" @click="removeProductRow(scope.$index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </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>
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import useUserStore from '@/store/modules/user'
import {
    addStockInCustom, updateProduct
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close', 'success'])
const operationType = ref('')// æ“ä½œç±»åž‹: 'add' æˆ– 'edit'
const dialogFormVisible = ref(false)// å¼¹æ¡†æ˜¾ç¤ºçŠ¶æ€
const productList = ref([]);// äº§å“åˆ—表数据
const loadingProducts = ref(false);// äº§å“åŠ è½½çŠ¶æ€
const loading = ref(false);
function formatDateTime(date = new Date(), includeTime = true) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  if (!includeTime) {
    return `${year}-${month}-${day}`;
  }
  const hours = String(d.getHours()).padStart(2, '0');
  const minutes = String(d.getMinutes()).padStart(2, '0');
  const seconds = String(d.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const itemTypeOptions = [
  { label: '物料', value: '物料' },
  { label: '原料', value: '原料' },
  { label: '成品', value: '成品' },
  { label: '其他', value: '其他' },
]
const taxRateOptions = [
  { label: '1', value: 1 },
  { label: '6', value: 6 },
  { label: '13', value: 13 },
]
const data = reactive({
  form: {
    id: null,
    supplierId: null,       // ä¾›åº”商ID
    supplierName: '',       // ä¾›åº”商名称
    recorderId: userStore.userId, // å½•入人ID
    recorderName: userStore.name, // å½•入人姓名
    entryDate: getCurrentDate(),  // å½•入日期
    remark: '',             // å¤‡æ³¨
  },
  rules: {
    supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }]
  }
})
const { form, rules } = toRefs(data)
// æ–°å¢žäº§å“è¡Œ
const addProductRow = () => {
  productList.value.push({
    id: null,
    productCategory: '',
    specificationModel: '',
    unit: '',
    supplierName: form.value.supplierName || '',
    itemType: '',
    inboundNum: 0,
    inboundDate: '',
    quantityStock: 0,
    unitPrice: 0,
    totalPrice: 0,
    taxRate: null,
    taxExclusiveTotalPrice: 0,
  });
};
// åˆ é™¤äº§å“è¡Œ
const removeProductRow = (index) => {
  productList.value.splice(index, 1);
};
// è®¡ç®—总价(根据数量、单价和含税单价)
const calculateTotalPrice = (row) => {
  // è®¡ç®—普通总价:inboundNum * unitPrice
  const quantity = Number(row.inboundNum || 0);
  const unitPrice = Number(row.unitPrice || 0);
  row.totalPrice = quantity * unitPrice;
  calculateExclusivePrice(row);
};
// è®¡ç®—不含税总价(根据含税总价和税率)
const calculateExclusivePrice = (row) => {
  const totalPrice = Number(row.totalPrice || 0);
  const taxRate = Number(row.taxRate || 0);
  row.taxExclusiveTotalPrice = totalPrice / (1 + taxRate / 100);
};
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    if (!productList.value.length) {
      proxy.$modal.msgError('请至少添加一条产品数据')
      return
    }
    // éªŒè¯è‡ªå®šä¹‰æ·»åŠ çš„æ•°æ®å¿…å¡«å­—æ®µ
    for (let i = 0; i < productList.value.length; i++) {
      const product = productList.value[i];
      if (!product.productCategory || !product.specificationModel || !product.unit) {
        proxy.$modal.msgError(`第${i + 1}行产品数据未填写完整(产品大类、规格型号、单位为必填)`)
        return
      }
      if (!product.inboundDate) {
        proxy.$modal.msgError(`第${i + 1}行请选择入库日期`)
        return
      }
      const stock = Number(product?.inboundNum ?? 0);
      if (!Number.isFinite(stock) || stock <= 0) {
        proxy.$modal.msgError(`第${i + 1}行本次入库数量需大于0`)
        return
      }
    }
    const payloadList = productList.value.map(product => ({
      id: product.id ?? null,
            inboundNum: Number(product.inboundNum),
      productCategory: product.productCategory,
      specificationModel: product.specificationModel,
      unit: product.unit,
      supplierName: product.supplierName || form.value.supplierName,
      itemType: product.itemType,
      inboundDate: formatDateTime(product.inboundDate, false),
      taxRate: Number(product.taxRate || 0),
      taxExclusiveTotalPrice: Number(product.taxExclusiveTotalPrice || 0),
            unitPrice: Number(product.unitPrice || 0),
    }));
    loading.value = true
    if (operationType.value === 'edit') {
      const editPayload = payloadList[0]
      await updateProduct(editPayload)
    } else {
      await addStockInCustom(payloadList)
    }
    proxy.$modal.msgSuccess(operationType.value === 'edit' ? '编辑自定义入库成功' : '新增自定义入库成功')
    closeDia()
    emit('success')
  } catch (error) {
    console.error('提交失败:', error)
    if (!error.errors) {
      proxy.$modal.msgError('操作失败,请重试')
    }
  } finally {
    loading.value = false
  }
}
const closeDia = () => {
  proxy.$refs.formRef.resetFields()
  dialogFormVisible.value = false
  productList.value = []
  emit('close')
}
const openDialog = async (type, row) => {
  operationType.value = type
  dialogFormVisible.value = true
  if (type === 'add') {
    form.value = {
      id: null,
      supplierId: null,
      supplierName: '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: ''
    }
    productList.value = []
  } else {
    // ç¼–辑模式:将行数据填充到表格中以支持修改
    form.value = {
      id: row?.id ?? null,
      supplierId: row?.supplierId ?? null,
      supplierName: row?.supplierName ?? '',
      recorderId: userStore.userId,
      recorderName: userStore.name,
      entryDate: getCurrentDate(),
      remark: row?.remark ?? ''
    }
    productList.value = [{
      id: row?.id ?? null,
      productCategory: row?.productCategory ?? '',
      specificationModel: row?.specificationModel ?? '',
      unit: row?.unit ?? '',
      supplierName: row?.supplierName ?? '',
      itemType: row?.itemType ?? '',
      inboundNum: Number(row?.inboundNum ?? row?.inboundQuantity ?? 0),
      inboundDate: row?.inboundDate ?? row?.createTime ?? '',
      taxRate: Number(row?.taxRate ?? 0),
      unitPrice: Number(row?.unitPrice ?? 0),
      taxExclusiveTotalPrice: Number(row?.taxExclusiveTotalPrice ?? 0),
    }]
  }
}
defineExpose({
  openDialog,
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/receiptManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,212 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">入库日期:</span>
        <el-date-picker
          v-model="searchForm.timeStr"
          type="date"
          placeholder="请选择日期"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          clearable
          @change="handleQuery"
        />
        <span class="search_title ml10">产品大类:</span>
        <el-input
          v-model="searchForm.productCategory"
          style="width: 240px"
          placeholder="请输入"
          clearable
        />
        <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="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys"
        :row-key="row => row.id"
        show-summary
        style="width: 100%"
        :summary-method="summarizeMainTable"
        height="calc(100vh - 18.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="入库时间" prop="inboundDate" width="100" show-overflow-tooltip />
        <el-table-column label="批次号" prop="code" width="130" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="70" show-overflow-tooltip />
        <el-table-column label="物品类型" prop="itemType" show-overflow-tooltip />
        <el-table-column label="入库数量" prop="inboundNum" width="100" show-overflow-tooltip />
        <el-table-column label="剩余库存" prop="inboundNum0" show-overflow-tooltip />
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150" />
        <el-table-column label="总价(元)" prop="taxInclusiveTotalPrice" width="150" />
        <el-table-column label="入库人" prop="createBy" width="80" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" width="100" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <form-dia-manual ref="formDiaManual" @close="handleQuery" @success="handleQuery"></form-dia-manual>
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import dayjs from 'dayjs'
import {
    delStockInCustom,
  getInPageByCustom,
} from "@/api/inventoryManagement/stockIn.js";
import FormDiaManual from './components/formDiaManual.vue'
import { getCurrentDate } from "@/utils/index.js";
const { proxy } = getCurrentInstance()
const tableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
const formDiaManual = ref()
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
const data = reactive({
  searchForm: {
    productCategory:'',
    timeStr: getCurrentDate(),
  },
})
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 params = { ...page }
  params.timeStr = searchForm.value.timeStr
  params.productCategory = searchForm.value.productCategory
    params.flag = false
  const apiCall = getInPageByCustom(params)
  apiCall.then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    // å‰ç«¯è®¡ç®—含税总价:含税总价 = taxInclusiveUnitPrice * å…¥åº“数量
    tableData.value = tableData.value.map(item => {
      const inboundNum = Number(item.inboundNum) || 0
      const taxInclusiveUnitPrice = Number(item.taxInclusiveUnitPrice) || 0
      item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * inboundNum).toFixed(2)
      return item
    })
    total.value = res.data.total
  }).catch(() => {
    tableLoading.value = false
  })
}
// æ‰“开弹框
const openForm = async (type, row) => {
  await nextTick(() => {
    formDiaManual.value?.openDialog(type, row)
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection.filter(item => item.id)
}
const expandedRowKeys = ref([])
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'])
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm('是否确认导出?', '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    const exportUrl = "/stockin/exportTwo"
    proxy.download(exportUrl, {}, '入库台账.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  const ids = selectedRows.value.map(item => item.id)
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    // ææ–™å…¥åº“删除
    delStockInCustom(ids).then(() => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    }).catch(() => {
      proxy.$modal.msgError("删除失败")
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss"></style>
src/views/inventoryManagement/stockManagement/components/FormDiaManual.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增材料库存' : '编辑材料库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @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="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="物品类型:" prop="itemType">
            <el-input disabled v-model="form.itemType" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单价(元):" prop="taxInclusiveUnitPrice">
            <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="总价(元):" prop="taxInclusiveTotalPrice">
            <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="自动计算" clearable />
          </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>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    itemType: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    itemType: [{ required: true, message: '请输入物品类型', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:总价 = å•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockManagement/components/FormDiaProduction.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增成品库存' : '编辑成品库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @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="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="单价(元):" prop="unitPrice">
            <el-input v-model="form.unitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="总价(元):" prop="totalPrice">
            <el-input disabled v-model="form.totalPrice" placeholder="自动计算" clearable />
          </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>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    unitPrice: '',
    totalPrice: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:总价 = å•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.unitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.totalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockManagement/components/FormDiaPurchase.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
<template>
  <el-dialog :model-value="dialogFormVisible" :title="operationType === 'add' ? '新增原料库存' : '编辑原料库存'" width="70%"
    @update:model-value="$emit('update:dialogFormVisible', $event)" @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="productCategory">
            <el-input disabled v-model="form.productCategory" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="规格型号:" prop="specificationModel">
            <el-input disabled v-model="form.specificationModel" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="单位:" prop="unit">
            <el-input disabled v-model="form.unit" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="入库时间:" prop="createTime">
            <el-date-picker style="width: 100%" v-model="form.createTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                            type="date" placeholder="请选择" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="库存数量:" prop="inboundNum">
            <el-input v-model="form.inboundNum" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="已出库数量:" prop="totalInboundNum">
            <el-input disabled v-model="form.totalInboundNum" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="待出库数量:" prop="inboundNum0">
            <el-input disabled v-model="form.inboundNum0" placeholder="请输入" clearable />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
            <el-input v-model="form.taxInclusiveUnitPrice" placeholder="请输入" clearable @input="calculateTotalPrice" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
            <el-input disabled v-model="form.taxInclusiveTotalPrice" placeholder="自动计算" clearable />
          </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>
</template>
<script setup>
import { ref, reactive, toRefs, watch } from 'vue'
const props = defineProps({
  dialogFormVisible: Boolean,
  operationType: String,
  formData: Object
})
const emit = defineEmits(['update:dialogFormVisible', 'submit', 'close'])
const formRef = ref()
const data = reactive({
  form: {
    productCategory: '',
    specificationModel: '',
    unit: '',
    createTime: '',
    inboundNum: '',
    totalInboundNum: '',
    inboundNum0: '',
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: ''
  },
  rules: {
    productCategory: [{ required: true, message: '请输入产品大类', trigger: 'blur' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    createTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundNum: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
    taxInclusiveUnitPrice: [{ required: true, message: '请输入含税单价', trigger: 'blur' }]
  }
})
const { form, rules } = toRefs(data)
// è®¡ç®—总价:含税总价 = å«ç¨Žå•ä»· Ã— å‰©ä½™åº“å­˜
const calculateTotalPrice = () => {
  const unitPrice = parseFloat(form.value.taxInclusiveUnitPrice) || 0
  const stockQuantity = parseFloat(form.value.inboundNum) || 0 // åº“存数量
  const outboundQuantity = parseFloat(form.value.totalInboundNum) || 0 // å·²å‡ºåº“数量
  const remainingStock = stockQuantity - outboundQuantity // å‰©ä½™åº“å­˜
  form.value.taxInclusiveTotalPrice = (unitPrice * remainingStock).toFixed(2)
}
// ç›‘听formData变化
watch(() => props.formData, (newVal) => {
  if (newVal) {
    form.value = { ...newVal }
    // æ•°æ®å˜åŒ–后重新计算总价
    calculateTotalPrice()
  }
}, { immediate: true })
// æäº¤è¡¨å•
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      emit('submit', form.value)
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  emit('close')
  emit('update:dialogFormVisible', false)
}
</script>
<style scoped lang="scss">
.dialog-footer {
  text-align: center;
}
</style>
src/views/inventoryManagement/stockManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,387 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">入库日期:</span>
        <el-date-picker
          v-model="searchForm.timeStr"
          type="date"
          placeholder="请选择日期"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          clearable
          @change="handleQuery"
        />
        <span class="search_title ml10">产品大类:</span>
        <el-input
          v-model="searchForm.productCategory"
          style="width: 240px"
          placeholder="请输入"
          clearable
        />
        <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"
        @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys"
        :row-key="row => row.id"
        show-summary
        style="width: 100%"
        :row-class-name="tableRowClassName"
        :summary-method="summarizeMainTable"
        height="calc(100vh - 18.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="入库日期" prop="inboundDate" width="100" show-overflow-tooltip />
        <el-table-column label="批次号" prop="code" width="130" show-overflow-tooltip />
        <el-table-column label="产品大类" prop="productCategory" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="80" show-overflow-tooltip />
        <el-table-column label="物品类型" prop="itemType" width="120" show-overflow-tooltip />
        <el-table-column label="已发料数量" prop="totalInboundNum" width="100" show-overflow-tooltip />
        <el-table-column label="剩余库存" prop="inboundNum0" width="100" show-overflow-tooltip />
        <el-table-column label="单价(元)" prop="taxInclusiveUnitPrice" width="150" />
        <el-table-column label="总价(元)" prop="taxInclusiveTotalPrice" width="150" />
        <el-table-column fixed="right" label="操作" width="100" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <!-- ææ–™åº“存弹框 -->
    <FormDiaManual
      v-model:dialogFormVisible="manualDialogVisible"
      :operationType="operationType"
      :formData="form"
      @submit="submitForm"
      @close="closeDia"
    />
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ElMessageBox } from "element-plus";
import useUserStore from '@/store/modules/user'
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { productTreeList,modelList } from "@/api/basicData/product.js"
import {
  getStockManagePageByCustom,
  delStockManage,
} from "@/api/inventoryManagement/stockManage.js";
import {
    updateManagement, updateManagementByCustom, updateStockIn
} from "@/api/inventoryManagement/stockIn.js";
import { getCurrentDate } from "@/utils/index.js";
// å¯¼å…¥ææ–™åº“存弹框组件
import FormDiaManual from './components/FormDiaManual.vue'
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const tableData = ref([])
const productData = ref([])
const selectedRows = ref([])
const userList = ref([])
const productList = ref([])
const productModelList = ref([])
// const customerOption = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
const fileList = ref([])
const loading = ref(false);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref('')
// ææ–™åº“存弹框显示状态
const manualDialogVisible = ref(false)
const data = reactive({
  searchForm: {
    // supplierName: '',
    productCategory:'',
    customerName: '',
    timeStr: getCurrentDate(),
  },
  form: {
    supplierId: null,
    productId: null,
    productName: '',
    userId: userStore.userId,
    nickName: '',
    productModelId: null,
    model: '',
    unit: '',
    productrecordId: null,
    taxInclusiveUnitPrice: '',
    taxInclusiveTotalPrice: '',
    taxRate: '',
    taxExclusiveTotalPrice: '',
    inboundTime: '',
    inboundBatch: '',
    stockQuantity: '',
    boundTime: '',
        warnNum: '', // æ–°å¢žæœ€ä½Žåº“存字段
    salesLedgerProductId: null,
  },
  rules: {
    // supplierName: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }],
    productCategory: [{ required: true, message: '请选择产品大类', trigger: 'change' }],
    specificationModel: [{ required: true, message: '请输入规格型号', trigger: 'blur' }],
    unit: [{ required: true, message: '请输入单位', trigger: 'blur' }],
    stockQuantity: [{ required: true, message: '请输入出库数量', trigger: 'blur' }],
    unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }], // æ·»åŠ æˆå“åº“å­˜å•ä»·çš„éªŒè¯è§„åˆ™
    taxInclusiveUnitPrice: [{ required: true, message: '请输入含税单价', trigger: 'blur' }],
    taxInclusiveTotalPrice: [{ required: true, message: '请输入含税总价', trigger: 'blur' }],
    taxRate: [{ required: true, message: '请输入税率', trigger: 'blur' }],
    taxExclusiveTotalPrice: [{ required: true, message: '请输入不含税总价', trigger: 'blur' }],
    boundTime: [{ required: true, message: '请选择库存时间', trigger: 'change' }],
    inboundTime: [{ required: true, message: '请选择入库时间', trigger: 'change' }],
    inboundPerson: [{ required: true, message: '请选择出库人', trigger: 'change' }],
        warnNum: [{ required: true, message: '请输入最低库存', trigger: 'blur' }],
  }
})
const { searchForm, form, rules } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
const buildQueryParams = () => {
  const params = {
    ...page,
    timeStr: searchForm.value.timeStr,
  }
  params.productCategory = searchForm.value.productCategory
  return params
}
const getList = () => {
  tableLoading.value = true
  const params = buildQueryParams()
  getStockManagePageByCustom(params).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    // ä¸ºè¡¨æ ¼æ•°æ®è‡ªåŠ¨è®¡ç®—æ€»ä»·
    tableData.value = tableData.value.map(item => {
      // è®¡ç®—剩余库存
      const stockQuantity = parseFloat(item.inboundNum) || 0
      const outboundQuantity = parseFloat(item.totalInboundNum) || 0
      const remainingStock = Math.max(stockQuantity - outboundQuantity, 0)
      // ææ–™åº“存:含税总价 = å«ç¨Žå•ä»· Ã— å‰©ä½™åº“å­˜
      const taxInclusiveUnitPrice = parseFloat(item.taxInclusiveUnitPrice) || 0
      item.taxInclusiveTotalPrice = (taxInclusiveUnitPrice * remainingStock).toFixed(2)
      return item
    })
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ['contractAmount', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
  const stock = Number(row?.inboundNum0 ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
    return '';
  }
  return stock < warn ? 'row-low-stock' : '';
};
// æ‰“开弹框
const openForm = async (type, row) => {
  operationType.value = type
  form.value = {}
  productData.value = []
  let userLists = await userListNoPageByTenantId()
  userList.value = userLists.data
  if (type === 'edit') {
    form.value = { ...row }
    productTreeList().then(res =>{
      productList.value = res
      productList.value.forEach(i =>{
        if (i.label === row.productCategory) {
          modelList({ id: i.id }).then((res) => {
            productModelList.value = res;
          });
        }
      })
    })
  }
  form.value.entryDate = getCurrentDate() // è®¾ç½®é»˜è®¤å½•入日期为当前日期
  // ä»…材料库存弹框
  manualDialogVisible.value = true
}
// æäº¤è¡¨å•
const submitForm = (submittedData) => {
  console.log('子组件提交的数据:', submittedData)
  // ä½¿ç”¨å­ç»„件提交的数据,而不是父组件的form对象
  const submitData = { ...submittedData }
  // ææ–™åº“存:移除含税总价字段
  delete submitData.taxInclusiveTotalPrice
  // ç§»é™¤å…¶ä»–可能的总价字段
  delete submitData.taxExclusiveTotalPrice
  console.log('提交给后端的数据(已移除总价字段):', submitData)
  // ææ–™åº“存使用 updateManagementByCustom æŽ¥å£
  updateManagementByCustom(submitData).then(res => {
    proxy.$modal.msgSuccess("提交成功")
    closeDia()
    getList()
    // æäº¤åŽæ£€æŸ¥åº“存并尝试创建请购单
    // checkStockAndCreatePurchase();
  }).catch(error => {
    console.error('提交失败:', error)
    proxy.$modal.msgError("提交失败,请重试")
  })
}
// æ£€æŸ¥åº“存并创建请购单
// const checkStockAndCreatePurchase = async () => {
//   const stockList = tableData.value;
//   // handList()
//   for (const item of stockList) {
//     if (item.inboundNum0 < item.warnNum) {
//       try {
//                 const stockInData = {
//                     id: item.id,
//                     quantityStock: item.warnNum + item.totalInboundNum,// ä½¿ç”¨æ–°æ ¼å¼åŒ–函数
//                 };
//                 loading.value = true
//                 await updateStockIn(stockInData)
//                 proxy.$modal.msgSuccess(`产品 ${item.productCategory} ä¿®æ”¹å…¥åº“成功`)
//                 loading.value = false
//       } catch (error) {
//         proxy.$modal.msgError(`产品 ${item.productCategory} ç”Ÿæˆè¯·è´­å•失败,请手动处理`);
//
//       }
//     }
//   }
// };
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef")
  manualDialogVisible.value = false
}
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    const exportParams = buildQueryParams()
    const exportUrl = "/stockin/exportCopyTwo"
    proxy.download(exportUrl, exportParams, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
    '选中的内容将被删除,是否确认删除?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    delStockManage({ids:ids}).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
  // checkStockAndCreatePurchase();
    // æ¯å°æ—¶æ£€æŸ¥ä¸€æ¬¡åº“å­˜
    // const intervalId = setInterval(checkStockAndCreatePurchase, 60 * 60 * 1000);
// onUnmounted(() => {
//   // ç»„件卸载时清除定时器
//   clearInterval(intervalId);
// });
})
</script>
<style scoped lang="scss">
:deep(.row-low-stock td) {
  background-color: #fde2e2;
  color: #c45656;
}
:deep(.row-low-stock:hover > td) {
  background-color: #fcd4d4;
}
</style>
src/views/inventoryManagement/stockReport/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,713 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <div class="search_left">
        <span class="search_title">报表类型:</span>
        <el-select
          v-model="searchForm.reportType"
          style="width: 150px;"
          placeholder="请选择"
          @change="handleReportTypeChange"
        >
          <el-option label="日报" value="daily" />
          <el-option label="月报" value="monthly" />
          <el-option label="作业报表" value="work" />
          <el-option label="进出存报表" value="inout" />
        </el-select>
        <span class="search_title ml10">时间范围:</span>
         <el-date-picker
           v-if="searchForm.reportType === 'daily'"
           v-model="searchForm.singleDate"
           type="date"
           placeholder="请选择日期"
           format="YYYY-MM-DD"
           value-format="YYYY-MM-DD"
           style="width: 200px;"
         />
        <el-date-picker
          v-else-if="searchForm.reportType === 'monthly'"
          v-model="searchForm.monthRange"
          type="monthrange"
          range-separator="至"
          start-placeholder="开始月份"
          end-placeholder="结束月份"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          style="width: 240px;"
        />
        <el-date-picker
          v-else
          v-model="searchForm.dateRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          style="width: 240px;"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æŸ¥è¯¢
        </el-button>
        <el-button @click="handleReset">重置</el-button>
      </div>
      <div class="search_right">
        <el-button type="success" @click="handleExport" icon="Download">
          å¯¼å‡ºæŠ¥è¡¨
        </el-button>
      </div>
    </div>
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <div class="stats_cards" v-if="reportData.summary">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon in">
                <el-icon><TrendCharts /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.totalIn || 0 }}</div>
                <div class="stats_label">总入库量</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon out">
                <el-icon><TrendCharts /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.totalOut || 0 }}</div>
                <div class="stats_label">总出库量</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon stock">
                <el-icon><Box /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.currentStock || 0 }}</div>
                <div class="stats_label">当前库存</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stats_card">
            <div class="stats_content">
              <div class="stats_icon turnover">
                <el-icon><Refresh /></el-icon>
              </div>
              <div class="stats_info">
                <div class="stats_value">{{ reportData.summary.turnoverRate || 0 }}%</div>
                <div class="stats_label">周转率</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="chart_section" v-if="reportData.chartData">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-card>
            <template #header>
              <span>库存趋势图</span>
            </template>
            <div ref="trendChart" style="height: 300px;"></div>
          </el-card>
        </el-col>
        <el-col :span="12">
          <el-card>
            <template #header>
              <span>进出库对比</span>
            </template>
            <div ref="comparisonChart" style="height: 300px;"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
    <div class="table_section">
      <el-card>
        <template #header>
          <span>{{ getTableTitle() }}</span>
        </template>
         <el-table
           v-loading="tableLoading"
           :data="reportData.tableData"
           border
           height="400"
           style="width: 100%"
           :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
         >
          <el-table-column
            align="center"
            label="序号"
            type="index"
            width="60"
          />
           <el-table-column
             v-if="searchForm.reportType === 'daily'"
             label="日期"
             prop="createTime"
             width="100"
             align="center"
           />
           <el-table-column
             v-if="searchForm.reportType === 'monthly'"
             label="月份"
             prop="createTime"
             width="100"
             align="center"
           />
           <el-table-column
             label="入库时间"
             prop="createTime"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="入库批次"
             prop="inboundBatches"
             width="160"
             show-overflow-tooltip
           />
           <el-table-column
             label="产品大类"
             prop="productCategory"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="规格型号"
             prop="specificationModel"
             min-width="200"
             show-overflow-tooltip
           />
           <el-table-column
             label="单位"
             prop="unit"
             width="70"
             show-overflow-tooltip
           />
           <!-- <el-table-column
             label="期初库存"
             prop="beginStock"
             width="100"
             align="center"
           /> -->
           <el-table-column
             label="入库数量"
             prop="inboundNum"
             width="100"
             align="center"
           />
           <!-- <el-table-column
             label="出库数量"
             prop=""
             width="100"
             align="center"
           /> -->
           <el-table-column
             label="现在库存"
             prop="inboundNum0"
             width="100"
             align="center"
           />
           <el-table-column
             label="含税单价"
             prop="taxInclusiveUnitPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="含税总价"
             prop="taxInclusiveTotalPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="税率(%)"
             prop="taxRate"
             width="80"
             show-overflow-tooltip
           />
           <el-table-column
             label="不含税总价"
             prop="taxExclusiveTotalPrice"
             width="100"
             show-overflow-tooltip
           />
           <el-table-column
             label="入库人"
             prop="createBy"
             width="80"
             show-overflow-tooltip
           />
           <el-table-column
             v-if="searchForm.reportType === 'work'"
             label="操作人员"
             prop="operator"
             width="80"
             align="center"
           />
           <el-table-column
             v-if="searchForm.reportType === 'work'"
             label="操作时间"
             prop="operateTime"
             width="150"
             align="center"
           />
        </el-table>
      </el-card>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import {
  getStockDailyReport,
  getStockMonthlyReport,
  getWorkReport,
  getStockInOutReport,
  exportStockReport
} from '@/api/inventoryManagement/stockReport'
const { proxy } = getCurrentInstance()
// å“åº”式数据
const tableLoading = ref(false)
const trendChart = ref(null)
const comparisonChart = ref(null)
const searchForm = reactive({
  reportType: 'daily',
  singleDate: '',
  dateRange: [],
  monthRange: []
})
const reportData = ref({
  summary: null,
  chartData: null,
  tableData: []
})
// èŽ·å–è¡¨æ ¼æ ‡é¢˜
const getTableTitle = () => {
  const typeMap = {
    daily: '日报详细数据',
    monthly: '月报详细数据',
    work: '作业报表详细数据',
    inout: '进出存报表详细数据'
  }
  return typeMap[searchForm.reportType] || '报表详细数据'
}
// æŠ¥è¡¨ç±»åž‹æ”¹å˜
const handleReportTypeChange = () => {
  reportData.value = {
    summary: null,
    chartData: null,
    tableData: []
  }
}
// æŸ¥è¯¢æ•°æ®
const handleQuery = async () => {
  if (!validateSearchForm()) {
    return
  }
  tableLoading.value = true
  try {
    const params = getQueryParams()
    let response
    switch (searchForm.reportType) {
      case 'daily':
        response = await getStockDailyReport(params)
        break
      case 'monthly':
        response = await getStockMonthlyReport(params)
        break
      case 'work':
        response = await getWorkReport(params)
        break
      case 'inout':
        response = await getStockInOutReport(params)
        break
      default:
        throw new Error('未知的报表类型')
    }
    if (response.code === 200) {
      // generateMockData()
      reportData.value.tableData = response.data.tableData
      reportData.value.summary = response.data.summary
      reportData.value.chartData = response.data.chartData
      nextTick(() => {
        initCharts()
      })
    }
  } catch (error) {
    ElMessage.error('查询失败:' + error.message)
  } finally {
    tableLoading.value = false
  }
}
// // ç”Ÿæˆå‡æ•°æ®
// const generateMockData = () => {
//   // ç”Ÿæˆç»Ÿè®¡å¡ç‰‡å‡æ•°æ®
//   const summary = {
//     totalIn: 1000,
//     totalOut: 600,
//     currentStock: 400,
//     turnoverRate: 30
//   }
//   // ç”Ÿæˆå›¾è¡¨å‡æ•°æ®
//   const trendDates = ['2025-09-15', '2025-09-16', '2025-09-17', '2025-09-18', '2025-09-19']
//   const trendValues = [300, 350, 400, 380, 420]
//   const comparisonDates = ['2025-09-15', '2025-09-16', '2025-09-17']
//   const inValues = [100, 150, 200]
//   const outValues = [80, 120, 100]
//   const chartData = {
//     trendDates,
//     trendValues,
//     comparisonDates,
//     inValues,
//     outValues
//   }
//   reportData.value = {
//     summary,
//     chartData,
//     tableData: []
//   }
// }
// éªŒè¯æœç´¢è¡¨å•
const validateSearchForm = () => {
  if (searchForm.reportType === 'daily') {
    if (!searchForm.singleDate) {
      ElMessage.warning('请选择日期')
      return false
    }
  } else if (searchForm.reportType === 'work' || searchForm.reportType === 'inout') {
    if (!searchForm.dateRange || searchForm.dateRange.length !== 2) {
      ElMessage.warning('请选择日期范围')
      return false
    }
  } else if (searchForm.reportType === 'monthly') {
    if (!searchForm.monthRange || searchForm.monthRange.length !== 2) {
      ElMessage.warning('请选择月份范围')
      return false
    }
  }
  return true
}
// èŽ·å–æŸ¥è¯¢å‚æ•°
const getQueryParams = () => {
  const params = {
    reportType: searchForm.reportType,
    reportDate: "",
    startMonth: "",
    endMonth: "",
    startDate: "",
    endDate: ""
  }
  if (searchForm.reportType === 'daily') {
    params.reportDate = searchForm.singleDate
  } else if (searchForm.reportType === 'monthly') {
    params.startMonth = searchForm.monthRange[0]
    params.endMonth = searchForm.monthRange[1]
  } else {
    params.startDate = searchForm.dateRange[0]
    params.endDate = searchForm.dateRange[1]
  }
  return params
}
// é‡ç½®æœç´¢
const handleReset = () => {
  searchForm.reportType = 'daily'
  searchForm.singleDate = ''
  searchForm.dateRange = []
  searchForm.monthRange = []
  reportData.value = {
    summary: null,
    chartData: null,
    tableData: []
  }
}
// å¯¼å‡ºæŠ¥è¡¨
const handleExport = async () => {
  if (!validateSearchForm()) {
    return
  }
  try {
    const params = getQueryParams()
    // const response = await exportStockReport(params)
    proxy.download("/stockin/exportCopy", params, '库存报表.xlsx')
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    // const blob = new Blob([response], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
    // const url = window.URL.createObjectURL(blob)
    // const link = document.createElement('a')
    // link.href = url
    // link.download = `${getTableTitle()}_${new Date().getTime()}.xlsx`
    // document.body.appendChild(link)
    // link.click()
    // document.body.removeChild(link)
    // window.URL.revokeObjectURL(url)
    // ElMessage.success('导出成功')
  } catch (error) {
    ElMessage.error('导出失败:' + error.message)
  }
}
// åˆå§‹åŒ–图表
const initCharts = () => {
  if (!reportData.value.chartData) return
  initTrendChart()
  initComparisonChart()
}
// åˆå§‹åŒ–趋势图
const initTrendChart = () => {
  if (!trendChart.value) return
  const chart = echarts.init(trendChart.value)
  const option = {
    title: {
      text: '库存变化趋势',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      data: ['库存量'],
      top: 30
    },
    xAxis: {
      type: 'category',
      data: reportData.value.chartData.trendDates || []
    },
    yAxis: {
      type: 'value'
    },
    series: [{
      name: '库存量',
      type: 'line',
      data: reportData.value.chartData.trendValues || [],
      smooth: true,
      itemStyle: {
        color: '#409EFF'
      }
    }]
  }
  chart.setOption(option)
}
// åˆå§‹åŒ–对比图
const initComparisonChart = () => {
  if (!comparisonChart.value) return
  const chart = echarts.init(comparisonChart.value)
  const option = {
    title: {
      text: '进出库对比',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      data: ['入库', '出库'],
      top: 30
    },
    xAxis: {
      type: 'category',
      data: reportData.value.chartData.comparisonDates || []
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        name: '入库',
        type: 'bar',
        data: reportData.value.chartData.inValues || [],
        itemStyle: {
          color: '#67C23A'
        }
      },
      {
        name: '出库',
        type: 'bar',
        data: reportData.value.chartData.outValues || [],
        itemStyle: {
          color: '#F56C6C'
        }
      }
    ]
  }
  chart.setOption(option)
}
// ç»„件挂载时设置默认时间
onMounted(() => {
  const today = new Date()
  searchForm.singleDate = today.toISOString().split('T')[0]
  const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
  searchForm.dateRange = [
    yesterday.toISOString().split('T')[0],
    today.toISOString().split('T')[0]
  ]
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.search_form {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 20px;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.search_left {
  display: flex;
  align-items: center;
}
.search_title {
  font-weight: 500;
  color: #333;
  margin-right: 8px;
}
.ml10 {
  margin-left: 10px;
}
.stats_cards {
  margin-bottom: 20px;
}
.stats_card {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stats_content {
  display: flex;
  align-items: center;
  padding: 10px 0;
}
.stats_icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 15px;
  font-size: 24px;
  color: #fff;
}
.stats_icon.in {
  background: linear-gradient(135deg, #67C23A, #85CE61);
}
.stats_icon.out {
  background: linear-gradient(135deg, #F56C6C, #F78989);
}
.stats_icon.stock {
  background: linear-gradient(135deg, #409EFF, #66B1FF);
}
.stats_icon.turnover {
  background: linear-gradient(135deg, #E6A23C, #EEBE77);
}
.stats_info {
  flex: 1;
}
.stats_value {
  font-size: 24px;
  font-weight: bold;
  color: #333;
  line-height: 1;
  margin-bottom: 5px;
}
.stats_label {
  font-size: 14px;
  color: #666;
}
.chart_section {
  margin-bottom: 20px;
}
.table_section {
  margin-bottom: 20px;
}
:deep(.el-card__header) {
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  font-weight: 500;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
</style>
src/views/inventoryManagement/stockWarning/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,943 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="储气罐名称:">
          <el-input v-model="searchForm.tankName" placeholder="请输入储气罐名称" clearable style="width: 200px" />
        </el-form-item>
        <el-form-item label="储气罐类型:">
          <el-select v-model="searchForm.tankType" placeholder="请选择储气罐类型" clearable style="width: 200px">
            <el-option label="液化气储罐" value="液化气储罐" />
            <el-option label="压缩气储罐" value="压缩气储罐" />
            <el-option label="天然气储罐" value="天然气储罐" />
            <el-option label="氧气储罐" value="氧气储罐" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警类型:">
          <el-select v-model="searchForm.warningType" placeholder="请选择预警类型" clearable style="width: 200px">
            <el-option label="气体不足" value="气体不足" />
            <el-option label="压力异常" value="压力异常" />
            <el-option label="温度异常" value="温度异常" />
            <el-option label="泄漏预警" value="泄漏预警" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警级别:">
          <el-select v-model="searchForm.warningLevel" placeholder="请选择预警级别" clearable style="width: 200px">
            <el-option label="紧急" value="紧急" />
            <el-option label="重要" value="重要" />
            <el-option label="一般" value="一般" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table_list">
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="table-operations">
        <el-button type="primary" @click="handleAdd">新增预警规则</el-button>
        <!-- <el-button type="success" @click="handleBatchProcess">批量处理@</el-button> -->
        <el-button @click="handleExport">导出</el-button>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        style="width: 100%"
        height="calc(100vh - 280px)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <!-- åŸºç¡€ä¿¡æ¯å­—段 -->
        <el-table-column label="储气罐编码" prop="tankCode" width="120" show-overflow-tooltip />
        <el-table-column label="储气罐名称" prop="tankName" width="200" show-overflow-tooltip />
        <el-table-column label="储气罐类型" prop="tankType" width="120" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="150" show-overflow-tooltip />
        <el-table-column label="容积(m³)" prop="volume" width="100" show-overflow-tooltip />
        <!-- åº“存相关字段 -->
        <el-table-column label="当前气体量" prop="currentGasLevel" width="120" show-overflow-tooltip>
          <template #default="scope">
            <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span>
          </template>
        </el-table-column>
        <el-table-column label="安全气体量" prop="safetyGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最低气体量" prop="minGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最高气体量" prop="maxGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="当前压力(MPa)" prop="currentPressure" width="140" show-overflow-tooltip />
        <!-- é¢„警规则字段 -->
        <el-table-column label="预警类型" prop="warningType" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningTypeTag(scope.row.warningType)">
              {{ scope.row.warningType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警级别" prop="warningLevel" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningLevelTag(scope.row.warningLevel)">
              {{ scope.row.warningLevel }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警阈值" prop="warningThreshold" width="100" show-overflow-tooltip />
        <el-table-column label="是否启用" prop="isEnabled" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" />
          </template>
        </el-table-column>
        <!-- æ—¶é—´ç›¸å…³å­—段 -->
        <el-table-column label="预警时间" prop="warningTime" width="150" show-overflow-tooltip />
        <el-table-column label="预警持续天数" prop="warningDuration" width="120" show-overflow-tooltip />
        <el-table-column label="最后更新时间" prop="lastUpdateTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计充装时间" prop="expectedRefillTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计缺气时间" prop="expectedShortageTime" width="150" show-overflow-tooltip>
          <template #default="scope">
            <div v-if="scope.row.expectedShortageTime">
              <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired">
                <el-tag type="danger">已缺气</el-tag>
              </div>
              <div v-else class="countdown-timer">
                <span :class="getCountdownClass(scope.row.expectedShortageTime)">
                  {{ getCountdown(scope.row.expectedShortageTime).text }}
                </span>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <!-- æ“ä½œåˆ— -->
        <el-table-column fixed="right" label="操作" width="200" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<!--            <el-button link type="success" size="small" @click="handleProcess(scope.row)">处理@</el-button>-->
            <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <!-- æ–°å¢ž/编辑预警规则弹窗 -->
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增预警规则' : '编辑预警规则'"
      width="50%"
      @close="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px">
        <el-row :gutter="20">
          <!-- åŸºç¡€ä¿¡æ¯ -->
          <el-col :span="12">
            <el-form-item label="储气罐编码:" prop="tankCode">
              <el-input v-model="form.tankCode" placeholder="请输入储气罐编码" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="储气罐名称:" prop="tankName">
              <el-input v-model="form.tankName" placeholder="请输入储气罐名称" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="储气罐类型:" prop="tankType">
              <el-select v-model="form.tankType" placeholder="请选择储气罐类型" style="width: 100%">
                <el-option label="液化气储罐" value="液化气储罐" />
                <el-option label="压缩气储罐" value="压缩气储罐" />
                <el-option label="天然气储罐" value="天然气储罐" />
                <el-option label="氧气储罐" value="氧气储罐" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="specificationModel">
              <el-input v-model="form.specificationModel" placeholder="请输入规格型号" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="容积(m³):" prop="volume">
              <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前气体量(%):" prop="currentGasLevel">
              <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- åº“存相关 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="安全气体量(%):" prop="safetyGasLevel">
              <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最低气体量(%):" prop="minGasLevel">
              <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="最高气体量(%):" prop="maxGasLevel">
              <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前压力(MPa):" prop="currentPressure">
              <el-input-number v-model="form.currentPressure" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- é¢„警规则 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警类型:" prop="warningType">
              <el-select v-model="form.warningType" placeholder="请选择预警类型" style="width: 100%">
                <el-option label="气体不足" value="气体不足" />
                <el-option label="压力异常" value="压力异常" />
                <el-option label="温度异常" value="温度异常" />
                <el-option label="泄漏预警" value="泄漏预警" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警级别:" prop="warningLevel">
              <el-select v-model="form.warningLevel" placeholder="请选择预警级别" style="width: 100%">
                <el-option label="紧急" value="紧急" />
                <el-option label="重要" value="重要" />
                <el-option label="一般" value="一般" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警阈值:" prop="warningThreshold">
              <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否启用:" prop="isEnabled">
              <el-switch v-model="form.isEnabled" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- æ—¶é—´ç›¸å…³ -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警时间:" prop="warningTime">
              <el-date-picker
                v-model="form.warningTime"
                type="datetime"
                placeholder="请选择预警时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预计充装时间:" prop="expectedRefillTime">
              <el-date-picker
                v-model="form.expectedRefillTime"
                type="datetime"
                placeholder="请选择预计充装时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预计缺气时间:" prop="expectedShortageTime">
              <el-date-picker
                v-model="form.expectedShortageTime"
                type="datetime"
                placeholder="请选择预计缺气时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警规则描述:" prop="warningRule">
              <el-input
                v-model="form.warningRule"
                type="textarea"
                :rows="3"
                placeholder="请输入预警规则描述"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDialog">取消</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { WarningFilled } from '@element-plus/icons-vue'
import pagination from '@/components/PIMTable/Pagination.vue'
// æ³¨é‡ŠæŽ‰API导入,使用假数据
import {
  getStockWarningPage,
  addStockWarning,
  updateStockWarning,
  deleteStockWarning,
  batchProcessStockWarning,
  exportStockWarning,
  toggleStockWarningStatus
} from '@/api/inventoryManagement/stockWarning.js'
const { proxy } = getCurrentInstance()
// å“åº”式数据
const tableData = ref([])
const tableLoading = ref(false)
const selectedRows = ref([])
const dialogFormVisible = ref(false)
const operationType = ref('add')
const total = ref(0)
// ç¼ºæ°”预警相关
const shortageWarningVisible = ref(false)
const currentWarningTank = ref({})
const countdownTimer = ref(null)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10,
  total: 0
})
// æœç´¢è¡¨å•
const searchForm = reactive({
  tankName: '',
  tankType: '',
  warningType: '',
  warningLevel: ''
})
// è¡¨å•数据
const form = reactive({
  id: null,
  tankCode: '',
  tankName: '',
  tankType: '',
  specificationModel: '',
  volume: 0,
  currentGasLevel: 0,
  safetyGasLevel: 0,
  minGasLevel: 0,
  maxGasLevel: 0,
  currentPressure: 0,
  warningType: '',
  warningLevel: '',
  warningThreshold: 0,
  isEnabled: true,
  warningTime: '',
  warningDuration: 0,
  lastUpdateTime: '',
  expectedRefillTime: '',
  expectedShortageTime: '',
  warningRule: ''
})
// è¡¨å•验证规则
const rules = {
  tankCode: [{ required: true, message: '请输入储气罐编码', trigger: 'blur' }],
  tankName: [{ required: true, message: '请输入储气罐名称', trigger: 'blur' }],
  tankType: [{ required: true, message: '请选择储气罐类型', trigger: 'change' }],
  warningType: [{ required: true, message: '请选择预警类型', trigger: 'change' }],
  warningLevel: [{ required: true, message: '请选择预警级别', trigger: 'change' }],
  warningThreshold: [{ required: true, message: '请输入预警阈值', trigger: 'blur' }]
}
// èŽ·å–å€’è®¡æ—¶ä¿¡æ¯
const getCountdown = (expectedTime) => {
  if (!expectedTime) return { text: '-', isExpired: false }
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return { text: '已缺气', isExpired: true }
  }
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
  const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
  if (days > 0) {
    return { text: `${days}天${hours}小时`, isExpired: false }
  } else if (hours > 0) {
    return { text: `${hours}小时${minutes}分钟`, isExpired: false }
  } else {
    return { text: `${minutes}分钟`, isExpired: false }
  }
}
// èŽ·å–å€’è®¡æ—¶æ ·å¼ç±»
const getCountdownClass = (expectedTime) => {
  if (!expectedTime) return ''
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return 'countdown-expired'
  } else if (diff <= 24 * 60 * 60 * 1000) { // 24小时内
    return 'countdown-urgent'
  } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天内
    return 'countdown-warning'
  } else {
    return 'countdown-normal'
  }
}
// æ£€æŸ¥ç¼ºæ°”预警
const checkShortageWarnings = () => {
  tableData.value.forEach(tank => {
    if (tank.expectedShortageTime) {
      const countdown = getCountdown(tank.expectedShortageTime)
      if (countdown.isExpired && !tank.warningShown) {
        // æ ‡è®°å·²æ˜¾ç¤ºé¢„警,避免重复弹框
        tank.warningShown = true
        showShortageWarning(tank)
      }
    }
  })
}
// æ˜¾ç¤ºç¼ºæ°”预警弹框
const showShortageWarning = (tank) => {
  currentWarningTank.value = tank
  shortageWarningVisible.value = true
  // æ’­æ”¾æç¤ºéŸ³ï¼ˆå¯é€‰ï¼‰
  // const audio = new Audio('/path/to/warning-sound.mp3')
  // audio.play()
}
// å¤„理缺气预警
const handleShortageWarning = () => {
  ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// å¤„理缺气预警
const closeShortageWarning = () => {
  // ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = async () => {
  tableLoading.value = true
  getStockWarningPage(page, searchForm)
  .then(res => {
    tableData.value = res.data.records
    page.value.total = res.data.total;
    tableLoading.value = false;
        // æ£€æŸ¥ç¼ºæ°”预警
    checkShortageWarnings()
  }).catch(err => {
    tableLoading.value = false;
  })
}
// æœç´¢
const handleQuery = () => {
  page.current = 1
  getList()
}
// é‡ç½®æœç´¢
const resetQuery = () => {
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = ''
  })
  handleQuery()
}
// åˆ†é¡µå˜åŒ–
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
// è¡¨æ ¼é€‰æ‹©å˜åŒ–
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  // resetForm()
  dialogFormVisible.value = true
}
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(form, row)
  dialogFormVisible.value = true
}
// å¤„理预警
const handleProcess = async (row) => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 300))
    ElMessage.success(`正在处理预警:${row.tankName}`)
    // getList()
  } catch (error) {
    ElMessage.error('处理预警失败')
  }
}
// åˆ é™¤
const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm(`确定要删除预警规则:${row.tankName}吗?`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    let ids = [];
    ids.push(row.id);
    deleteStockWarning(ids).then(res => {
      if(res.code == 200){
        ElMessage.success("删除成功");
        ids.value = [];
        getList();
      }
    }).catch(err => {
      ElMessage.error(err.msg);
    })
    // // æ¨¡æ‹ŸAPI调用延迟
    // await new Promise(resolve => setTimeout(resolve, 300))
    // ElMessage.success('删除成功')
    // getList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
// æ‰¹é‡å¤„理
const handleBatchProcess = async () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning('请选择要处理的预警')
    return
  }
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    ElMessage.success(`批量处理了 ${selectedRows.value.length} æ¡é¢„è­¦`)
    getList()
  } catch (error) {
    ElMessage.error('批量处理失败')
  }
}
// å¯¼å‡º
const handleExport = async () => {
  //   if (selectedRows.value.length === 0) {
  //     exportStockWarning().then(res => {
  //       // // åˆ›å»ºä¸‹è½½é“¾æŽ¥
  //       // const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' })
  //       // const url = window.URL.createObjectURL(blob)
  //       // const link = document.createElement('a')
  //       // link.href = url
  //       // link.download = `储气罐预警数据_${new Date().getTime()}.csv`
  //       // link.click()
  //       // window.URL.revokeObjectURL(url)
  //     }).catch(err => {
  //       ElMessage.error(err.msg);
  //     })
  // }else{
  //     let ids = [];
  //     selectedRows.value.forEach(item => {
  //       ids.push(item.id);
  //     })
  //     exportStockWarning(ids).then(res => {
  //       // // åˆ›å»ºä¸‹è½½é“¾æŽ¥
  //       // const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' })
  //       // const url = window.URL.createObjectURL(blob)
  //       // const link = document.createElement('a')
  //       // link.href = url
  //       // link.download = `储气罐预警数据_${new Date().getTime()}.csv`
  //       // link.click()
  //       // window.URL.revokeObjectURL(url)
  //     }).catch(err => {
  //       ElMessage.error(err.msg);
  //     })
  // }
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
      proxy.download("/gasTankWarning/export", {ids: selectedRows.value.map(item => item.id)}, "储气罐预警.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
}
// // å¯ç”¨çŠ¶æ€å˜åŒ–
const handleEnableChange = async (row) => {
  try {
    updateStockWarning(row).then(res => {
      if(res.code == 200){
        ElMessage.success(`${row.tankName} çš„启用状态已更新`);
        getList();
      }
    }).catch(err => {
      ElMessage.error(err.msg);
    })
  } catch (error) {
    ElMessage.error('状态更新失败')
    // æ¢å¤åŽŸçŠ¶æ€
    row.isEnabled = !row.isEnabled
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    // æ¨¡æ‹ŸAPI调用延迟
    // await new Promise(resolve => setTimeout(resolve, 500))
    if (operationType.value === 'add') {
      addStockWarning(form).then(res => {
        if(res.code == 200){
          ElMessage.success("添加成功");
          dialogFormVisible.value = false
          getList()
          resetForm()
        }
      }).catch(err => {
        ElMessage.error(err.msg);
      })
      // ElMessage.success('新增成功')
    } else {
      updateStockWarning(form).then(res => {
        if(res.code == 200){
          ElMessage.success("更新成功");
          dialogFormVisible.value = false
          getList()
          resetForm()
        }
      }).catch(err => {
        ElMessage.error(err.msg);
      })
      // ElMessage.success('编辑成功')
    }
    // closeDialog()
    // getList()
  } catch (error) {
    if (!error.errors) {
      ElMessage.error(operationType.value === 'add' ? '新增失败' : '编辑失败')
    }
  }
}
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogFormVisible.value = false
  // resetForm()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  Object.keys(form).forEach(key => {
    if (key === 'isEnabled') {
      form[key] = true
    } else if (typeof form[key] === 'number') {
      form[key] = 0
    } else {
      form[key] = ''
    }
  })
  proxy.$refs.formRef?.resetFields()
}
// èŽ·å–æ°”ä½“é‡æ ·å¼ç±»
const getGasLevelClass = (row) => {
  if (row.currentGasLevel < row.minGasLevel) {
    return 'text-danger'
  } else if (row.currentGasLevel > row.maxGasLevel) {
    return 'text-warning'
  }
  return 'text-success'
}
// èŽ·å–é¢„è­¦ç±»åž‹æ ‡ç­¾æ ·å¼
const getWarningTypeTag = (type) => {
  const typeMap = {
    '气体不足': 'danger',
    '压力异常': 'warning',
    '温度异常': 'info',
    '泄漏预警': 'danger'
  }
  return typeMap[type] || 'info'
}
// èŽ·å–é¢„è­¦çº§åˆ«æ ‡ç­¾æ ·å¼
const getWarningLevelTag = (level) => {
  const levelMap = {
    '紧急': 'danger',
    '重要': 'warning',
    '一般': 'info'
  }
  return levelMap[level] || 'info'
}
// å¯åŠ¨å€’è®¡æ—¶å®šæ—¶å™¨
const startCountdownTimer = () => {
  countdownTimer.value = setInterval(() => {
    checkShortageWarnings()
  }, 60000) // æ¯åˆ†é’Ÿæ£€æŸ¥ä¸€æ¬¡
}
// åœæ­¢å€’计时定时器
const stopCountdownTimer = () => {
  if (countdownTimer.value) {
    clearInterval(countdownTimer.value)
    countdownTimer.value = null
  }
}
// é¡µé¢åŠ è½½
onMounted(() => {
  getList()
  startCountdownTimer()
})
// é¡µé¢å¸è½½
onUnmounted(() => {
  stopCountdownTimer()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .table-operations {
    text-align: right;
    margin-bottom: 20px;
    .el-button {
      margin-right: 10px;
    }
  }
  .table_list {
    background: #fff;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .text-danger {
    color: #f56c6c;
    font-weight: bold;
  }
  .text-warning {
    color: #e6a23c;
    font-weight: bold;
  }
  .text-success {
    color: #67c23a;
    font-weight: bold;
  }
  .dialog-footer {
    text-align: right;
  }
  // å€’计时样式
  .countdown-timer {
    font-weight: bold;
  }
  .countdown-normal {
    color: #67c23a;
  }
  .countdown-warning {
    color: #e6a23c;
  }
  .countdown-urgent {
    color: #f56c6c;
    animation: blink 1s infinite;
  }
  .countdown-expired {
    color: #f56c6c;
    font-weight: bold;
  }
  @keyframes blink {
    0%, 50% { opacity: 1; }
    51%, 100% { opacity: 0.5; }
  }
  // ç¼ºæ°”预警弹框样式
  .shortage-warning-content {
    text-align: center;
    padding: 20px 0;
    .warning-icon {
      margin-bottom: 20px;
    }
    .warning-message {
      h3 {
        color: #f56c6c;
        margin-bottom: 10px;
      }
      p {
        margin-bottom: 10px;
        color: #606266;
      }
      .warning-details {
        background: #f5f7fa;
        padding: 15px;
        border-radius: 4px;
        text-align: left;
        font-size: 14px;
        line-height: 1.6;
      }
    }
  }
}
</style>
src/views/procurementManagement/paymentEntry/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,575 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :inline="true" :model="searchForm" style="width: 100%">
        <el-row justify="space-between">
          <el-col :span="20">
            <el-form-item label="供应商名称/合同号">
              <el-input
                v-model="searchForm.supplierNameOrContractNo"
                style="width: 240px"
                placeholder="输入供应商名称/合同号搜索"
                clearable
                prefix-icon="Search"
                @change="handleQuery"
              />
            </el-form-item>
            <el-form-item>
              <el-checkbox
                v-model="searchForm.status"
                label="不显示待付款为0"
                @change="handleQuery"
              />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item style="float: right; margin-right: unset">
              <el-button type="primary" @click="openForm('add')">
                æ–°å¢žä»˜æ¬¾
              </el-button>
<!--              <el-button type="danger" plain @click="handleDelete">-->
<!--                åˆ é™¤-->
<!--              </el-button>-->
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :expandRowKeys="expandedRowKeys"
        :isSelection="true"
        :isShowSummary="isShowSummarySon"
        :summaryMethod="summarizeMainTable1"
        @selection-change="handleSelectionChange"
        @expand-change="expandChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      >
                <template #expand="{ row }">
                    <el-table
                        :data="expandData"
                        border
                        show-summary
                        v-loading="childrenLoading"
                        :summary-method="summarizeMainTable2"
                        stripe
                    >
                        <el-table-column
                            align="center"
                            label="序号"
                            type="index"
                            width="60"
                        />
                        <el-table-column label="付款日期" prop="paymentDate" />
                        <el-table-column label="付款金额" prop="currentPaymentAmount">
                            <template #default="scope">
                                <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                                 v-model="scope.row.currentPaymentAmount"
                                                                 :disabled="!scope.row.editType"
                                                                 :precision="2"
                                                                 placeholder="请输入"
                                                                 clearable
                                />
                            </template>
                        </el-table-column>
                        <el-table-column label="付款方式" prop="paymentMethod">
                            <template #default="scope">
                                <el-input
                                    :disabled="!scope.row.editType"
                                    v-model="scope.row.paymentMethod"
                                    placeholder="请输入"
                                    clearable
                                />
                            </template>
                        </el-table-column>
                        <el-table-column label="登记人" prop="registrant" />
                        <el-table-column label="登记日期" prop="registrationtDate" />
                        <el-table-column label="操作" width="150">
                            <template #default="scope">
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="changeEditType(scope.row)"
                                    v-if="!scope.row.editType"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >编辑</el-button
                                >
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="saveReceiptPayment(scope.row)"
                                    v-if="scope.row.editType"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >保存</el-button
                                >
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="handleDelete(scope.row)"
                                    :disabled="scope.row.registrant !== userStore.nickName"
                                >删除</el-button
                                >
                            </template>
                        </el-table-column>
                    </el-table>
                </template>
            </PIMTable>
    </div>
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增付款登记' : '编辑付款登记'"
      width="60%"
      @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="purchaseContractNumber">
              <el-input
                v-model="form.purchaseContractNumber"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:" prop="salesContractNo">
              <el-input
                v-model="form.salesContractNo"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                v-model="form.supplierName"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发票号:" prop="invoiceNumber">
              <el-input
                v-model="form.invoiceNumber"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票金额(元):" prop="invoiceAmount">
              <el-input
                v-model="form.invoiceAmount"
                placeholder="自动填充"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="本次付款金额:" prop="currentPaymentAmount">
              <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                             :precision="2"
                v-model="form.currentPaymentAmount"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式:" prop="paymentMethod">
              <el-input
                v-model="form.paymentMethod"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="付款日期:" prop="paymentDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.paymentDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="登记人:" prop="registrant">
                            <el-input
                                v-model="form.registrant"
                                placeholder="请输入"
                                clearable
                                disabled
                            />
                        </el-form-item>
                    </el-col>
          <el-col :span="12">
            <el-form-item label="登记日期:" prop="registrationtDate">
              <el-input
                v-model="form.registrationtDate"
                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>
  </div>
</template>
<script setup>
import { ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user.js";
import {
  byPurchaseId,
  paymentRegistrationAdd,
  paymentRegistrationDel,
  paymentRegistrationEdit,
  getTicketNo,
} from "@/api/procurementManagement/paymentEntry.js";
import {
    delPaymentRegistration,
    invoiceListPage,
    registrationListPageGetById,
    updatePaymentRegistration
} from "@/api/procurementManagement/procurementInvoiceLedger.js";
import useFormData from "@/hooks/useFormData";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
    {
        type: "expand",
        dataType: "slot",
        slot: "expand",
    },
  {
    label: "采购合同号",
    prop: "purchaseContractNumber",
  },
  {
    label: "销售合同号",
    prop: "salesContractNo",
  },
  {
    label: "供应商名称",
    prop: "supplierName",
    width:240
  },
  {
    label: "发票号",
    prop: "invoiceNumber",
    width:200
  },
  {
    label: "发票金额(元)",
    prop: "invoiceAmount",
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "已付款金额(元)",
    prop: "paymentAmountTotal",
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "待付款金额(元)",
    prop: "unPaymentAmountTotal",
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "录入人",
    prop: "issUer",
  },
]);
const tableData = ref([]);
const expandData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
const userStore = useUserStore();
const page = reactive({
  current: 1,
  size: 100,
    total: 0,
});
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierNameOrContractNo: "",
    status: false,
  },
  form: {
    purchaseContractNumber: "",
    purchaseLedgerId: "",
    salesContractNo: "",
    supplierName: "",
    invoiceNumber: "",
    invoiceAmount: "",
    taxRate: "",
    currentPaymentAmount: "",
    paymentMethod: "",
    registrant: "",
    registrantId: "",
    paymentDate: "",
    registrationtDate: "",
  },
  rules: {
    purchaseLedgerId: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    currentPaymentAmount: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    paymentMethod: [{ required: true, message: "请选择", trigger: "change" }],
    invoiceNumber: [
      { required: true, message: "请选择采购合同号", trigger: "change" },
    ],
  },
});
const { form, rules } = toRefs(data);
const { form: searchForm, resetForm } = useFormData(data.searchForm);
const isShowSummarySon = ref(true);
const expandedRowKeys = ref([]);
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceAmount", "paymentAmountTotal", "unPaymentAmountTotal"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable2 = (param) => {
    return proxy.summarizeTable(param, ["currentPaymentAmount"], {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    });
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  invoiceListPage({ ...searchForm, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.records;
        page.total = res.total;
        if (expandedRowKeys.value.length > 0) {
            const arr = []
            const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
            if (index > -1) {
                arr.push(tableData.value[index]);
                expandChange(tableData.value[index], arr)
            }
        }
  });
};
// å±•开行
const expandChange = (row, expandedRows) => {
    if (expandedRows.length > 0) {
        nextTick(() => {
            expandedRowKeys.value = [];
            try {
                childrenLoading.value = true;
                registrationListPageGetById({ id: row.id }).then((res) => {
                    childrenLoading.value = false;
                    const index = tableData.value.findIndex((item) => item.id === row.id);
                    if (index > -1) {
                        expandData.value = res;
                    }
                    expandedRowKeys.value.push(row.id);
                });
            } catch (error) {
                childrenLoading.value = false;
                console.log(error);
            }
        })
    } else {
        expandedRowKeys.value = [];
    }
};
// ç¼–辑修改状态
const changeEditType = (row) => {
    row.editType = !row.editType;
};
// ä¿å­˜å›žæ¬¾è®°å½•
const saveReceiptPayment = (row) => {
    let updateData = {
        id: row.id,
        currentPaymentAmount: row.currentPaymentAmount,
        paymentMethod: row.paymentMethod,
    };
    updatePaymentRegistration(updateData).then((res) => {
        row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  if (selectedRows.value.length !== 1) {
    proxy.$message.error("请选择一条发票数据");
    return;
  }
    if (selectedRows.value[0].unPaymentAmountTotal == 0) {
        proxy.$message.warning("无需再付款");
        return;
    }
  operationType.value = type;
  form.value = {};
  form.value = { ...selectedRows.value[0] };
  form.value.ticketRegistrationId = selectedRows.value[0].id;
  form.value.id = null;
  // æŸ¥è¯¢é‡‡è´­åˆåŒå·
  form.value.registrationtDate = getCurrentDate();
  form.value.paymentDate = getCurrentDate();
  form.value.registrant = userStore.name;
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitEdit();
      } else {
        submitAdd();
      }
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  paymentRegistrationAdd(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  paymentRegistrationEdit(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// åˆ é™¤
const handleDelete = (row) => {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
            delPaymentRegistration(row.id)
        .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}`;
}
getList();
</script>
<style scoped lang="scss">
.table_list {
  margin-top: unset;
}
::v-deep(.el-checkbox__label) {
  font-weight: bold;
}
</style>
src/views/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/procurementManagement/procurementLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1131 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <el-form :model="searchForm" :inline="true">
          <el-form-item label="供应商名称:">
            <el-input v-model="searchForm.supplierName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="采购合同号:">
            <el-input
                v-model="searchForm.purchaseContractNumber"
                style="width: 240px"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                :prefix-icon="Search"
            />
          </el-form-item>
          <el-form-item label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="项目名称:">
            <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
          </el-form-item>
          <el-form-item label="录入日期:">
            <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                            placeholder="请选择" clearable @change="changeDaterange" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        :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" type="selection" 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>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
          label="采购合同号"
          prop="purchaseContractNumber"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          label="供应商名称"
          width="240"
          prop="supplierName"
          show-overflow-tooltip
        />
        <el-table-column
          label="项目名称"
          prop="projectName"
          width="420"
          show-overflow-tooltip
        />
        <el-table-column
          label="付款方式"
          width="100"
          prop="paymentMethod"
          show-overflow-tooltip
        />
        <el-table-column
          label="合同金额(元)"
          prop="contractAmount"
           width="200"
          show-overflow-tooltip
          :formatter="formattedNumber"
        />
        <el-table-column
          label="录入人"
          prop="recorderName"
           width="100"
          show-overflow-tooltip
        />
        <el-table-column
          label="录入日期"
          prop="entryDate"
           width="100"
          show-overflow-tooltip
        />
        <el-table-column
          fixed="right"
          label="操作"
          min-width="100"
          align="center"
        >
          <template #default="scope">
            <el-button
              link
              type="primary"
              size="small"
              @click="openForm('edit', scope.row)"
                            :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName"
              >编辑</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <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="purchaseContractNumber">
              <el-input
                v-model="form.purchaseContractNumber"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:" prop="salesContractNo">
              <el-input
                v-model="form.salesContractNo"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierId">
              <el-select
                v-model="form.supplierId"
                placeholder="请选择"
                clearable
              >
                <el-option
                  v-for="item in supplierList"
                  :key="item.id"
                  :label="item.supplierName"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input
                v-model="form.projectName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="录入人:" prop="recorderId">
              <el-select
                v-model="form.recorderId"
                placeholder="请选择"
                clearable
                disabled
              >
                <el-option
                  v-for="item in userList"
                  :key="item.userId"
                  :label="item.nickName"
                  :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker
                disabled
                style="width: 100%"
                v-model="form.entryDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input
                v-model="form.paymentMethod"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
            <el-button type="primary" @click="openProductForm('add')"
              >添加</el-button
            >
            <el-button plain type="danger" @click="deleteProduct"
              >删除</el-button
            >
          </el-form-item>
        </el-row>
        <el-table
          :data="productData"
          border
          @selection-change="productSelected"
          show-summary
          stripe
          :summary-method="summarizeProTable"
        >
          <el-table-column align="center" type="selection" width="55" />
          <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" width="70" />
          <el-table-column label="数量" prop="quantity" width="70" />
          <el-table-column label="税率(%)" prop="taxRate" width="80" />
          <el-table-column
            label="含税单价(元)"
            prop="taxInclusiveUnitPrice"
            :formatter="formattedNumber"
            width="150"
          />
          <el-table-column
            label="含税总价(元)"
            prop="taxInclusiveTotalPrice"
            :formatter="formattedNumber"
            width="150"
          />
          <el-table-column
            label="不含税总价(元)"
            prop="taxExclusiveTotalPrice"
            :formatter="formattedNumber"
            width="150"
          />
          <el-table-column
            fixed="right"
            label="操作"
            min-width="60"
            align="center"
          >
            <template #default="scope">
              <el-button
                link
                type="primary"
                size="small"
                @click="openProductForm('edit', scope.row, scope.$index)"
                >编辑</el-button
              >
            </template>
          </el-table-column>
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:" prop="remark">
              <el-input
                v-model="form.remark"
                placeholder="请输入"
                clearable
                type="textarea"
                :rows="2"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
              <el-upload
                v-model:file-list="fileList"
                :action="upload.url"
                multiple
                ref="fileUpload"
                auto-upload
                :headers="upload.headers"
                :before-upload="handleBeforeUpload"
                :on-error="handleUploadError"
                :on-success="handleUploadSuccess"
                :on-remove="handleRemove"
              >
                <el-button type="primary">上传</el-button>
                <template #tip>
                  <div class="el-upload__tip">
                    æ–‡ä»¶æ ¼å¼æ”¯æŒ
                    doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
                  </div>
                </template>
              </el-upload>
            </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
      v-model="productFormVisible"
      :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
      width="40%"
      @close="closeProductDia"
    >
      <el-form
        :model="productForm"
        label-width="140px"
        label-position="top"
        :rules="productRules"
        ref="productFormRef"
      >
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品大类:" prop="productId">
              <el-tree-select
                v-model="productForm.productId"
                placeholder="请选择"
                clearable
                check-strictly
                @change="getModels"
                :data="productOptions"
                :render-after-expand="false"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select
                v-model="productForm.productModelId"
                placeholder="请选择"
                clearable
                @change="getProductModel"
              >
                <el-option
                  v-for="item in modelOptions"
                  :key="item.id"
                  :label="item.model"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input
                v-model="productForm.unit"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="税率(%):" prop="taxRate">
                            <el-select
                                v-model="productForm.taxRate"
                                placeholder="请选择"
                                clearable
                                @change="mathNum"
                            >
                                <el-option label="1" value="1" />
                                <el-option label="6" value="6" />
                                <el-option label="13" value="13" />
                            </el-select>
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input-number
                v-model="productForm.taxInclusiveUnitPrice"
                :precision="2"
                :step="0.1"
                clearable
                style="width: 100%"
                @change="mathNum"
              />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="数量:" prop="quantity">
                            <el-input-number
                                :step="0.1"
                                clearable
                                :precision="2"
                                style="width: 100%"
                                v-model="productForm.quantity"
                                placeholder="请输入"
                                @change="mathNum"
                            />
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
              <el-input-number
                v-model="productForm.taxInclusiveTotalPrice"
                :precision="2"
                :step="0.1"
                clearable
                style="width: 100%"
                @change="reverseMathNum('taxInclusiveTotalPrice')"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item
              label="不含税总价(元):"
              prop="taxExclusiveTotalPrice"
            >
              <el-input
                v-model="productForm.taxExclusiveTotalPrice"
                @change="reverseMathNum('taxExclusiveTotalPrice')"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票类型:" prop="invoiceType">
              <el-select
                v-model="productForm.invoiceType"
                placeholder="请选择"
                clearable
              >
                <el-option label="增普票" value="增普票" />
                <el-option label="增专票" value="增专票" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  getSalesLedgerWithProducts,
  addOrUpdateSalesLedgerProduct,
  delProduct,
  delLedgerFile,
} from "@/api/salesManagement/salesLedger.js";
import {
  addOrEditPurchase,
  delPurchase,
  purchaseListPage,
  productList,
  getPurchaseById,
  getOptions,
  createPurchaseNo,
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
const productSelectedRows = ref([]);
const modelOptions = ref([]);
const userList = ref([]);
const productOptions = ref([]);
const supplierList = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const fileList = ref([]);
import useUserStore from "@/store/modules/user";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import dayjs from "dayjs";
const userStore = useUserStore();
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "", // ä¾›åº”商名称
    purchaseContractNumber: "", // é‡‡è´­åˆåŒç¼–号
    salesContractNo: "", // é”€å”®åˆåŒç¼–号
    projectName: "", // é¡¹ç›®åç§°
    entryDate: [
      dayjs().format("YYYY-MM-DD"),
      dayjs().add(1, "day").format("YYYY-MM-DD"),
    ], // å½•入日期
    entryDateStart: dayjs().format("YYYY-MM-DD"),
    entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
  },
  form: {
    purchaseContractNumber: "",
    salesContractNo: "",
    projectName: "",
    recorderId: "",
    entryDate: "",
    productData: [],
    supplierName: "",
    supplierId: "",
    paymentMethod: "",
  },
  rules: {
    purchaseContractNumber: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const {  form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false);
const productOperationType = ref("");
const productOperationIndex = ref("");
const currentId = ref("");
const productFormData = reactive({
  productForm: {
    productId: "",
    productCategory: "",
    productModelId: "",
    specificationModel: "",
    unit: "",
    quantity: "",
    taxInclusiveUnitPrice: "",
    taxRate: "",
    taxInclusiveTotalPrice: "",
    taxExclusiveTotalPrice: "",
    invoiceType: "",
  },
  productRules: {
    productId: [{ required: true, message: "请选择", trigger: "change" }],
    productModelId: [{ required: true, message: "请选择", trigger: "change" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    taxInclusiveUnitPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxExclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { productForm, productRules } = toRefs(productFormData);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const changeDaterange = (value) => {
  if (value) {
    searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    searchForm.entryDateStart = undefined;
    searchForm.entryDateEnd = undefined;
  }
  handleQuery();
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
      "ticketsNum",
      "ticketsAmount",
      "futureTickets",
      "futureTicketsAmount",
    ],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { entryDate, ...rest } = searchForm;
  purchaseListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      tableData.value.map((item) => {
        item.children = [];
      });
      total.value = res.data.total;
      expandedRowKeys.value = [];
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const productSelected = (selectedRows) => {
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res.data;
        }
        expandedRowKeys.value.push(row.id);
      });
    } catch (error) {
      console.log(error);
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, ["contractAmount"]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeProTable = (param) => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  productData.value = [];
  fileList.value = [];
  if (operationType.value == "add") {
    createPurchaseNo().then((res) => {
      form.value.purchaseContractNumber = res.data;
    });
  }
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  getOptions().then((res) => {
    supplierList.value = res.data;
  });
  form.value.recorderId = userStore.id;
  form.value.entryDate = getCurrentDate();
  if (type === "edit") {
    currentId.value = row.id;
    getPurchaseById({ id: row.id, type: 2 }).then((res) => {
      form.value = { ...res };
      productData.value = form.value.productData;
      if (form.value.salesLedgerFiles) {
        fileList.value = form.value.salesLedgerFiles;
      } else {
        fileList.value = [];
      }
    });
  }
  dialogFormVisible.value = true;
};
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (file.size > 1024 * 1024 * 50) {
    proxy.$modal.msgError("上传文件大小不能超过50MB!");
    return false;
  }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    file.tempId = res.data.tempId;
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
function handleRemove(file) {
  console.log("handleRemove", file.id);
  if (file.size > 1024 * 1024 * 10) {
    // ä»…前端清理,不调用删除接口和提示
    return;
  }
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
    });
  }
}
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (productData.value.length > 0) {
        form.value.productData = proxy.HaveJson(productData.value);
      } else {
        proxy.$modal.msgWarning("请添加产品信息");
        return;
      }
      let tempFileIds = [];
      if (fileList.value.length > 0) {
        tempFileIds = fileList.value.map((item) => item.tempId);
      }
      form.value.tempFileIds = tempFileIds;
      form.value.type = 2;
      addOrEditPurchase(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    }
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// æ‰“开产品弹框
const openProductForm = (type, row, index) => {
  productOperationType.value = type;
  productOperationIndex.value = index;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  if (type === "edit") {
    productForm.value = { ...row };
  }
  productFormVisible.value = true;
  getProductOptions();
};
const getProductOptions = () => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  });
};
const getModels = (value) => {
  productForm.value.productCategory = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  });
};
const getProductModel = (value) => {
  const index = modelOptions.value.findIndex((item) => item.id === value);
  if (index !== -1) {
    productForm.value.specificationModel = modelOptions.value[index].model;
    productForm.value.unit = modelOptions.value[index].unit;
  } else {
    productForm.value.specificationModel = null;
    productForm.value.unit = null;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode.label; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// æäº¤äº§å“è¡¨å•
const submitProduct = () => {
  proxy.$refs["productFormRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitProductEdit();
      } else {
        if (productOperationType.value === "add") {
          productData.value.push({ ...productForm.value });
          console.log("productData.value---", productData.value);
        } else {
          productData.value[productOperationIndex.value] = {
            ...productForm.value,
          };
        }
        closeProductDia();
      }
    }
  });
};
const submitProductEdit = () => {
  productForm.value.salesLedgerId = currentId.value;
  productForm.value.type = 2;
  addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeProductDia();
    getPurchaseById({ id: currentId.value, type: 2 }).then((res) => {
      productData.value = res.productData;
    });
  });
};
// åˆ é™¤äº§å“
const deleteProduct = () => {
  if (productSelectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  if (operationType.value === "add") {
    productSelectedRows.value.forEach((selectedRow) => {
      const index = productData.value.findIndex(
        (product) => product.id === selectedRow.id
      );
      if (index !== -1) {
        productData.value.splice(index, 1);
      }
    });
  } else {
    let ids = [];
    if (productSelectedRows.value.length > 0) {
      ids = productSelectedRows.value.map((item) => item.id);
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delProduct(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          closeProductDia();
          getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
            (res) => {
              productData.value = res.productData;
            }
          );
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  }
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProductDia = () => {
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/purchase/ledger/export", {}, "采购台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
        // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
        const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== 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(() => {
      delPurchase(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .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}`;
}
const mathNum = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (!productForm.value.taxInclusiveUnitPrice) {
    return;
  }
  if (!productForm.value.quantity) {
    return;
  }
  // å«ç¨Žæ€»ä»·è®¡ç®—
  productForm.value.taxInclusiveTotalPrice =
    proxy.calculateTaxIncludeTotalPrice(
      productForm.value.taxInclusiveUnitPrice,
      productForm.value.quantity
    );
  if (productForm.value.taxRate) {
    // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        productForm.value.taxInclusiveTotalPrice,
        productForm.value.taxRate
      );
  }
};
const reverseMathNum = (field) => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  const taxRate = Number(productForm.value.taxRate);
  if (!taxRate) return;
  if (field === 'taxInclusiveTotalPrice') {
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žæ€»ä»·å’Œå«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
    // åç®—不含税总价
    productForm.value.taxExclusiveTotalPrice =
      (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
  } else if (field === 'taxExclusiveTotalPrice') {
    // åç®—含税总价
    productForm.value.taxInclusiveTotalPrice =
      (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2);
    // å·²çŸ¥æ•°é‡ï¼Œåç®—含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
    }
    // å·²çŸ¥å«ç¨Žå•价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
    }
  }
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss"></style>
src/views/productionManagement/operationScheduling/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,181 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="工序排产"
        width="70%"
        @close="closeDia"
    >
      <el-button type="primary" @click="addRow" style="margin-bottom: 10px;">新增</el-button>
            <span style="font-size: 18px;margin-left: 10px">待排产数量:{{pendingNum}}</span>
      <el-table :data="tableData" border style="width: 100%" :summary-method="summarizeMainTable" show-summary :row-key="row => row.id" stripe>
        <el-table-column label="序号" width="60">
          <template #default="scope">
            {{ scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column label="工序" prop="process">
          <template #default="scope">
            <el-input
              v-model="scope.row.process"
              placeholder="请输入工序"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="单位" prop="unit">
          <template #default="scope">
            <el-input v-model="scope.row.unit" placeholder="请输入单位" />
          </template>
        </el-table-column>
        <el-table-column label="排产数量" width="200" prop="schedulingNum">
          <template #default="scope">
                        <el-input-number
                            v-model="scope.row.schedulingNum"
                            placeholder="请输入"
                            :min="0"
                            :step="0.1"
                            :precision="2"
                            clearable
                            style="width: 100%"
                        />
          </template>
        </el-table-column>
        <el-table-column label="工时定额" width="200" prop="workHours">
          <template #default="scope">
                        <el-input-number
                            v-model="scope.row.workHours"
                            placeholder="请输入"
                            :min="0"
                            :step="0.1"
                            :precision="2"
                            clearable
                            style="width: 100%"
                        />
          </template>
        </el-table-column>
        <el-table-column label="排产日期" prop="schedulingDate">
          <template #default="scope">
            <el-date-picker v-model="scope.row.schedulingDate" type="date" placeholder="选择日期" style="width: 100%;" value-format="YYYY-MM-DD" format="YYYY-MM-DD"/>
          </template>
        </el-table-column>
        <el-table-column label="排产人" prop="schedulingUserId">
          <template #default="scope">
                        <el-select
                            v-model="scope.row.schedulingUserId"
                            placeholder="选择人员"
                            style="width: 100%;"
                        >
                            <el-option
                                v-for="user in userList"
                                :key="user.userId"
                                :label="user.nickName"
                                :value="user.userId"
                            />
                        </el-select>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80">
          <template #default="scope">
            <el-button type="danger" size="small" @click="removeRow(scope.$index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">确认</el-button>
                    <el-button @click="closeDia">取消</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {processScheduling} from "@/api/productionManagement/operationScheduling.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const tableData = ref([
    { process: '', schedulingDate: '', schedulingNum: '', schedulingUserId: '', workHours: '', unit: '' }
]);
const unitFromRow = ref('');
const idFromRow = ref('');
const pendingNum = ref('');
const userList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    pendingNum.value = row.pendingNum
  if (row && row.unit !== undefined) {
    unitFromRow.value = row.unit;
    idFromRow.value = row.id;
    tableData.value.forEach(item => {
      item.unit = row.unit;
      item.id = row.id;
    });
  } else {
    unitFromRow.value = '';
  }
}
const submitForm = () => {
    // 1. æ£€æŸ¥æ¯ä¸€è¡Œæ˜¯å¦å¡«å†™å®Œæ•´
    for (let i = 0; i < tableData.value.length; i++) {
        const row = tableData.value[i];
        if (
            !row.process ||
            !row.schedulingDate ||
            row.schedulingNum === '' || row.schedulingNum === null ||
            !row.schedulingUserId ||
            row.workHours === '' || row.workHours === null ||
            !row.unit
        ) {
            proxy.$modal.msgError(`第${i + 1}行数据未填写完整`);
            return;
        }
    }
    // 2. åˆè®¡æŽ’产数量
    const totalSchedulingNum = tableData.value.reduce((sum, row) => {
        return sum + Number(row.schedulingNum || 0);
    }, 0);
    if (totalSchedulingNum > Number(pendingNum.value)) {
        proxy.$modal.msgError('排产数量合计不能超过待排产数量');
        return;
    }
    processScheduling(tableData.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
    })
}
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, ['schedulingNum']);
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
const addRow = () => {
  tableData.value.push({ id: idFromRow.value, process: '', unit: unitFromRow.value, schedulingNum: '', workHours: '', schedulingDate: '', schedulingUserId: '' });
};
const removeRow = (index) => {
  tableData.value.splice(index, 1);
};
</script>
<style scoped>
</style>
src/views/productionManagement/operationScheduling/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,228 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <el-form :model="searchForm" :inline="true">
                <el-form-item label="派工日期:">
                    <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                    placeholder="请选择" clearable @change="changeDaterange" />
                </el-form-item>
                <el-form-item label="状态:">
                    <el-select v-model="searchForm.status" placeholder="请选择状态" @change="handleQuery" style="width: 140px" clearable>
                        <el-option label="待排产" :value="1"></el-option>
                        <el-option label="已排产" :value="3"></el-option>
                        <el-option label="排产中" :value="2"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleQuery">搜索</el-button>
                </el-form-item>
            </el-form>
        </div>
        <div class="table_list">
            <div style="text-align: right" class="mb10">
                <el-button type="primary" @click="openForm">工序排产</el-button>
                <el-button type="danger" @click="handleDelete" plain>取消排产</el-button>
            </div>
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import FormDia from "@/views/productionManagement/operationScheduling/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {listPageProcess, productionDispatchDelete} from "@/api/productionManagement/operationScheduling.js";
const data = reactive({
    searchForm: {
        staffName: "",
        status: 1,
        entryDate: [
            dayjs().format("YYYY-MM-DD"),
            dayjs().add(1, "day").format("YYYY-MM-DD"),
        ], // å½•入日期
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 3) {
                return "已排产";
            } else if (params == 1) {
                return "待排产";
            } else {
                return '排产中';
            }
        },
        formatType: (params) => {
            if (params == 3) {
                return "success";
            } else if (params == 1) {
                return "primary";
            } else {
                return 'warning';
            }
        },
    },
    {
        label: "派工日期",
        prop: "schedulingDate",
        width: 120,
    },
    {
        label: "派工人",
        prop: "schedulingUserName",
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 150,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 150,
    },
    {
        label: "单位",
        prop: "unit",
    },
    {
        label: "排产总数",
        prop: "schedulingNum",
    },
    {
        label: "已排产数量",
        prop: "successNum",
        width: 100,
    },
    {
        label: "待排产数量",
        prop: "pendingNum",
        width: 100,
    },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
        searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    listPageProcess(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.successNum) || 0)
        }));
        page.total = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
        proxy.$message.error("请选择一条数据");
        return;
    }
    if (selectedRows.value[0].pendingNum == 0) {
        proxy.$message.warning("无需再排产");
        return;
    }
    nextTick(() => {
        formDia.value?.openDialog(type, selectedRows.value[0])
    })
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        // æ–°å¢žï¼šåˆ¤æ–­æ˜¯å¦æœ‰å·²æŽ’产的数据
        const hasScheduled = selectedRows.value.some(item => item.status == 3);
        if (hasScheduled) {
            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;
            productionDispatchDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("取消排产成功");
                    getList();
                })
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped></style>
src/views/productionManagement/productionCosting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">生产日期:</span>
                <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                placeholder="请选择" clearable @change="changeDaterange" />
                <span class="search_title ml10">生产人:</span>
                <el-input
                    v-model="searchForm.schedulingUserName"
                    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 @click="handleOut">导出</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {
    listCustomer,
} from "@/api/basicData/customerFile.js";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {productionAccountingListPage} from "@/api/productionManagement/productionCosting.js";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
    {
        label: "生产日期",
        prop: "schedulingDate",
        width: 120,
    },
    {
        label: "生产人",
        prop: "schedulingUserName",
        width: 90,
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 160,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 160,
    },
    {
        label: "单位",
        prop: "unit",
    },
    {
        label: "工序",
        prop: "process",
    },
    {
        label: "生产数量",
        prop: "finishedNum",
        width: 100,
    },
    {
        label: "工时定额",
        prop: "workHours",
        width: 100,
    },
    {
        label: "工资",
        prop: "wages",
        width: 100,
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const data = reactive({
    searchForm: {
        schedulingUserName: "",
        entryDate: [
            dayjs().format("YYYY-MM-DD"),
            dayjs().add(1, "day").format("YYYY-MM-DD"),
        ], // å½•入日期
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
        searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    productionAccountingListPage(params).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/basic/customer/export", {}, "生产核算.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped lang="scss"></style>
src/views/productionManagement/productionDispatching/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,167 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="生产派工"
        width="50%"
        @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="productCategory">
              <el-input v-model="form.productCategory" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="总数量:" prop="quantity">
              <el-input v-model="form.quantity" placeholder="请输入" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
                        <el-form-item label="待排产数量:" prop="pendingQuantity">
                            <el-input v-model="form.pendingQuantity" placeholder="请输入" clearable disabled/>
                        </el-form-item>
          </el-col>
          <el-col :span="12">
                        <el-form-item label="本次排产数量:" prop="schedulingNum">
                            <el-input-number
                                v-model="form.schedulingNum"
                                placeholder="请输入"
                                :min="0"
                                :step="0.1"
                                :precision="2"
                                clearable
                                @change="changeNum"
                                style="width: 100%"
                            />
                        </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="派工人:" prop="schedulingUserId">
                            <el-select
                                v-model="form.schedulingUserId"
                                placeholder="选择人员"
                                style="width: 100%;"
                            >
                                <el-option
                                    v-for="user in userList"
                                    :key="user.userId"
                                    :label="user.nickName"
                                    :value="user.userId"
                                />
                            </el-select>
                        </el-form-item>
                    </el-col>
          <el-col :span="12">
            <el-form-item label="派工日期:" prop="schedulingDate">
              <el-date-picker
                  v-model="form.schedulingDate"
                  type="date"
                  placeholder="请选择日期"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  clearable
                  style="width: 100%"
              />
            </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>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
import useUserStore from "@/store/modules/user.js";
import dayjs from "dayjs";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {
        productCategory: "",
        quantity: "",
        schedulingNum: "",
        schedulingUserId: "",
        schedulingDate: "",
        pendingQuantity: "",
        salesLedgerProductId: "",
  },
  rules: {
        schedulingNum: [{ required: true, message: "请输入", trigger: "blur" },],
        schedulingUserId: [{ required: true, message: "请选择", trigger: "change" },],
        schedulingDate: [{ required: true, message: "请选择", trigger: "change" },],
  },
});
const { form, rules } = toRefs(data);
const userList = ref([])
const userStore = useUserStore()
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    form.value = {...row}
    // ç»‘定外层传入的产品ID到后端需要的 salesLedgerProductId å­—段
    form.value.salesLedgerProductId = row.id;
    // ç¡®ä¿ä¸ä¼šæŠŠåŽŸå§‹ id å½“作排产记录主键传给后端
    delete form.value.id;
    form.value.schedulingNum = 0
    form.value.schedulingUserId = userStore.id
    form.value.schedulingDate = dayjs().format("YYYY-MM-DD");
}
//
const changeNum = (value) => {
    if (value > form.value.pendingQuantity) {
        form.value.schedulingNum = form.value.pendingQuantity;
        proxy.$modal.msgWarning('排产数量不可大于待排产数量')
    }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
            productionDispatch(form.value).then(res => {
                proxy.$modal.msgSuccess("提交成功");
                closeDia();
            })
    }
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/productionManagement/productionDispatching/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,164 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">录入日期:</span>
                <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                placeholder="请选择" clearable @change="changeDaterange" />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">生产派工</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"
                :total="page.total"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import FormDia from "@/views/productionManagement/productionDispatching/components/formDia.vue";
import dayjs from "dayjs";
import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
const data = reactive({
    searchForm: {
        entryDate: [
            dayjs().format("YYYY-MM-DD"),
            dayjs().add(1, "day").format("YYYY-MM-DD"),
        ], // å½•入日期
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
    {
        label: "录入日期",
        prop: "registerDate",
        width: 120,
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 160,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 220,
    },
    {
        label: "单位",
        prop: "unit",
        width:90
    },
    {
        label: "数量",
        prop: "quantity",
    },
    {
        label: "排产数量",
        prop: "schedulingNum",
        width: 100,
    },
    {
        label: "待排数量",
        prop: "pendingQuantity",
        width: 100,
    },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    schedulingListPage(params).then((res) => {
        tableLoading.value = false;
        // å¤„理每条数据,增加pendingQuantity字段
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
        }));
        page.total = res.data.total;
    }).catch(() => {
        tableLoading.value = false;
    })
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type) => {
    if (selectedRows.value.length !== 1) {
        proxy.$message.error("请选择一条数据");
        return;
    }
    if (selectedRows.value[0].pendingQuantity == 0) {
        proxy.$message.warning("无需再派工");
        return;
    }
    nextTick(() => {
        formDia.value?.openDialog(type, selectedRows.value[0])
    })
};
onMounted(() => {
    searchForm.value.entryDate = [
        dayjs().format("YYYY-MM-DD"),
        dayjs().add(1, "day").format("YYYY-MM-DD"),
    ]
    searchForm.value.entryDateStart = dayjs().format("YYYY-MM-DD")
    searchForm.value.entryDateEnd = dayjs().add(1, "day").format("YYYY-MM-DD")
    getList();
});
</script>
<style scoped></style>
src/views/productionManagement/productionOrder/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,374 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">产品大类:</span>
                <el-tree-select
                    v-model="searchForm.productCategory"
                    :data="productOptions"
                    placeholder="请选择"
                    clearable
                    check-strictly
                    :render-after-expand="false"
                    style="width: 240px"
                    @change="handleQuery"
                />
                <span class="search_title ml10">录入日期:</span>
                <el-date-picker v-model="searchForm.registerDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                placeholder="请选择" clearable @change="changeDaterange" />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openDialog('create')">新增订单</el-button>
                <el-button @click="handleOut">导出</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                @pagination="pagination"
            >
                <template #action="{ row }">
                    <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
                    <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
                </template>
            </PIMTable>
        </div>
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="40%" @close="closeDialog">
        <el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
            <el-form-item label="录入日期" prop="registerDate">
                <el-date-picker v-model="form.registerDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="请选择录入日期" style="width: 100%"/>
            </el-form-item>
            <el-form-item label="产品大类" prop="productCategory">
                <el-input
                    v-model="form.productCategory"
                    placeholder="请输入产品大类"
                    clearable
                />
            </el-form-item>
            <el-form-item label="规格型号" prop="specificationModel">
                <el-input
                    v-model="form.specificationModel"
                    placeholder="请输入规格型号"
                    clearable
                />
            </el-form-item>
            <el-form-item label="单位" prop="unit">
                <el-input
                    v-model="form.unit"
                    placeholder="请输入单位"
                    clearable
                />
            </el-form-item>
            <el-form-item label="数量" prop="quantity">
                <el-input-number v-model="form.quantity" :min="0" :step="0.1" style="width: 100%"/>
            </el-form-item>
        </el-form>
        <template #footer>
            <div class="dialog-footer">
                <el-button type="primary" @click="submitForm">确认</el-button>
                <el-button @click="closeDialog">取消</el-button>
            </div>
        </template>
    </el-dialog>
    </div>
</template>
<script setup>
import {onMounted, ref, reactive, toRefs, getCurrentInstance} from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {schedulingListPage, addProductionOrder, updateProductionOrder, deleteProductionOrder} from "@/api/productionManagement/productionOrder.js";
import {productTreeList} from "@/api/basicData/product.js";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
    {
        label: "录入日期",
        prop: "registerDate",
        width: 120,
    },
    {
        label: "生产订单号",
        prop: "orderNo",
    },
    {
        label: "产品大类",
        prop: "productCategory",
    },
    {
        label: "规格型号",
        prop: "specificationModel",
    },
    {
        label: "单位",
        prop: "unit",
    },
    {
        label: "数量",
        prop: "quantity",
    },
    // {
    //     label: "排产数量",
    //     prop: "schedulingNum",
    //     width: 100,
    // },
    // {
    //     label: "完工数量",
    //     prop: "successNum",
    //     width: 100,
    // },
    {
        label: "操作",
        prop: "action",
        width: 120,
        fixed: "right",
        dataType: "slot",
        align: "center",
        slot: "action"
    }
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = ref({
    current: 1,
    size: 100,
    total: 0,
});
const dialogVisible = ref(false);
const dialogTitle = ref("");
const dialogMode = ref(""); // 'create' æˆ– 'edit'
const formRef = ref();
const productOptions = ref([]);
const form = reactive({
    id: null,
    registerDate: dayjs().format("YYYY-MM-DD"),
    productCategory: "",
    specificationModel: "",
    unit: "",
    quantity: null,
});
const formRules = {
    registerDate: [{ required: true, message: "请选择录入日期", trigger: "change" }],
    productCategory: [{ required: true, message: "请输入产品大类", trigger: "blur" }],
    specificationModel: [{ required: true, message: "请输入规格型号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入数量", trigger: "blur" }],
};
const data = reactive({
    searchForm: {
        productCategory: "",
        registerDate: null, // å½•入日期
        entryDateStart: undefined,
        entryDateEnd: undefined,
    },
});
const { searchForm } = toRefs(data);
const openDialog = (mode, row = null) => {
    dialogMode.value = mode;
    if (mode === 'create') {
        dialogTitle.value = "新增生产订单";
        resetForm();
    } else if (mode === 'edit') {
        dialogTitle.value = "编辑生产订单";
        resetForm();
        console.log('编辑数据:', row);
        // å¡«å……编辑数据
        form.id = row.id;
        form.registerDate = row.registerDate;
        form.productCategory = row.productCategory;
        form.specificationModel = row.specificationModel;
        form.unit = row.unit;
        form.quantity = row.quantity;
    }
    dialogVisible.value = true;
};
const closeDialog = () => {
    dialogVisible.value = false;
};
const resetForm = () => {
    form.id = null;
    form.registerDate = dayjs().format("YYYY-MM-DD");
    form.productCategory = "";
    form.specificationModel = "";
    form.unit = "";
    form.quantity = null;
};
const submitForm = () => {
    formRef.value?.validate(async (valid) => {
        if (!valid) return;
        try {
            const payload = {
                registerDate: form.registerDate,
                productCategory: form.productCategory,
                specificationModel: form.specificationModel,
                unit: form.unit,
                quantity: form.quantity,
            };
            if (dialogMode.value === 'create') {
                await addProductionOrder(payload);
                proxy.$modal.msgSuccess("新增成功");
            } else if (dialogMode.value === 'edit') {
                payload.id = form.id;
                await updateProductionOrder(payload);
                proxy.$modal.msgSuccess("编辑成功");
            }
            closeDialog();
            getList();
        } catch (err) {
            console.error(`${dialogMode.value === 'create' ? '新增' : '编辑'}失败`, err);
            proxy.$modal.msgError(`${dialogMode.value === 'create' ? '新增' : '编辑'}失败,请重试`);
        }
    });
};
// ç¼–辑方法
const handleEdit = (row) => {
    openDialog('edit', row);
};
// åˆ é™¤æ–¹æ³•
const handleDelete = (row) => {
    proxy.$modal.confirm(`确定要删除生产订单"${row.orderNo}"吗?`, "删除确认", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        try {
            await deleteProductionOrder([row.id]);
            proxy.$modal.msgSuccess("删除成功");
            getList(); // åˆ·æ–°åˆ—表
        } catch (err) {
            console.error("删除失败", err);
            proxy.$modal.msgError("删除失败,请重试");
        }
    }).catch(() => {
        proxy.$modal.msg("已取消删除");
    });
};
const getProductOptions = () => {
    return productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res || []);
    });
};
const convertIdToValue = (data) => {
    return data.map((item) => {
        const { id, children, ...rest } = item;
        const newItem = {
            ...rest,
            value: id,
        };
        if (children && children.length > 0) {
            newItem.children = convertIdToValue(children);
        }
        return newItem;
    });
};
const findNodeById = (nodes, value) => {
    for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].value === value) {
            return nodes[i].label;
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
            const label = findNodeById(nodes[i].children, value);
            if (label) return label;
        }
    }
    return null;
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.value.current = 1;
    getList();
};
const pagination = (obj) => {
    page.value.current = obj.page;
    page.value.size = obj.limit;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const getList = () => {
    tableLoading.value = true;
    // æž„造一个新的对象,不包含entryDate字段和 total å­—段
    const { total, ...pageParams } = page.value;
    const params = { ...searchForm.value, ...pageParams };
    params.registerDate = undefined;
    if (params.productCategory) {
        // å¦‚果是对象类型,获取其label(名称)而不是value(ID)
        if (typeof params.productCategory === "object") {
            params.productCategory = findNodeById(productOptions.value, params.productCategory) || params.productCategory;
        }
        // å¦‚果是ID,转换为名称
        else if (typeof params.productCategory === "string" || typeof params.productCategory === "number") {
            const categoryName = findNodeById(productOptions.value, params.productCategory);
            if (categoryName) {
                params.productCategory = categoryName;
            }
        }
    }
    schedulingListPage(params).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.value.total = res.data.total;
    }).catch(() => {
        tableLoading.value = false;
    })
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/salesLedger/scheduling/export", {}, "生产订单.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getProductOptions();
    // ä¸è®¾ç½®é»˜è®¤æ—¥æœŸï¼Œå…¨éƒ¨æ¡ä»¶ä¸ºç©ºåŠ è½½
    searchForm.value.registerDate = null;
    searchForm.value.entryDateStart = undefined;
    searchForm.value.entryDateEnd = undefined;
    getList();
});
</script>
<style scoped lang="scss"></style>
src/views/productionManagement/productionReporting/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,401 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <el-form :model="searchForm" :inline="true">
                <el-form-item label="排产日期:">
                    <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                    placeholder="请选择" clearable @change="changeDaterange" />
                </el-form-item>
                <el-form-item label="状态:">
                    <el-select v-model="searchForm.status" placeholder="请选择状态" style="width: 140px" clearable>
                        <el-option label="待生产" :value="1"></el-option>
                        <el-option label="已报工" :value="3"></el-option>
                        <el-option label="生产中" :value="2"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleQuery">搜索</el-button>
                </el-form-item>
            </el-form>
        </div>
        <div class="table_list">
            <div style="text-align: right" class="mb10">
                <el-button type="primary" @click="openForm('add')">生产报工</el-button>
            </div>
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                :expandRowKeys="expandedRowKeys"
                @expand-change="expandChange"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            >
                <template #expand="{ row }">
                    <el-table
                        :data="expandData"
                        border
                        show-summary
                        stripe
                        :summary-method="summarizeMainTable"
                        v-loading="childrenLoading"
                    >
                        <el-table-column
                            align="center"
                            label="序号"
                            type="index"
                            width="60"
                        />
                        <el-table-column label="本次生产数量" prop="finishedNum" align="center" width="400">
                            <template #default="scope">
                                <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                                 v-model="scope.row.finishedNum"
                                                                 :disabled="!scope.row.editType"
                                                                 :precision="2"
                                                                 placeholder="请输入"
                                                                 clearable
                                                                 @change="changeNum(scope.row)"
                                />
                            </template>
                        </el-table-column>
<!--                        <el-table-column label="待生产数量" prop="pendingNum" width="240" align="center"></el-table-column>-->
                        <el-table-column label="生产人" prop="schedulingUserId" width="400">
                            <template #default="scope">
                                <el-select
                                    v-model="scope.row.schedulingUserId"
                                    placeholder="选择人员"
                                    :disabled="!scope.row.editType"
                                    style="width: 100%;"
                                >
                                    <el-option
                                        v-for="user in userList"
                                        :key="user.userId"
                                        :label="user.nickName"
                                        :value="user.userId"
                                    />
                                </el-select>
                            </template>
                        </el-table-column>
                        <el-table-column label="生产日期" prop="schedulingDate" width="400">
                            <template #default="scope">
                                <el-date-picker
                                    v-model="scope.row.schedulingDate"
                                    type="date"
                                    :disabled="!scope.row.editType"
                                    placeholder="请选择日期"
                                    value-format="YYYY-MM-DD"
                                    format="YYYY-MM-DD"
                                    clearable
                                    style="width: 100%"
                                />
                            </template>
                        </el-table-column>
                        <el-table-column label="操作" width="60">
                            <template #default="scope">
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="changeEditType(scope.row)"
                                    v-if="!scope.row.editType"
                                    :disabled="scope.row.parentStatus === 3"
                                >编辑</el-button
                                >
                                <el-button
                                    link
                                    type="primary"
                                    size="small"
                                    @click="saveReceiptPayment(scope.row)"
                                    v-if="scope.row.editType"
                                >保存</el-button
                                >
                            </template>
                        </el-table-column>
                    </el-table>
                </template>
            </PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import FormDia from "@/views/productionManagement/productionReporting/components/formDia.vue";
import {staffJoinDel, staffJoinListPage} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {
    productionReportUpdate,
    workListPage,
    workListPageById
} from "@/api/productionManagement/productionReporting.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
const data = reactive({
    searchForm: {
        staffName: "",
        entryDate: [
            dayjs().format("YYYY-MM-DD"),
            dayjs().add(1, "day").format("YYYY-MM-DD"),
        ], // å½•入日期
        entryDateStart: dayjs().format("YYYY-MM-DD"),
        entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
    },
});
const { searchForm } = toRefs(data);
const expandedRowKeys = ref([]);
const expandData = ref([]);
const userList = ref([])
const tableColumn = ref([
    {
        type: "expand",
        dataType: "slot",
        slot: "expand",
    },
    {
        label: "状态",
        prop: "status",
        dataType: "tag",
        formatData: (params) => {
            if (params == 3) {
                return "已报工";
            } else if (params == 1) {
                return "待生产";
            } else {
                return '生产中';
            }
        },
        formatType: (params) => {
            if (params == 3) {
                return "success";
            } else if (params == 1) {
                return "primary";
            } else {
                return 'warning';
            }
        },
    },
    {
        label: "排产日期",
        prop: "schedulingDate",
        width: 120,
    },
    {
        label: "排产人",
        prop: "schedulingUserName",
    },
    {
        label: "产品大类",
        prop: "productCategory",
        width: 150,
    },
    {
        label: "规格型号",
        prop: "specificationModel",
        width: 150,
    },
    {
        label: "单位",
        prop: "unit",
    },
    {
        label: "工序",
        prop: "process",
    },
    {
        label: "排产数量",
        prop: "schedulingNum",
        width: 100,
    },
    {
        label: "生产数量",
        prop: "finishedNum",
        width: 100,
    },
    {
        label: "待生产数量",
        prop: "pendingFinishNum",
        width: 100,
    },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const childrenLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const changeDaterange = (value) => {
    if (value) {
        searchForm.value.entryDateStart = value[0];
        searchForm.value.entryDateEnd = value[1];
    } else {
        searchForm.value.entryDateStart = undefined;
        searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined
    expandedRowKeys.value = []
    workListPage(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records.map(item => ({
            ...item,
            pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
        }));
        page.total = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
};
// å±•开行
const expandChange = (row, expandedRows) => {
    userListNoPageByTenantId().then((res) => {
        userList.value = res.data;
    });
    if (expandedRows.length > 0) {
        nextTick(() => {
            expandedRowKeys.value = [];
            try {
                childrenLoading.value = true;
                workListPageById({ id: row.id }).then((res) => {
                    childrenLoading.value = false;
                    const index = tableData.value.findIndex((item) => item.id === row.id);
                    if (index > -1) {
                        expandData.value = res.data.map(item => ({
                            ...item,
                            pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
                            parentStatus: row.status // æ–°å¢žçˆ¶è¡¨çŠ¶æ€
                        }));
                    }
                    expandedRowKeys.value.push(row.id);
                });
            } catch (error) {
                childrenLoading.value = false;
                console.log(error);
            }
        })
    } else {
        expandedRowKeys.value = [];
    }
};
const changeNum = (row) => {
    // æ‰¾åˆ°çˆ¶è¡¨æ ¼æ•°æ®
    const parentRow = tableData.value.find(item => item.id === expandedRowKeys.value[0]);
    // è®¡ç®—所有子表格 finishedNum çš„æ€»å’Œ
    const totalFinishedNum = expandData.value.reduce((sum, item) => sum + (Number(item.finishedNum) || 0), 0);
    // çˆ¶è¡¨æ ¼çš„æŽ’产数量
    const schedulingNum = parentRow ? Number(parentRow.schedulingNum) : 0;
    if (totalFinishedNum > schedulingNum) {
        // å›žé€€æœ¬æ¬¡è¾“å…¥
        row.finishedNum = schedulingNum - (totalFinishedNum - Number(row.finishedNum));
        proxy.$modal.msgWarning('所有本次生产数量之和不可大于排产数量');
    }
    row.pendingNum = row.schedulingNum - row.finishedNum;
}
// ç¼–辑修改状态
const changeEditType = (row) => {
    row.editType = !row.editType;
};
// ä¿å­˜è®°å½•
const saveReceiptPayment = (row) => {
    productionReportUpdate(row).then((res) => {
        row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
    });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const summarizeMainTable = (param) => {
    return proxy.summarizeTable(param, [
        "finishedNum"
    ]);
};
// æ‰“开弹框
const openForm = (type, row) => {
    if (selectedRows.value.length !== 1) {
        proxy.$message.error("请选择一条数据");
        return;
    }
    if (selectedRows.value[0].pendingFinishNum == 0) {
        proxy.$message.warning("无需再报工");
        return;
    }
    nextTick(() => {
        const rowInfo = type === 'add' ? selectedRows.value[0] : row
        formDia.value?.openDialog(type, rowInfo)
    })
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            staffJoinDel(ids).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "人员入职.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped></style>
src/views/salesManagement/receiptPayment/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,640 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :inline="true" :model="searchForm" style="width: 100%">
        <el-row justify="space-between">
          <el-col :span="24">
            <el-form-item label="客户名称">
              <el-input
                v-model="searchForm.customerName"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                prefix-icon="Search"
              />
            </el-form-item>
            <el-form-item label="客户合同号">
              <el-input
                v-model="searchForm.customerContractNo"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                prefix-icon="Search"
              />
            </el-form-item>
            <el-form-item label="项目名称">
              <el-input
                v-model="searchForm.projectName"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                prefix-icon="Search"
              />
            </el-form-item>
            <el-form-item>
              <el-checkbox
                v-model="searchForm.status"
                label="不显示待回款为0"
                @change="handleQuery"
              />
            </el-form-item>
            <br/>
            <el-form-item label="开票日期">
              <el-date-picker style="width: 240px" v-model="searchForm.commonDate" 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"> æœç´¢ </el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="openForm('add')">
            æ–°å¢žå›žæ¬¾
          </el-button>
          <el-button icon="Download" @click="handleOut"> å¯¼å‡º </el-button>
        </div>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        :row-key="(row) => row.id"
        show-summary
        :summary-method="summarizeMainTable"
        :expand-row-keys="expandedRowKeys"
        @expand-change="expandChange"
        stripe
        height="calc(100vh - 21.5em)"
      >
        <el-table-column align="center" type="selection" 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="receiptPaymentDate"  width="130"/>
              <el-table-column label="回款金额" prop="receiptPaymentAmount">
                <template #default="scope">
                                    <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                                     v-model="scope.row.receiptPaymentAmount"
                                                                     :disabled="!scope.row.editType"
                                                                     :precision="2"
                                                                     placeholder="请输入"
                                                                     clearable
                                    />
                </template>
              </el-table-column>
              <el-table-column label="回款方式" prop="receiptPaymentType">
                <template #default="scope">
                  <el-input
                    v-model="scope.row.receiptPaymentType"
                    placeholder="请输入"
                    clearable
                    :disabled="!scope.row.editType"
                  />
                </template>
              </el-table-column>
              <el-table-column label="登记人" prop="registrant" width="90"/>
              <el-table-column label="登记日期" prop="createTime"   width="130"/>
              <el-table-column label="操作" width="150" align="center">
                <template #default="scope">
                  <el-button
                    link
                    type="primary"
                    size="small"
                    @click="changeEditType(scope.row)"
                    v-if="!scope.row.editType"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >编辑</el-button
                  >
                  <el-button
                    link
                    type="primary"
                    size="small"
                    @click="saveReceiptPayment(scope.row)"
                    v-if="scope.row.editType"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >保存</el-button
                  >
                  <el-button
                    link
                    type="primary"
                    size="small"
                    @click="delReceiptRecord(scope.row)"
                                        :disabled="scope.row.registrant !== userStore.nickName"
                    >删除</el-button
                  >
                </template>
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column
            label="开票日期"
            prop="invoiceDate"
            show-overflow-tooltip
            width="240"
        />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          show-overflow-tooltip
          width="240"
        />
        <el-table-column
          label="客户合同号"
          prop="customerContractNo"
          show-overflow-tooltip
          width="240"
        />
        <el-table-column
          label="客户名称"
          prop="customerName"
          show-overflow-tooltip
          width="240"
        />
        <el-table-column
          label="项目名称"
          prop="projectName"
          show-overflow-tooltip
          width="340"
        />
        <el-table-column
          label="产品大类"
          prop="productCategory"
          show-overflow-tooltip
          width="100"
        />
        <el-table-column
          label="发票号"
          prop="invoiceNo"
          show-overflow-tooltip
          width="200"
        />
        <el-table-column
          label="发票金额(元)"
          prop="invoiceTotal"
          show-overflow-tooltip
          :formatter="formattedNumber"
          width="200"
        />
        <el-table-column label="税率(%)" prop="taxRate" show-overflow-tooltip />
        <el-table-column
          label="回款金额(元)"
          prop="receiptPaymentAmountTotal"
          show-overflow-tooltip
          :formatter="formattedNumber"
          width="200"
        />
        <el-table-column
          label="待回款金额(元)"
          prop="noReceiptAmount"
          show-overflow-tooltip
          width="200"
        >
          <template #default="{ row, column }">
            <el-text type="danger">
              {{ formattedNumber(row, column, row.noReceiptAmount) }}
            </el-text>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <el-dialog
      v-model="dialogFormVisible"
      title="新增发票号页面"
      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="salesContractNo">
              <el-input
                v-model="form.salesContractNo"
                placeholder="自动填充"
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称:" prop="customerName">
              <el-input
                v-model="form.customerName"
                placeholder="自动填充"
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票号:" prop="invoiceNo">
              <el-input
                v-model="form.invoiceNo"
                placeholder="自动填充"
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="发票金额(元):" prop="invoiceTotal">
              <el-input
                type="number"
                v-model="form.invoiceTotal"
                placeholder="自动填充"
                :step="0.01"
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="税率:" prop="taxRate">
              <el-input
                type="number"
                v-model="form.taxRate"
                placeholder="自动填充"
                :step="0.01"
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="本次回款金额:" prop="receiptPaymentAmount">
              <el-input-number :step="0.01" :min="0" style="width: 100%"
                                                             :precision="2"
                v-model="form.receiptPaymentAmount"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="回款形式:" prop="receiptPaymentType">
              <el-input
                v-model="form.receiptPaymentType"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="登记人:" prop="registrant">
              <el-input
                v-model="form.registrant"
                placeholder="请输入"
                clearable
                disabled
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="来款日期:" prop="receiptPaymentDate">
              <el-date-picker
                style="width: 100%"
                v-model="form.receiptPaymentDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
              />
            </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>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { onMounted, ref } from "vue";
import {
  receiptPaymentSaveOrUpdate,
  bindInvoiceNoRegPage,
  invoiceInfo,
  receiptPaymentHistoryListNoPage,
  receiptPaymentDel,
} from "../../../api/salesManagement/receiptPayment.js";
import useUserStore from "@/store/modules/user";
import { ElMessage, ElMessageBox } from "element-plus";
import useFormData from "@/hooks/useFormData";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const expandedRowKeys = ref([]);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    searchText: "",
    status: true,
    customerName: "",
    customerContractNo: "",
    projectName: "",
  },
  form: {
    salesContractNo: "",
    customerName: "",
    invoiceNo: "",
    invoiceTotal: "",
    taxRate: "",
    receiptPaymentAmount: "",
    receiptPaymentType: "",
    registrant: "",
    receiptPaymentDate: "",
  },
  rules: {
    salesContractNo: [{ required: true, message: "请选择", trigger: "change" }],
    customerName: [{ required: true, message: "请输入", trigger: "blur" }],
    invoiceNo: [{ required: true, message: "请选择", trigger: "change" }],
    invoiceTotal: [{ required: true, message: "请输入", trigger: "blur" }],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
    receiptPaymentAmount: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    receiptPaymentType: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    registrant: [{ required: true, message: "请选择", trigger: "change" }],
    receiptPaymentDate: [
      { required: true, message: "请选择", trigger: "change" },
    ],
  },
});
const { form, rules } = toRefs(data);
const { form: searchForm, resetForm } = useFormData(data.searchForm);
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
const changeDateRange = (date) => {
  if (date) {
    searchForm.invoiceDateStart = date[0];
    searchForm.invoiceDateEnd = date[1];
    getList();
  }
};
const clearRange = () => {
  searchForm.commonDate = [];
  searchForm.invoiceDateStart = undefined;
  searchForm.invoiceDateEnd = undefined;
  getList();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  bindInvoiceNoRegPage({ ...searchForm, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      total.value = res.data.total;
            if (expandedRowKeys.value.length > 0) {
                const arr = []
                const index = tableData.value.findIndex(item => item.id === expandedRowKeys.value[0]);
                if (index > -1) {
                    arr.push(tableData.value[index]);
                    expandChange(tableData.value[index], arr)
                }
            }
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      receiptPaymentHistoryListNoPage({
        invoiceLedgerId: row.id,
        type: 1,
      }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          if (res?.length > 0) {
            res.forEach((item) => {
              item.editType = false;
            });
          }
          tableData.value[index].children = res;
        }
        expandedRowKeys.value.push(row.id);
      });
    } catch (error) {
      console.log(error);
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  console.log("selection", selection);
  selectedRows.value = selection.filter(
    (item) => item.customerContractNo !== null
  );
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(
    param,
    ["invoiceTotal", "receiptPaymentAmountTotal", "noReceiptAmount"],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
  );
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(param, ["receiptPaymentAmount"]);
};
// æ‰“开弹框
const openForm = () => {
  form.value = {};
  if (selectedRows.value.length !== 1) {
    proxy.$modal.msgError("请选择一条数据");
    return;
  }
  if (selectedRows.value[0].noReceiptAmount == 0) {
    proxy.$modal.msgWarning("无需再回款");
    return;
  }
  invoiceInfo({ id: selectedRows.value[0].id }).then((res) => {
    form.value = { ...res.data };
    form.value.invoiceLedgerId = form.value.id;
    form.value.id = "";
    form.value.registrant = userStore.nickName;
  });
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      receiptPaymentSaveOrUpdate(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    }
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// åˆ é™¤å›žæ¬¾è®°å½•
const delReceiptRecord = (row) => {
  console.log("row", row);
  ElMessageBox.confirm("确认删除该记录吗?", "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(async () => {
      try {
        let ids = [];
        ids.push(row.id);
        await receiptPaymentDel(ids);
        ElMessage.success("删除成功");
        getList();
      } catch (error) {
        console.error("删除失败:", error);
        ElMessage.error("删除失败");
      }
    })
    .catch(() => {
      ElMessage.info("已取消删除");
    });
};
// ç¼–辑修改状态
const changeEditType = (row) => {
  row.editType = !row.editType;
};
// ä¿å­˜å›žæ¬¾è®°å½•
const saveReceiptPayment = (row) => {
  let updateData = {
    id: row.id,
    receiptPaymentType: row.receiptPaymentType,
    receiptPaymentAmount: row.receiptPaymentAmount,
  };
  receiptPaymentSaveOrUpdate(updateData).then((res) => {
    row.editType = !row.editType;
        getList();
        proxy.$modal.msgSuccess("提交成功");
  });
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      const ids = selectedRows.value.map((item) => item.id);
      proxy.download(
        `/receiptPayment/export`,
        { ids: `${ids}` },
        "回款登记档案.xlsx"
      );
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.table_list {
  margin-top: unset;
}
::v-deep(.el-checkbox__label) {
  font-weight: bold;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>
src/views/salesManagement/receiptPaymentHistory/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,208 @@
<template>
  <div class="app-container">
    <el-form :model="searchForm" :inline="true">
      <el-form-item label="客户名称">
        <el-input
          v-model="searchForm.searchText"
          placeholder="输入客户名称搜索"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
      </el-form-item>
      <el-form-item label="客户合同号">
        <el-input
          v-model="searchForm.customerContractNo"
          placeholder="输入客户合同号"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
      </el-form-item>
      <el-form-item label="项目名称">
        <el-input
          v-model="searchForm.projectName"
          placeholder="输入项目名称"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
        />
      </el-form-item>
      <el-form-item label="回款日期">
        <el-date-picker
          v-model="searchForm.receiptPaymentDate"
          value-format="YYYY-MM-DD"
          format="YYYY-MM-DD"
          type="daterange"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          clearable
          style="width: 300px"
          @change="changeDateRange"
          @clear="clearRange"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
      </el-form-item>
    </el-form>
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        :isShowSummary="isShowSummarySon"
        :summaryMethod="summarizeMainTable1"
        :tableLoading="tableLoading"
        :total="page.total"
        @pagination="pagination"
        @selection-change="handleSelectionChange"
      ></PIMTable>
    </div>
  </div>
</template>
<script setup>
import { ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { receiptPaymentHistoryListPage } from "@/api/salesManagement/receiptPayment.js";
import useFormData from "@/hooks/useFormData";
import dayjs from "dayjs";
const { proxy } = getCurrentInstance();
const tableColumn = ref([
  {
    label: "销售合同号",
    prop: "salesContractNo",
    width:240
  },
  {
    label: "客户合同号",
    prop: "customerContractNo",
    width:240
  },
  {
    label: "回款日期",
    prop: "receiptPaymentDate",
    width:100
  },
  {
    label: "客户名称",
    prop: "customerName",
    width:240
  },
  {
    label: "项目名称",
    prop: "projectName",
    width:200
  },
  {
    label: "回款金额(元)",
    prop: "receiptPaymentAmount",
    width:200,
    formatData: (params) => {
      return params ? parseFloat(params).toFixed(2) : 0;
    },
  },
  {
    label: "回款方式",
    prop: "receiptPaymentType",
  },
  {
    label: "登记人",
    prop: "registrant",
  },
  {
    label: "登记日期",
    prop: "createTime",
    width:100
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
    total: 0,
});
const total = ref(0);
const { form: searchForm } = useFormData({
  searchText: undefined,
  receiptPaymentDate: [
    dayjs().startOf("month").format("YYYY-MM-DD"),
    dayjs().endOf("month").format("YYYY-MM-DD"),
  ],
  receiptPaymentDateStart: dayjs()
    .startOf("month")
    .format("YYYY-MM-DD 00:00:00"),
  receiptPaymentDateEnd: dayjs().endOf("month").format("YYYY-MM-DD 23:59:59"),
  customerContractNo: undefined,
  projectName: undefined,
});
const { receipt_payment_type } = proxy.useDict("receipt_payment_type");
const isShowSummarySon = ref(true);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { receiptPaymentDate, ...rest } = searchForm;
  receiptPaymentHistoryListPage({ ...rest, ...page }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.records;
        page.total = res.total;
  });
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable1 = (param) => {
  return proxy.summarizeTable(param, ["receiptPaymentAmount"], {
    ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
  });
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const changeDateRange = (date) => {
  if (date) {
    searchForm.receiptPaymentDateStart = dayjs(date[0]).format(
      "YYYY-MM-DD 00:00:00"
    );
    searchForm.receiptPaymentDateEnd = dayjs(date[1]).format(
      "YYYY-MM-DD 23:59:59"
    );
    getList();
  }
};
const clearRange = () => {
  searchForm.receiptPaymentDate = [];
  searchForm.receiptPaymentDateStart = undefined;
  searchForm.receiptPaymentDateEnd = undefined;
  getList();
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.table_list {
  margin-top: unset;
}
</style>
src/views/salesManagement/salesLedger/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1018 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item label="客户合同号:">
          <el-input v-model="searchForm.customerContractNo" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item label="销售合同号:">
          <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item label="项目名称:">
          <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
            @change="handleQuery" />
        </el-form-item>
        <el-form-item label="录入日期:">
          <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
            placeholder="请选择" clearable @change="changeDaterange" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery"> æœç´¢ </el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <div class="actions">
        <div></div>
        <div>
          <el-button type="primary" @click="openForm('add')">
            æ–°å¢žå°è´¦
          </el-button>
          <el-button @click="handleOut">导出</el-button>
          <el-button type="danger" plain @click="handleDelete">删除</el-button>
        </div>
      </div>
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%"
        :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)" stripe>
        <el-table-column align="center" type="selection" 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>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip />
        <el-table-column label="客户合同号" prop="customerContractNo" width="180" show-overflow-tooltip />
        <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip />
        <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip />
        <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip />
        <el-table-column label="付款方式" prop="paymentMethod" show-overflow-tooltip />
        <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip
          :formatter="formattedNumber" />
        <el-table-column label="已开票金额(元)" prop="invoiceTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="未开票金额(元)" prop="noInvoiceAmountTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="回款金额(元)" prop="receiptPaymentAmountTotal" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="待回款金额(元)" prop="noReceiptAmount" width="220" show-overflow-tooltip
                         :formatter="formattedNumber" />
        <el-table-column label="录入人" prop="entryPersonName" width="100" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip />
        <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="140" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" :disabled="scope.row.invoiceTotal>0 || scope.row.entryPersonName !== userStore.nickName" @click="openForm('edit', scope.row)">编辑</el-button>
<!--            <el-button link type="primary" size="small" @click="openForm('view', scope.row)">详情</el-button>-->
            <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">附件</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper"
        :page="page.current" :limit="page.size" @pagination="paginationChange" />
    </div>
    <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="salesContractNo">
              <el-input v-model="form.salesContractNo" placeholder="自动生成" clearable disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="业务员:" prop="salesman">
              <el-input
                v-model="form.salesman"
                placeholder="请输入"
                clearable
                :disabled="operationType === 'view'"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="客户合同号:" prop="customerContractNo">
              <el-input v-model="form.customerContractNo" placeholder="请输入" clearable :disabled="operationType === 'view'"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称:" prop="customerId">
              <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'">
                <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
                  {{
                    item.customerName + "——" + item.taxpayerIdentificationNumber
                  }}
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="签订日期:" prop="executionDate">
                            <el-date-picker style="width: 100%" v-model="form.executionDate" value-format="YYYY-MM-DD"
                                                            format="YYYY-MM-DD" type="date" placeholder="请选择" clearable :disabled="operationType === 'view'" />
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="录入人:" prop="entryPerson">
                            <el-select v-model="form.entryPerson" placeholder="请选择" clearable @change="changs" disabled>
                                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
                            </el-select>
                        </el-form-item>
                    </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="付款方式">
              <el-input v-model="form.paymentMethod" placeholder="请输入" clearable :disabled="operationType === 'view'" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
            <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button>
            <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button>
          </el-form-item>
        </el-row>
        <el-table :data="productData" border @selection-change="productSelected" show-summary stripe
          :summary-method="summarizeMainTable">
          <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" />
          <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 fixed="right" label="操作" min-width="60" align="center" v-if="operationType !== 'view'">
            <template #default="scope">
              <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:" prop="remark">
              <el-input v-model="form.remark" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
              <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
                :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
                :on-success="handleUploadSuccess" :on-remove="handleRemove">
                <el-button type="primary" v-if="operationType !== 'view'">上传</el-button>
                <template #tip v-if="operationType !== 'view'">
                  <div class="el-upload__tip">
                    æ–‡ä»¶æ ¼å¼æ”¯æŒ
                    doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
                  </div>
                </template>
              </el-upload>
            </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 v-model="productFormVisible" :title="productOperationType === 'add' ? '新增产品' : '编辑产品'" width="40%"
      @close="closeProductDia">
      <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品大类:" prop="productCategory">
              <!-- <el-select v-model="productForm.productCategory" placeholder="请选择" clearable>
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/>
              </el-select> -->
              <el-tree-select v-model="productForm.productCategory" placeholder="请选择" clearable check-strictly
                @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="productForm.unit" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="税率(%):" prop="taxRate">
                            <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate">
                                <el-option label="1" value="1" />
                                <el-option label="6" value="6" />
                                <el-option label="13" value="13" />
                            </el-select>
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%"
                                                             :precision="2"
                                                             placeholder="请输入" clearable @change="calculateFromUnitPrice" />
            </el-form-item>
          </el-col>
                    <el-col :span="12">
                        <el-form-item label="数量:" prop="quantity">
                            <el-input-number  :step="0.1" :min="0" v-model="productForm.quantity" placeholder="请输入" clearable
                                                                :precision="2"
                                                                @change="calculateFromQuantity" style="width: 100%" />
                        </el-form-item>
                    </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
              <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromTotalPrice" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice">
              <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="发票类型:" prop="invoiceType">
              <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable>
                <el-option label="增普票" value="增普票" />
                <el-option label="增专票" value="增专票" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import {onMounted, ref} from "vue";
import { ElMessageBox } from "element-plus";
import useUserStore from "@/store/modules/user";
import { userListNoPage } from "@/api/system/user.js";
import FileList from "./fileList.vue";
import {
  ledgerListPage,
  productList,
  customerList,
  addOrUpdateSalesLedger,
  getSalesLedgerWithProducts,
  delLedger,
  addOrUpdateSalesLedgerProduct,
  delProduct,
  delLedgerFile,
} from "@/api/salesManagement/salesLedger.js";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
import dayjs from "dayjs";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
const productSelectedRows = ref([]);
const userList = ref([]);
const customerOption = ref([]);
const productOptions = ref([]);
const modelOptions = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
const fileList = ref([]);
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    customerName: "", // å®¢æˆ·åç§°
    customerContractNo: "", // å®¢æˆ·åˆåŒç¼–号
    salesContractNo: "", // é”€å”®åˆåŒç¼–号
    projectName: "", // é¡¹ç›®åç§°
    entryDate: [
      dayjs().format("YYYY-MM-DD"),
      dayjs().add(1, "day").format("YYYY-MM-DD"),
    ], // å½•入日期
    entryDateStart: dayjs().format("YYYY-MM-DD"),
    entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
  },
  form: {
    salesContractNo: "",
    salesman: "",
    customerContractNo: "",
    customerId: "",
    projectName: "",
    entryPerson: "",
    entryDate: "",
    maintenanceTime: "",
    productData: [],
    executionDate: "",
    paymentMethod: "",
  },
  rules: {
    salesman: [{ required: true, message: "请选择", trigger: "change" }],
    customerContractNo: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    customerId: [{ required: true, message: "请选择", trigger: "change" }],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    entryPerson: [{ required: true, message: "请选择", trigger: "change" }],
    entryDate: [{ required: true, message: "请选择", trigger: "change" }],
    executionDate: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false);
const productOperationType = ref("");
const currentId = ref("");
const productFormData = reactive({
  productForm: {
    productCategory: "",
    specificationModel: "",
    unit: "",
    quantity: "",
    taxInclusiveUnitPrice: "",
    taxRate: "",
    taxInclusiveTotalPrice: "",
    taxExclusiveTotalPrice: "",
    invoiceType: "",
  },
  productRules: {
    productCategory: [{ required: true, message: "请选择", trigger: "change" }],
        productModelId: [{ required: true, message: "请选择", trigger: "change" }],
    specificationModel: [
      { required: true, message: "请选择", trigger: "change" },
    ],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    taxInclusiveUnitPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxExclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { productForm, productRules } = toRefs(productFormData);
// é˜²æ­¢å¾ªçŽ¯è®¡ç®—çš„æ ‡å¿—
const isCalculating = ref(false);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const changeDaterange = (value) => {
  if (value) {
    searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
    searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD");
  } else {
    searchForm.entryDateStart = undefined;
    searchForm.entryDateEnd = undefined;
  }
  handleQuery();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
    expandedRowKeys.value = [];
  getList();
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  const { entryDate, ...rest } = searchForm;
  ledgerListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.records;
      tableData.value.map((item) => {
        item.children = [];
      });
      total.value = res.total;
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  });
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// èŽ·å–tree子数据
const getModels = (value) => {
  productForm.value.productCategory = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  });
};
const getProductModel = (value) => {
  console.log("value", value);
  const index = modelOptions.value.findIndex((item) => item.id === value);
  if (index !== -1) {
    productForm.value.specificationModel = modelOptions.value[index].model;
    productForm.value.unit = modelOptions.value[index].unit;
  } else {
    productForm.value.specificationModel = null;
    productForm.value.unit = null;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode; // åœ¨å­èŠ‚ç‚¹ä¸­æ‰¾åˆ°ï¼Œè¿”å›žè¯¥èŠ‚ç‚¹
      }
    }
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
    };
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter((item) => item.children !== undefined);
  console.log("selection", selectedRows.value);
};
const productSelected = (selectedRows) => {
  productSelectedRows.value = selectedRows;
};
const expandedRowKeys = ref([]);
// å±•开行
const expandChange = (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      productList({ salesLedgerId: row.id, type: 1 }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res.data;
        }
        expandedRowKeys.value.push(row.id);
      });
    } catch (error) {
      console.log(error);
    }
  } else {
    expandedRowKeys.value = [];
  }
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  return proxy.summarizeTable(param, [
    "contractAmount",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
        'invoiceTotal',
        'noInvoiceAmountTotal',
        'receiptPaymentAmountTotal',
        'noReceiptAmount',
  ]);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
};
// æ‰“开弹框
const openForm = async (type, row) => {
  operationType.value = type;
  form.value = {};
  productData.value = [];
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  customerList().then((res) => {
    customerOption.value = res;
  });
  form.value.entryPerson = userStore.id;
  if (type !== "add") {
    currentId.value = row.id;
    getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
      form.value = { ...res };
      form.value.entryPerson = Number(res.entryPerson);
      productData.value = form.value.productData;
      fileList.value = form.value.salesLedgerFiles;
    });
  }
  // let userAll = await userStore.getInfo()
  // userList.value.forEach(element => {
  //   if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) {
  //     form.value.entryPerson = userAll.user.userId // è®¾ç½®é»˜è®¤ä¸šåŠ¡å‘˜ä¸ºå½“å‰ç”¨æˆ·
  //   }
  // });
  form.value.entryDate = getCurrentDate(); // è®¾ç½®é»˜è®¤å½•入日期为当前日期
  dialogFormVisible.value = true;
};
function changs(val) {
  console.log(val);
}
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  // if (file.size > 1024 * 1024 * 10) {
  //   proxy.$modal.msgError("上传文件大小不能超过10MB!");
  //   return false;
  // }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    file.tempId = res.data.tempId;
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
function handleRemove(file) {
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
    });
  }
}
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
            console.log('productData.value--', productData.value)
      if (productData.value !== null && productData.value.length > 0) {
        form.value.productData = proxy.HaveJson(productData.value);
      } else {
        proxy.$modal.msgWarning("请添加产品信息");
        return;
      }
      let tempFileIds = [];
      if (fileList.value !== null && fileList.value.length > 0) {
        tempFileIds = fileList.value.map((item) => item.tempId);
      }
      form.value.tempFileIds = tempFileIds;
      form.value.type = 1;
      addOrUpdateSalesLedger(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
      });
    }
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
const productIndex = ref(0);
// æ‰“开产品弹框
const openProductForm = (type, row,index) => {
  productOperationType.value = type;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  if (type === "edit") {
    productForm.value = { ...row };
    productIndex.value = index;
  }
  productFormVisible.value = true;
  getProductOptions();
};
// æäº¤äº§å“è¡¨å•
const submitProduct = () => {
  proxy.$refs["productFormRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        submitProductEdit();
      } else {
        if(productOperationType.value === "add"){
          productData.value.push({ ...productForm.value });
        }else{
          productData.value[productIndex.value] = { ...productForm.value }
        }
        closeProductDia();
      }
    }
  });
};
const submitProductEdit = () => {
  productForm.value.salesLedgerId = currentId.value;
  productForm.value.type = 1
  addOrUpdateSalesLedgerProduct(productForm.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeProductDia();
    getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => {
      productData.value = res.productData;
    });
  });
};
// åˆ é™¤äº§å“
const deleteProduct = () => {
  if (productSelectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  if (operationType.value === "add") {
    productSelectedRows.value.forEach((selectedRow) => {
      const index = productData.value.findIndex(
        (product) => product.id === selectedRow.id
      );
      if (index !== -1) {
        productData.value.splice(index, 1);
      }
    });
  } else {
    let ids = [];
    if (productSelectedRows.value.length > 0) {
      ids = productSelectedRows.value.map((item) => item.id);
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        delProduct(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          closeProductDia();
          getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then(
            (res) => {
              productData.value = res.productData;
            }
          );
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  }
};
// å…³é—­äº§å“å¼¹æ¡†
const closeProductDia = () => {
  proxy.resetForm("productFormRef");
  productFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      proxy.download("/sales/ledger/export", {}, "销售台账.xlsx");
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      delLedger(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .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}`;
}
const mathNum = () => {
  console.log("productForm.value", productForm.value);
  if (!productForm.value.taxInclusiveUnitPrice) {
    return;
  }
  if (!productForm.value.quantity) {
    return;
  }
  // å«ç¨Žæ€»ä»·è®¡ç®—
  productForm.value.taxInclusiveTotalPrice =
    proxy.calculateTaxIncludeTotalPrice(
      productForm.value.taxInclusiveUnitPrice,
      productForm.value.quantity
    );
  if (productForm.value.taxRate) {
    // ä¸å«ç¨Žæ€»ä»·è®¡ç®—
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        productForm.value.taxInclusiveTotalPrice,
        productForm.value.taxRate
      );
  }
};
// æ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const calculateFromTotalPrice = () => {
  if (isCalculating.value) return;
  const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
  const quantity = parseFloat(productForm.value.quantity);
  if (!totalPrice || !quantity || quantity <= 0) {
    return;
  }
  isCalculating.value = true;
  // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
  productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  if (productForm.value.taxRate) {
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        totalPrice,
        productForm.value.taxRate
      );
  }
  isCalculating.value = false;
};
// æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const calculateFromExclusiveTotalPrice = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (isCalculating.value) return;
  const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice);
  const quantity = parseFloat(productForm.value.quantity);
  const taxRate = parseFloat(productForm.value.taxRate);
  if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
    return;
  }
  isCalculating.value = true;
  // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (1 - ç¨Žçއ/100)
  const taxRateDecimal = taxRate / 100;
  const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
  productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
  // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
  productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
  isCalculating.value = false;
};
// æ ¹æ®æ•°é‡å˜åŒ–计算总价
const calculateFromQuantity = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (isCalculating.value) return;
  const quantity = parseFloat(productForm.value.quantity);
  const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
  if (!quantity || quantity <= 0 || !unitPrice) {
    return;
  }
  isCalculating.value = true;
  // è®¡ç®—含税总价
  productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  if (productForm.value.taxRate) {
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        productForm.value.taxInclusiveTotalPrice,
        productForm.value.taxRate
      );
  }
  isCalculating.value = false;
};
// æ ¹æ®å«ç¨Žå•价变化计算总价
const calculateFromUnitPrice = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (isCalculating.value) return;
  const quantity = parseFloat(productForm.value.quantity);
  const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice);
  if (!quantity || quantity <= 0 || !unitPrice) {
    return;
  }
  isCalculating.value = true;
  // è®¡ç®—含税总价
  productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
  // å¦‚果有税率,计算不含税总价
  if (productForm.value.taxRate) {
    productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxExclusiveTotalPrice(
        productForm.value.taxInclusiveTotalPrice,
        productForm.value.taxRate
      );
  }
  isCalculating.value = false;
};
// æ ¹æ®ç¨ŽçŽ‡å˜åŒ–è®¡ç®—ä¸å«ç¨Žæ€»ä»·
const calculateFromTaxRate = () => {
    if (!productForm.value.taxRate) {
        proxy.$modal.msgWarning("请先选择税率");
        return;
    }
  if (isCalculating.value) return;
  const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice);
  const taxRate = parseFloat(productForm.value.taxRate);
  if (!inclusiveTotalPrice || !taxRate) {
    return;
  }
  isCalculating.value = true;
  // è®¡ç®—不含税总价
  productForm.value.taxExclusiveTotalPrice =
    proxy.calculateTaxExclusiveTotalPrice(
      inclusiveTotalPrice,
      taxRate
    );
  isCalculating.value = false;
};
/**
 * ä¸‹è½½æ–‡ä»¶
 *
 * @param row ä¸‹è½½æ–‡ä»¶çš„相关信息对象
 */
const fileListRef = ref(null)
const downLoadFile = (row) => {
  getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => {
    fileListRef.value.open(res.salesLedgerFiles)
  });
}
onMounted(() => {
    getList();
});
</script>
<style scoped lang="scss">
.ml-10 {
  margin-left: 10px;
}
.table_list {
  margin-top: unset;
}
.actions {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>