zhangwencui
19 小时以前 1156fbe1fa77e4a6b7d890604d25e98edf8a7059
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New
已修改17个文件
526 ■■■■ 文件已修改
src/api/viewIndex.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementInvoiceLedger/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionReporting/components/formDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPayment/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/viewIndex.js
@@ -114,3 +114,44 @@
        method: 'get'
    })
}
// 产品销售金额分析
export const productSalesAnalysis = () => {
    return request({
        url: '/home/productSalesAnalysis',
        method: 'get'
    })
}
// 原材料采购金额占比
export const rawMaterialPurchaseAmountRatio = () => {
    return request({
        url: '/home/rawMaterialPurchaseAmountRatio',
        method: 'get'
    })
}
// 销售/采购/储存产品数
export const salesPurchaseStorageProductCount = () => {
    return request({
        url: '/home/salesPurchaseStorageProductCount',
        method: 'get'
    })
}
// 产品出入库分析(可传 productType: 1 原材料 2 半成品 3 成品)
export const productInOutAnalysis = (params) => {
    return request({
        url: '/home/productInOutAnalysis',
        method: 'get',
        params
    })
}
// 产品周转天数
export const productTurnoverDays = () => {
    return request({
        url: '/home/productTurnoverDays',
        method: 'get'
    })
}
src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
@@ -34,10 +34,14 @@
          <el-form-item label="来票数:">
            <el-input-number :step="0.1"
                             :min="0"
                             :max="maxTicketsNum"
                             style="width: 100%"
                             v-model="form.ticketsNum"
                             @change="inputTicketsNum"
                             :precision="2" />
            <div style="font-size: 12px; color: #909399; margin-top: 4px;">
              可填写数量:{{ maxTicketsNum }}
            </div>
          </el-form-item>
        </el-col>
        <el-col :span="12">
