zouyu
2 天以前 3443eef779d9fa60ded99ad12a72e2710a3c8f3f
Merge branch 'dev_tide' into dev_tide_sbjkxt

# Conflicts:
# .env.production
# index.html
# src/store/modules/user.js
# src/utils/auth.js
# 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/financialManagement/revenueManagement/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
已添加12个文件
已修改6个文件
3693 ■■■■■ 文件已修改
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/financialManagement/accounting/index.vue 547 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/inventoryAccounting/index.vue 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/index.vue 309 ●●●●● 补丁 | 查看 | 原始文档 | 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/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/stockReport/index.vue 713 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/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/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/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/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/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/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>