@@ -73,7 +77,7 @@
import useFormData from "@/hooks/useFormData";
import { updateRegistration, getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
import { ElMessage } from "element-plus";
import { getCurrentInstance, ref, nextTick } from "vue";
import { getCurrentInstance, ref, nextTick, computed } from "vue";
defineOptions({
  name: "来票台账编辑",
@@ -82,7 +86,13 @@
const saleLedgerProjectId = ref("");
const temFutureTickets = ref(0);
const originalTicketsNum = ref(0); // 原始已来票数
const { proxy } = getCurrentInstance();
// 计算最大可填写数量 = 原始已来票数 + 未来票数
const maxTicketsNum = computed(() => {
  return Number(originalTicketsNum.value) + Number(temFutureTickets.value);
});
const {
  id,
@@ -123,6 +133,8 @@
    form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
    form.futureTickets = data.futureTickets;
    temFutureTickets.value = data.futureTickets;
    // 保存原始已来票数
    originalTicketsNum.value = data.ticketsNum || 0;
  }
};
@@ -132,16 +144,30 @@
    proxy.$modal.msgWarning("含税单价不能为零或未定义");
    return;
  }
  if (Number(form.ticketsNum) > Number(temFutureTickets.value)) {
    proxy.$modal.msgWarning("开票数不得大于未开票数");
    form.ticketsNum = temFutureTickets.value;
  // 检查来票数不能大于(原始已来票数 + 未来票数)
  const maxNum = maxTicketsNum.value;
  if (Number(form.ticketsNum) > maxNum) {
    proxy.$modal.msgWarning(`来票数不能大于${maxNum}(已来票数${originalTicketsNum.value} + 未来票数${temFutureTickets.value})`);
    form.ticketsNum = maxNum;
    return;
  }
  // 计算本次新增的来票数(当前来票数 - 原始已来票数)
  const newTicketsNum = Number(form.ticketsNum) - Number(originalTicketsNum.value);
  // 如果新增的来票数大于未来票数,则限制
  if (newTicketsNum > Number(temFutureTickets.value)) {
    proxy.$modal.msgWarning("本次新增来票数不得大于未来票数");
    form.ticketsNum = Number(originalTicketsNum.value) + Number(temFutureTickets.value);
    return;
  }
  // 确保所有数值都转换为数字类型进行计算
  const ticketsAmount =
    Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
  const futureTickets =
    Number(temFutureTickets.value) - Number(form.ticketsNum);
    Number(temFutureTickets.value) - newTicketsNum;
  form.futureTickets = Number(futureTickets.toFixed(2));
  form.ticketsAmount = Number(ticketsAmount.toFixed(2));
};
@@ -153,20 +179,33 @@
    return;
  }
  if (Number(val) > Number(form.futureTickets * form.taxInclusiveUnitPrice)) {
    proxy.$modal.msgWarning("本次来票金额不得大于总金额");
    form.ticketsAmount = (
      form.futureTickets * form.taxInclusiveUnitPrice
    ).toFixed(2);
    const ticketsNum =
      Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice);
    form.ticketsNum = Number(ticketsNum.toFixed(2));
  // 计算最大可填写金额 = (原始已来票数 + 未来票数)* 含税单价
  const maxAmount = maxTicketsNum.value * Number(form.taxInclusiveUnitPrice);
  if (Number(val) > maxAmount) {
    proxy.$modal.msgWarning(`本次来票金额不得大于${maxAmount.toFixed(2)}元`);
    form.ticketsAmount = maxAmount.toFixed(2);
    form.ticketsNum = maxTicketsNum.value;
    return;
  }
  // 确保所有数值都转换为数字类型进行计算
  const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice);
  // 检查来票数不能大于最大值
  if (ticketsNum > maxTicketsNum.value) {
    proxy.$modal.msgWarning(`来票数不能大于${maxTicketsNum.value}`);
    form.ticketsNum = maxTicketsNum.value;
    form.ticketsAmount = maxAmount.toFixed(2);
    return;
  }
  form.ticketsNum = Number(ticketsNum.toFixed(2));
  // 计算未来票数
  const newTicketsNum = form.ticketsNum - originalTicketsNum.value;
  const futureTickets = Number(temFutureTickets.value) - newTicketsNum;
  form.futureTickets = Number(futureTickets.toFixed(2));
};
const open = async row => {
src/views/procurementManagement/procurementInvoiceLedger/index.vue
@@ -62,16 +62,16 @@
          <el-button
            type="primary"
            link
            @click="downLoadFile(row)"
            @click="openEdit(row)"
          >
            附件
            编辑
          </el-button>
          <el-button
            type="primary"
            link
            @click="openEdit(row)"
            @click="downLoadFile(row)"
          >
            编辑
            附件
          </el-button>
          <el-button
            type="primary"
@@ -166,6 +166,11 @@
      width: 240,
    },
    {
      label: "产品大类",
      prop: "productCategory",
      width: 150,
    },
    {
      label: "规格型号",
      prop: "specificationModel",
      width: 150,
src/views/productionManagement/productionReporting/components/formDia.vue
@@ -94,7 +94,7 @@
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
// import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
const { proxy } = getCurrentInstance()
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -18,13 +18,17 @@
                  @change="getModels"
                  :data="productOptions"
                  :render-after-expand="false"
                  :disabled="operationType === 'edit'"
                  style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="model">
              <el-input v-model="form.model" placeholder="请输入" clearable/>
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="form.productModelId" placeholder="请选择" clearable :disabled="operationType === 'edit'"
                         filterable readonly @change="handleChangeModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -49,7 +53,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="form.unit" placeholder="请输入" clearable/>
              <el-input v-model="form.unit" placeholder="请输入" disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -121,7 +125,7 @@
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {userListNoPage} from "@/api/system/user.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
@@ -138,6 +142,7 @@
    checkName: "",
    productName: "",
    productId: "",
    productModelId: "",
    model: "",
    testStandardId: "",
    unit: "",
@@ -150,7 +155,7 @@
    process: [{ required: true, message: "请输入", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    productModelId: [{ required: false, message: "请选择", trigger: "change" }],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -190,6 +195,7 @@
const userList = ref([]);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // 指标选择下拉框数据
const modelOptions = ref([]);
// 打开弹框
const openDialog = async (type, row) => {
@@ -257,12 +263,24 @@
  });
};
const getModels = (value) => {
  form.value.productModelId = undefined;
  form.value.unit = undefined;
  modelOptions.value = [];
    currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  })
    if (currentProductId.value) {
        getList();
    }
};
const handleChangeModel = (value) => {
  form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -23,6 +23,7 @@
                  @change="getModels"
                  :data="productOptions"
                  :render-after-expand="false"
                  :disabled="operationType === 'edit'"
                  style="width: 100%"
              />
            </el-form-item>
@@ -30,8 +31,11 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="model">
              <el-input v-model="form.model" placeholder="请输入" clearable/>
            <el-form-item label="规格型号:" prop="productModelId">
              <el-select v-model="form.productModelId" placeholder="请选择" clearable :disabled="operationType === 'edit'"
                         filterable readonly @change="handleChangeModel">
                <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -56,7 +60,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input v-model="form.unit" placeholder="请输入" clearable/>
              <el-input v-model="form.unit" placeholder="请输入" disabled/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -128,7 +132,7 @@
<script setup>
import {ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js";
import {userListNoPage} from "@/api/system/user.js";
@@ -145,6 +149,7 @@
    checkName: "",
    productName: "",
    productId: "",
    productModelId: "",
    model: "",
    testStandardId: "",
    unit: "",
@@ -157,7 +162,7 @@
    process: [{ required: true, message: "请输入工序", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    productModelId: [{ required: false, message: "请选择", trigger: "change" }],
    testStandardId: [{required: false, message: "请选择指标", trigger: "change"}],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -197,6 +202,7 @@
const tableLoading = ref(false);
const currentProductId = ref(0);
const testStandardOptions = ref([]); // 指标选择下拉框数据
const modelOptions = ref([]);
// 打开弹框
const openDialog = async (type, row) => {
@@ -265,12 +271,24 @@
  });
};
const getModels = (value) => {
  form.value.productModelId = undefined;
  form.value.unit = undefined;
  modelOptions.value = [];
    currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  })
    if (currentProductId.value) {
        getList();
    }
};
const handleChangeModel = (value) => {
  form.value.model = modelOptions.value.find(item => item.id == value)?.model || '';
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -281,6 +281,7 @@
};
const getModels = (value) => {
  form.value.productModelId = undefined;
  form.value.unit = undefined;
  modelOptions.value = [];
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue
@@ -5,8 +5,8 @@
    @change="handleChange"
  >
    <el-radio-button :label="1">原材料</el-radio-button>
    <el-radio-button :label="2">半成品</el-radio-button>
    <el-radio-button :label="3">成品</el-radio-button>
    <el-radio-button :label="3">半成品</el-radio-button>
    <el-radio-button :label="2">成品</el-radio-button>
  </el-radio-group>
</template>
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -28,6 +28,7 @@
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
import ProductTypeSwitch from './ProductTypeSwitch.vue'
import { productInOutAnalysis } from '@/api/viewIndex.js'
const productType = ref(1) // 1=原材料 2=半成品 3=成品
@@ -58,7 +59,7 @@
const xAxis1 = ref([
  {
    type: 'category',
    data: ['6/9', '6/10', '6/11', '6/12', '6/13', '6/14', '6/15'],
    data: [],
    axisTick: { show: false },
    axisLine: { show: false,lineStyle: { color: 'rgba(184, 200, 224, 0.3)' } },
    axisLabel: { color: '#B8C8E0', fontSize: 12 },
@@ -86,7 +87,7 @@
    showSymbol: true,
    symbol: 'circle',
    symbolSize: 8,
    lineStyle: { color: 'rgba(11, 137, 254,1', width: 2 },
    lineStyle: { color: 'rgba(11, 137, 254, 1)', width: 2 },
    itemStyle: { color: 'rgba(11, 137, 254, 1)', borderWidth: 0 },
    areaStyle: {
      color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
@@ -94,7 +95,7 @@
        { offset: 1, color: 'rgba(11, 137, 254, 0.05)' },
      ]),
    },
    data: [80, 100, 140, 160, 120, 150, 180],
    data: [],
    emphasis: { focus: 'series' },
  },
  {
@@ -104,7 +105,6 @@
    showSymbol: true,
    symbol: 'circle',
    symbolSize: 8,
    lineStyle: { color: 'rgba(11, 249, 254, 1)', width: 2 },
    itemStyle: { color: 'rgba(11, 249, 254, 1)', borderWidth: 0 },
    areaStyle: {
@@ -113,7 +113,7 @@
        { offset: 1, color: 'rgba(11, 249, 254, 0.05)' },
      ]),
    },
    data: [160, 200, 200, 200, 170, 200, 200],
    data: [],
    emphasis: { focus: 'series' },
  },
])
@@ -132,11 +132,28 @@
  },
}
const handleFilterChange = () => {
  // 可按 productType 切换后请求出入库接口,此处仅预留
const fetchData = () => {
  productInOutAnalysis({ type: productType.value })
    .then((res) => {
      if (res.code === 200 && Array.isArray(res.data)) {
        const list = res.data
        xAxis1.value[0].data = list.map((d) => d.date)
        lineSeries.value[0].data = list.map((d) => Number(d.outCount) || 0)
        lineSeries.value[1].data = list.map((d) => Number(d.inCount) || 0)
      }
    })
    .catch((err) => {
      console.error('获取产品出入库分析失败:', err)
    })
}
onMounted(() => {})
const handleFilterChange = () => {
  fetchData()
}
onMounted(() => {
  fetchData()
})
</script>
<style scoped>
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -29,19 +29,14 @@
<script setup>
import { ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import { customerRevenueAnalysis } from '@/api/viewIndex.js'
import { listCustomer } from '@/api/basicData/customerFile.js'
const dateType = ref(1)
const customerValue = ref(null)
const customerOptions = ref([])
import { productTurnoverDays } from '@/api/viewIndex.js'
const chartStyle = { width: '100%', height: '100%' }
const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true }
const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['营收'] }
const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['周转天数'] }
const barSeries1 = ref([
  {
    name: '营收',
    name: '周转天数',
    type: 'bar',
    barGap: 0,
    barWidth: 30,
@@ -65,7 +60,7 @@
  formatter(params) {
    let result = params[0].axisValueLabel + '<br/>'
    params.forEach((item) => {
      result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>`
      result += `<div>${item.marker} ${item.seriesName}: ${item.value} 天</div>`
    })
    return result
  },
@@ -73,47 +68,22 @@
const xAxis1 = ref([{ type: 'category', axisTick: { show: false }, axisLabel: { color: '#B8C8E0' }, data: [] }])
const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }]
const getCustomerRevenueAnalysis = () => {
  if (customerOptions.value.length > 0 && !customerValue.value) customerValue.value = customerOptions.value[0].value
  if (!customerValue.value) return
  customerRevenueAnalysis({ customerId: customerValue.value, type: dateType.value })
const fetchData = () => {
  productTurnoverDays()
    .then((res) => {
      xAxis1.value[0].data = []
      barSeries1.value[0].data = []
      const items = res.data?.items || []
      items.forEach((item) => {
        xAxis1.value[0].data.push(item.name)
        barSeries1.value[0].data.push(item.value)
      if (res.code === 200 && Array.isArray(res.data)) {
        const list = res.data
        xAxis1.value[0].data = list.map((d) => d.name)
        barSeries1.value[0].data = list.map((d) => Number(d.value) || 0)
      }
      })
    .catch((err) => {
      console.error('获取产品周转天数失败:', err)
    })
    .catch((e) => console.error('获取客户营收分析失败:', e))
}
const fetchCustomerOptions = async () => {
  try {
    const res = await listCustomer({ pageNum: 1, pageSize: 200 })
    const records = res?.records || res?.data?.records || res?.rows || []
    customerOptions.value = records.map((r) => ({
      label: r.customerName || r.name || r.customer || '-',
      value: r.id ?? r.customerId ?? r.customerCode ?? r.customerName,
    }))
    if (customerOptions.value.length > 0 && !customerValue.value) {
      customerValue.value = customerOptions.value[0].value
      getCustomerRevenueAnalysis()
    }
  } catch (e) {
    customerOptions.value = [
      { label: '华东精密', value: '华东精密' },
      { label: '星辰电子', value: '星辰电子' },
      { label: '启航科技', value: '启航科技' },
      { label: '铭诚制造', value: '铭诚制造' },
      { label: '远景材料', value: '远景材料' },
    ]
  }
}
onMounted(() => {
  fetchCustomerOptions()
  fetchData()
})
</script>
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -2,39 +2,19 @@
  <div>
    <!-- 顶部统计卡片 -->
    <div class="stats-cards">
      <div class="stat-card">
      <div
        v-for="item in statItems"
        :key="item.name"
        class="stat-card"
      >
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">销售产品数</span>
          <span class="card-value">{{ totalStaff }}</span>
          <div class="card-compare" :class="compareClass(staffYoY)">
          <span class="card-label">{{ item.name }}</span>
          <span class="card-value">{{ item.value }}</span>
          <div class="card-compare" :class="compareClass(Number(item.rate))">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(staffYoY) }}</span>
            <span class="compare-icon">{{ staffYoY >= 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">采购产品数</span>
          <span class="card-value">{{ totalCustomers }}</span>
          <div class="card-compare" :class="compareClass(customersYoY)">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(customersYoY) }}</span>
            <span class="compare-icon">{{ customersYoY >= 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
      <div class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
        <div class="card-content">
          <span class="card-label">库存数</span>
          <span class="card-value">{{ totalSuppliers }}</span>
          <div class="card-compare" :class="compareClass(suppliersYoY)">
            <span>同比</span>
            <span class="compare-value">{{ formatPercent(suppliersYoY) }}</span>
            <span class="compare-icon">{{ suppliersYoY >= 0 ? '↑' : '↓' }}</span>
            <span class="compare-value">{{ formatPercent(item.rate) }}</span>
            <span class="compare-icon">{{ Number(item.rate) >= 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
      </div>
@@ -45,40 +25,35 @@
<script setup>
import { ref, onMounted } from 'vue'
import { summaryStatistics } from '@/api/viewIndex.js'
import { salesPurchaseStorageProductCount } from '@/api/viewIndex.js'
// 统计数据
const totalStaff = ref(0)
const totalCustomers = ref(0)
const totalSuppliers = ref(0)
// 同比
const staffYoY = ref(0)
const customersYoY = ref(0)
const suppliersYoY = ref(0)
const statItems = ref([])
const formatPercent = (val) => {
  const num = Number(val) || 0
  return `${Math.abs(num).toFixed(2)}%`
  return `${num.toFixed(2)}%`
}
const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down')
// 获取员工、客户、供应商数量
const getNum = () => {
  summaryStatistics().then((res) => {
    totalStaff.value = res.data.totalStaff
    staffYoY.value = res.data.staffGrowthRate
    totalCustomers.value = res.data.totalCustomer
    customersYoY.value = res.data.customerGrowthRate
    totalSuppliers.value = res.data.totalSupplier
    suppliersYoY.value = res.data.supplierGrowthRate
  }).catch(err => {
    console.error('获取基础统计数据失败:', err)
const fetchData = () => {
  salesPurchaseStorageProductCount()
    .then((res) => {
      if (res.code === 200 && Array.isArray(res.data)) {
        statItems.value = res.data.map((item) => ({
          name: item.name,
          value: item.value,
          rate: item.rate,
        }))
      }
    })
    .catch((err) => {
      console.error('获取销售/采购/储存产品数失败:', err)
  })
}
onMounted(() => {
  getNum()
  fetchData()
})
</script>
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -26,7 +26,7 @@
import Echarts from '@/components/Echarts/echarts.vue'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
import { productCategoryDistribution } from '@/api/viewIndex.js'
import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js'
/**
 * @introduction 把数组中key值相同的那一项提取出来,组成一个对象
@@ -48,16 +48,6 @@
// 卡片数据
const cardItems = ref([])
// 假数据
const mockCardData = [
  { name: '电子产品', value: 156, rate: '28.5' },
  { name: '机械设备', value: 132, rate: '24.1' },
  { name: '原材料', value: 98, rate: '17.9' },
  { name: '化工产品', value: 87, rate: '15.9' },
  { name: '纺织品', value: 45, rate: '8.2' },
  { name: '其他', value: 31, rate: '5.7' },
]
// 颜色列表
const landColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF']
@@ -77,13 +67,13 @@
  return {
    orient: 'vertical',
    top: 'center',
    left: '60%',
    left: '52%',
    itemGap: 30,
    data: data,
    formatter: function (name) {
      const item = landObjData.value[name]
      if (!item) return name
      return `{title|${name}}{value|${item.value}}{unit|件}{percent|${item.rate}}{unit|%}`
      return `{title|${name}}{value|${item.value}}{unit|元}{percent|${item.rate}}{unit|%}`
    },
    textStyle: {
      rich: {
@@ -91,7 +81,7 @@
          color: '#43e8fc',
          fontSize: 14,
          fontWeight: 600,
          padding: [0, 0, 0, 30],
          padding: [0, 0, 0, 10],
        },
        unit: {
          color: '#82baff',
@@ -117,7 +107,7 @@
// 提示框
const landTooltip = {
  trigger: 'item',
  formatter: '{a} <br/>{b} : {c} ({d}%)',
  formatter: '{a} <br/>{b} : {c}元 ({d}%)',
}
// 双层环形饼图
@@ -174,52 +164,33 @@
  textStyle: { color: '#B8C8E0' },
}
const setMockData = () => {
  // 卡片数据
  cardItems.value = mockCardData.map(item => ({
const fetchData = () => {
  rawMaterialPurchaseAmountRatio()
    .then((res) => {
      if (res.code === 200 && Array.isArray(res.data)) {
        const items = res.data
        cardItems.value = items.map((item) => ({
    label: item.name,
    value: item.value,
    unit: '件',
    rate: item.rate
          unit: '元',
          rate: item.rate,
  }))
  // 图表数据
  dataList.value = mockCardData.map((it) => ({
        dataList.value = items.map((it) => ({
    name: it.name,
    value: Number(it.value || 0),
          value: parseFloat(it.value) || 0,
    rate: it.rate,
    children: [],
  }))
  landSeries.value[0].data = dataList.value
}
const loadData = async () => {
  setMockData()
  // try {
  //   const res = await productCategoryDistribution()
  //   const items = res?.data?.items || []
  //   dataList.value = items.map((it) => ({
  //     name: it.name,
  //     value: Number(it.value || 0),
  //     rate: it.rate,
  //     children: Array.isArray(it.children) ? it.children : [],
  //   }))
  //   // 卡片数据
  //   cardItems.value = items.map(item => ({
  //     label: item.name,
  //     value: parseInt(item.value),
  //     unit: '件',
  //     rate: item.rate
  //   }))
  //   landLegend.data = dataList.value.map((d) => d.name)
  //   landSeries.value[0].data = dataList.value
  // } catch (e) {
  //   console.error('获取产品大类分布失败:', e)
  //   setMockData()
  // }
    })
    .catch((err) => {
      console.error('获取原材料采购金额占比失败:', err)
    })
}
onMounted(() => {
  loadData()
  fetchData()
})
</script>
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -22,7 +22,7 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { deptStaffDistribution } from '@/api/viewIndex.js'
import { productSalesAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
import CarouselCards from './CarouselCards.vue'
import Echarts from '@/components/Echarts/echarts.vue'
@@ -65,13 +65,13 @@
  return {
    orient: 'vertical',
    top: 'center',
    left: '60%',
    left: '52%',
    itemGap: 30,
    data: data,
    formatter: function (name) {
      const item = pieObjData.value[name]
      if (!item) return name
      return `{title|${name}}{value|${item.value}}{unit|人}{percent|${item.rate}}{unit|%}`
      return `{title|${name}}{value|${item.value}}{unit|元}{percent|${item.rate}}{unit|%}`
    },
    textStyle: {
      rich: {
@@ -79,7 +79,7 @@
          color: '#43e8fc',
          fontSize: 14,
          fontWeight: 600,
          padding: [0, 0, 0, 30],
          padding: [0, 0, 0, 10],
        },
        unit: {
          color: '#82baff',
@@ -104,7 +104,7 @@
const pieTooltip = {
  trigger: 'item',
  formatter: '{a} <br/>{b} : {c} ({d}%)',
  formatter: '{a} <br/>{b} : {c}元 ({d}%)',
}
const pieSeries = computed(() => [
@@ -137,61 +137,31 @@
const cardItems = ref([])
// 假数据
const mockData = [
  { name: '生产部', value: 125, rate: '35.2' },
  { name: '技术部', value: 85, rate: '23.9' },
  { name: '销售部', value: 65, rate: '18.3' },
  { name: '财务部', value: 32, rate: '9.0' },
  { name: '人事部', value: 28, rate: '7.9' },
  { name: '行政部', value: 20, rate: '5.6' },
]
const getDeptStaffDistribution = () => {
  setMockData()
  // deptStaffDistribution().then(res => {
  //   if (res.code === 200) {
  //     const items = res.data.items || []
  //     // 卡片数据
  //     cardItems.value = items.map(item => ({
  //       label: item.name,
  //       value: parseInt(item.value),
  //       unit: '人'
  //     }))
  //     // 图表数据
  //     pieDatas.value = items.map(item => ({
  //       name: item.name,
  //       value: parseInt(item.value),
  //       rate: item.rate
  //     }))
  //   } else {
  //     // 使用假数据
  //     setMockData()
  //   }
  // }).catch(err => {
  //   console.error('获取部门人员分布数据失败:', err)
  //   // 使用假数据
  //   setMockData()
  // })
}
const setMockData = () => {
  // 卡片数据
  cardItems.value = mockData.map(item => ({
const fetchData = () => {
  productSalesAnalysis()
    .then((res) => {
      if (res.code === 200 && Array.isArray(res.data)) {
        const items = res.data
        cardItems.value = items.map((item) => ({
    label: item.name,
    value: item.value,
    unit: '人'
          unit: '元',
          rate: item.rate,
  }))
  // 图表数据
  pieDatas.value = mockData.map(item => ({
        pieDatas.value = items.map((item) => ({
    name: item.name,
    value: item.value,
    rate: item.rate
          value: parseFloat(item.value) || 0,
          rate: item.rate,
  }))
      }
    })
    .catch((err) => {
      console.error('获取产品销售金额分析失败:', err)
    })
}
onMounted(() => {
  getDeptStaffDistribution()
  fetchData()
})
</script>
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
@@ -371,6 +371,7 @@
  border: 1px solid #1a58b0;
  padding: 18px;
  height: 240px;
  padding-top: 0px;
}
.equipment-header {
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -104,7 +104,7 @@
  formatter: function (params) {
    let result = params[0].axisValueLabel + '<br/>'
    params.forEach((item) => {
      result += `<div>${item.marker} ${item.seriesName}: ${item.value}</div>`
      result += `<div>${item.marker} ${item.seriesName}: ${item.value}元</div>`
    })
    return result
  },
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -120,7 +120,7 @@
    textStyle: { fontSize: '100%' },
    formatter: function (params) {
      let result = params[0].axisValueLabel + '<br/>'
      result += `<div>${params[0].marker}${params[0].value}</div>`
      result += `<div>${params[0].marker}${params[0].value}元</div>`
      return result
    },
  }
src/views/salesManagement/receiptPayment/index.vue
@@ -433,7 +433,8 @@
    specificationModel: row.specificationModel || "",
    pendingInvoiceTotal: Number(row.pendingInvoiceTotal || 0),
    taxRate: row.taxRate ?? "",
    receiptPaymentAmount: "",
    // 默认本次回款金额 = 待回款金额
    receiptPaymentAmount: Number(row.pendingInvoiceTotal || 0),
    receiptPaymentType: "",
    registrant: userStore.nickName,
    receiptPaymentDate: "",
@@ -518,6 +519,29 @@
// 保存回款记录
const saveReceiptPayment = (row) => {
  // 子表回款金额合计校验:所有回款记录金额之和不能大于父数据合同金额
  // 这里父数据“合同金额”按:已回款金额( invoiceTotal ) + 待回款金额( pendingInvoiceTotal ) 计算
  const findParentRowByChildId = (childId) => {
    return tableData.value.find((p) =>
      Array.isArray(p.children) && p.children.some((c) => c.id === childId)
    );
  };
  const parentRow = findParentRowByChildId(row.id);
  if (parentRow) {
    const contractAmount =
      Number(parentRow.invoiceTotal || 0) + Number(parentRow.pendingInvoiceTotal || 0);
    const sumReceipt = (parentRow.children || []).reduce((sum, item) => {
      const val = Number(item?.receiptPaymentAmount ?? 0);
      return sum + (Number.isFinite(val) ? val : 0);
    }, 0);
    if (sumReceipt > contractAmount) {
      proxy.$modal.msgError(
        `回款金额合计(${sumReceipt.toFixed(2)})不能大于合同金额(${contractAmount.toFixed(2)})`
      );
      return;
    }
  }
  let updateData = {
    id: row.id,
    receiptPaymentType: row.receiptPaymentType,