gaoluyang
2026-04-08 dc98edae03217e7e254c9be94724ba715520c285
新疆马铃薯
1.添加收货管理页面并联调
2.首页展示数据修改
已添加2个文件
已修改1个文件
674 ■■■■■ 文件已修改
src/views/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/receivingManagement/index.vue 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/receivingManagement/modal/ReceiptForm.vue 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue
@@ -39,10 +39,10 @@
              <div class="data-desc">本月销售额/元</div>
              <div class="data-value">{{ businessInfo.monthSaleMoney }}</div>
            </div>
            <div>
              <div class="data-desc">未开票金额/元</div>
              <div class="data-value">{{ businessInfo.monthSaleHaveMoney }}</div>
            </div>
<!--            <div>-->
<!--              <div class="data-desc">未开票金额/元</div>-->
<!--              <div class="data-value">{{ businessInfo.monthSaleHaveMoney }}</div>-->
<!--            </div>-->
          </div>
        </div>
@@ -53,10 +53,10 @@
              <div class="data-desc">本月采购额/元</div>
              <div class="data-value">{{ businessInfo.monthPurchaseMoney }}</div>
            </div>
            <div>
              <div class="data-desc">待付款金额/元</div>
              <div class="data-value">{{ businessInfo.monthPurchaseHaveMoney }}</div>
            </div>
<!--            <div>-->
<!--              <div class="data-desc">待付款金额/元</div>-->
<!--              <div class="data-value">{{ businessInfo.monthPurchaseHaveMoney }}</div>-->
<!--            </div>-->
          </div>
        </div>
        <div class="data-card inventory">
src/views/procurementManagement/receivingManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,320 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="供应商名称:">
          <el-input
            v-model="searchForm.supplierName"
            placeholder="请输入"
            clearable
            prefix-icon="Search"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="采购合同号:">
          <el-input
            v-model="searchForm.purchaseContractNumber"
            style="width: 240px"
            placeholder="请输入"
            clearable
            prefix-icon="Search"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="销售合同号:">
          <el-input
            v-model="searchForm.salesContractNo"
            placeholder="请输入"
            clearable
            prefix-icon="Search"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="项目名称:">
          <el-input
            v-model="searchForm.projectName"
            placeholder="请输入"
            clearable
            prefix-icon="Search"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="录入日期:">
          <el-date-picker
            v-model="searchForm.entryDate"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            type="daterange"
            placeholder="请选择"
            clearable
            @change="changeDaterange"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table_list">
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        :expand-row-keys="expandedRowKeys"
        :row-key="(row) => row.id"
        show-summary
        :summary-method="summarizeMainTable"
        @expand-change="expandChange"
        height="calc(100vh - 21.5em)"
      >
        <el-table-column type="expand">
          <template #default="props">
            <el-table
              :data="props.row.children"
              border
              show-summary
              :summary-method="summarizeChildrenTable"
            >
              <el-table-column align="center" label="序号" type="index" width="60" />
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="可用数量" prop="availableQuality" />
              <el-table-column label="退货数量" prop="returnQuality" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" />
              <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" />
              <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" />
            </el-table>
          </template>
        </el-table-column>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="采购合同号" prop="purchaseContractNumber" width="160" show-overflow-tooltip />
        <el-table-column label="销售合同号" prop="salesContractNo" width="160" show-overflow-tooltip />
        <el-table-column label="供应商名称" prop="supplierName" width="160" show-overflow-tooltip />
        <el-table-column label="项目名称" prop="projectName" width="320" show-overflow-tooltip />
        <el-table-column label="收货状态" prop="status" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getReceiptStatusType(scope.row.status)" size="small">
              {{ receiptStatusText[scope.row.status] || '未知状态' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="审批状态" prop="approvalStatus" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small">
              {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="签订日期" prop="executionDate" width="100" show-overflow-tooltip />
        <el-table-column label="付款方式" prop="paymentMethod" width="100" show-overflow-tooltip />
        <el-table-column label="合同金额(元)" prop="contractAmount" width="200" show-overflow-tooltip :formatter="formattedNumber" />
        <el-table-column label="录入人" prop="recorderName" width="120" show-overflow-tooltip />
        <el-table-column label="录入日期" prop="entryDate" width="100" show-overflow-tooltip />
        <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" width="120" align="center">
          <template #default="scope">
            <el-button
              link
              type="primary"
              :disabled="isConfirmed(scope.row)"
              @click="confirmReceipt(scope.row)"
            >
              {{ isConfirmed(scope.row) ? '已收货' : '确认收货' }}
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
  </div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import dayjs from 'dayjs'
import Pagination from '@/components/PIMTable/Pagination.vue'
import { productList, purchaseListPage, updateApprovalStatus } from '@/api/procurementManagement/procurementLedger.js'
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
const expandedRowKeys = ref([])
const receiptResultMap = ref({})
const page = reactive({
  current: 1,
  size: 100
})
const searchForm = reactive({
  supplierName: '',
  purchaseContractNumber: '',
  salesContractNo: '',
  projectName: '',
  entryDate: [],
  entryDateStart: undefined,
  entryDateEnd: undefined
})
const approvalStatusText = {
  1: '待审核',
  2: '审批中',
  3: '审批通过',
  4: '审批失败'
}
const receiptStatusText = {
  1: '待收货',
  2: '收货中',
  3: '已收货'
}
const getApprovalStatusType = (status) => {
  const typeMap = {
    1: 'info',
    2: 'warning',
    3: 'success',
    4: 'danger'
  }
  return typeMap[status]
}
const getReceiptStatusType = (status) => {
  const typeMap = {
    1: 'info',
    2: 'warning',
    3: 'success'
  }
  return typeMap[status]
}
const formattedNumber = (_row, _column, cellValue) => {
  const value = Number(cellValue)
  return Number.isFinite(value) ? value.toFixed(2) : '0.00'
}
const createSummary = (columns, data, sumFields) => {
  const sums = []
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = '合计'
      return
    }
    if (!sumFields.includes(column.property)) {
      sums[index] = ''
      return
    }
    const totalValue = data.reduce((sum, item) => sum + Number(item?.[column.property] || 0), 0)
    sums[index] = Number.isFinite(totalValue) ? totalValue.toFixed(2) : '0.00'
  })
  return sums
}
const summarizeMainTable = ({ columns, data }) => createSummary(columns, data, ['contractAmount'])
const summarizeChildrenTable = ({ columns, data }) =>
  createSummary(columns, data, ['quantity', 'availableQuality', 'returnQuality', 'taxInclusiveUnitPrice', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'])
const isConfirmed = (row) => Number(row?.status) === 3 || Boolean(receiptResultMap.value[row?.id])
const handleQuery = () => {
  page.current = 1
  getList()
}
const changeDaterange = (value) => {
  if (value) {
    searchForm.entryDateStart = dayjs(value[0]).format('YYYY-MM-DD')
    searchForm.entryDateEnd = dayjs(value[1]).format('YYYY-MM-DD')
  } else {
    searchForm.entryDateStart = undefined
    searchForm.entryDateEnd = undefined
  }
  handleQuery()
}
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
const expandChange = async (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = []
    try {
      const res = await productList({ salesLedgerId: row.id, type: 2 })
      const index = tableData.value.findIndex((item) => item.id === row.id)
      if (index > -1) {
        tableData.value[index].children = res.data || []
        expandedRowKeys.value.push(row.id)
      }
    } catch (_error) {
      ElMessage.error('加载产品列表失败')
      const index = expandedRows.findIndex((item) => item.id === row.id)
      if (index > -1) {
        expandedRows.splice(index, 1)
      }
    }
  } else {
    expandedRowKeys.value = []
  }
}
const getList = () => {
  tableLoading.value = true
  const { entryDate, ...rest } = searchForm
  purchaseListPage({ ...rest, ...page, approvalStatus: 3 })
    .then((res) => {
      tableData.value = (res.data?.records || []).map((record) => ({
        ...record,
        children: []
      }))
      total.value = res.data?.total || 0
      expandedRowKeys.value = []
    })
    .finally(() => {
      tableLoading.value = false
    })
}
const confirmReceipt = async (row) => {
  try {
    await ElMessageBox.confirm('是否确认收货?', '确认收货', {
      type: 'warning',
      confirmButtonText: '确认',
      cancelButtonText: '取消'
    })
    await updateApprovalStatus({
      id: row.id,
      status: 3
    })
    receiptResultMap.value[row.id] = true
    ElMessage.success('确认收货成功')
    getList()
  } catch (error) {
    if (error !== 'cancel' && error !== 'close') {
      ElMessage.error('确认收货失败')
    }
  }
}
onMounted(() => {
  getList()
})
</script>
src/views/procurementManagement/receivingManagement/modal/ReceiptForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,338 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    title="确认收货"
    width="70%"
    operation-type="edit"
    @close="handleClose"
    @cancel="handleClose"
    @confirm="handleConfirm"
  >
    <el-form ref="formRef" :model="detailData" label-width="140px" label-position="top">
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="采购合同号:">
            <el-input :model-value="detailData.purchaseContractNumber || ''" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="销售合同号:">
            <el-input :model-value="detailData.salesContractNo || ''" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="供应商名称:">
            <el-input :model-value="detailData.supplierName || ''" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="项目名称:">
            <el-input :model-value="detailData.projectName || ''" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="付款方式:">
            <el-input :model-value="detailData.paymentMethod || ''" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="签订日期:">
            <el-input :model-value="detailData.executionDate || ''" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="12">
          <el-form-item label="录入人:">
            <el-input :model-value="detailData.recorderName || ''" disabled />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="录入日期:">
            <el-input :model-value="detailData.entryDate || ''" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="24">
          <el-form-item label="审批人选择:">
            <div class="approver-nodes-container">
              <div v-for="(name, index) in approverNames" :key="`${name}-${index}`" class="approver-node-item">
                <div class="approver-node-header">
                  <span class="approver-node-label">审批节点 {{ index + 1 }}</span>
                </div>
                <el-input :model-value="name" disabled />
              </div>
              <el-empty v-if="!approverNames.length" description="暂无审批人信息" />
            </div>
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item label="产品信息:">
        <el-table :data="detailData.productData" border show-summary :summary-method="summarizeProductTable">
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="产品大类" prop="productCategory" />
          <el-table-column label="规格型号" prop="specificationModel" />
          <el-table-column label="单位" prop="unit" width="70" />
          <el-table-column label="数量" prop="quantity" width="90" />
          <el-table-column label="库存预警数量" prop="warnNum" width="120" show-overflow-tooltip />
          <el-table-column label="税率(%)" prop="taxRate" width="80" />
          <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" width="150" :formatter="formattedNumber" />
          <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" width="150" :formatter="formattedNumber" />
          <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" width="150" :formatter="formattedNumber" />
          <el-table-column label="是否质检" prop="isChecked" width="100">
            <template #default="scope">
              <el-tag :type="scope.row.isChecked ? 'success' : 'info'">
                {{ scope.row.isChecked ? '是' : '否' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="是否合格" min-width="180">
            <template #default="scope">
              <el-form-item
                :prop="`productData.${scope.$index}.isQualified`"
                :rules="[{ required: true, message: '请选择是否合格', trigger: 'change' }]"
                class="inline-form-item"
              >
                <el-radio-group v-model="scope.row.isQualified">
                  <el-radio :label="1">合格</el-radio>
                  <el-radio :label="2">不合格</el-radio>
                </el-radio-group>
              </el-form-item>
            </template>
          </el-table-column>
          <el-table-column label="不合格原因" min-width="220">
            <template #default="scope">
              <el-form-item
                :prop="`productData.${scope.$index}.reason`"
                :rules="[{
                  validator: (_rule, value, callback) => validateReason(scope.row, value, callback),
                  trigger: 'blur'
                }]"
                class="inline-form-item"
              >
                <el-input
                  v-model="scope.row.reason"
                  :disabled="scope.row.isQualified !== 2"
                  placeholder="不合格时请填写原因"
                  clearable
                />
              </el-form-item>
            </template>
          </el-table-column>
        </el-table>
      </el-form-item>
      <el-row :gutter="30">
        <el-col :span="24">
          <el-form-item label="备注:">
            <el-input :model-value="detailData.remarks || ''" type="textarea" :rows="2" disabled />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="30">
        <el-col :span="24">
          <el-form-item label="附件材料:">
            <div class="file-list">
              <el-tag v-for="file in detailData.salesLedgerFiles" :key="file.id || file.fileName" class="file-tag">
                {{ file.fileName || file.name }}
              </el-tag>
              <el-empty v-if="!detailData.salesLedgerFiles?.length" description="暂无附件" />
            </div>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import FormDialog from '@/components/Dialog/FormDialog.vue'
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  record: {
    type: Object,
    default: () => ({})
  }
})
const emit = defineEmits(['update:modelValue', 'submit'])
const formRef = ref()
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value)
})
const createDefaultDetail = () => ({
  id: '',
  purchaseContractNumber: '',
  salesContractNo: '',
  supplierName: '',
  projectName: '',
  paymentMethod: '',
  executionDate: '',
  recorderName: '',
  entryDate: '',
  remarks: '',
  approveUserIds: '',
  approverNames: [],
  salesLedgerFiles: [],
  productData: []
})
const detailData = reactive(createDefaultDetail())
const approverNames = computed(() => detailData.approverNames || [])
const formattedNumber = (_row, _column, cellValue) => {
  const value = Number(cellValue)
  return Number.isFinite(value) ? value.toFixed(2) : '0.00'
}
const summarizeProductTable = ({ columns, data }) => {
  const sumFields = ['quantity', 'taxInclusiveUnitPrice', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']
  const sums = []
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = '合计'
      return
    }
    if (!sumFields.includes(column.property)) {
      sums[index] = ''
      return
    }
    const total = data.reduce((sum, item) => sum + Number(item?.[column.property] || 0), 0)
    sums[index] = Number.isFinite(total) ? total.toFixed(2) : '0.00'
  })
  return sums
}
const validateReason = (row, value, callback) => {
  if (row.isQualified === 2 && (!value || !value.trim())) {
    callback(new Error('不合格时必须填写原因'))
    return
  }
  callback()
}
watch(
  () => props.record,
  (value) => {
    const nextData = createDefaultDetail()
    Object.assign(nextData, value || {})
    nextData.productData = (value?.productData || []).map((item) => ({
      ...item,
      isQualified: item.isQualified ?? undefined,
      reason: item.reason || ''
    }))
    nextData.salesLedgerFiles = value?.salesLedgerFiles || []
    nextData.approverNames = value?.approverNames || []
    Object.assign(detailData, nextData)
  },
  { immediate: true, deep: true }
)
const handleClose = () => {
  dialogVisible.value = false
}
const handleConfirm = async () => {
  if (!formRef.value) {
    return
  }
  try {
    await formRef.value.validate()
    const unqualifiedRows = detailData.productData.filter((item) => item.isQualified === 2)
    emit('submit', {
      id: detailData.id,
      purchaseContractNumber: detailData.purchaseContractNumber,
      supplierName: detailData.supplierName,
      projectName: detailData.projectName,
      productData: detailData.productData.map((item) => ({
        id: item.id,
        specificationModel: item.specificationModel,
        quantity: item.quantity,
        isQualified: item.isQualified,
        reason: item.reason || ''
      })),
      unqualifiedQuantity: unqualifiedRows.reduce((sum, item) => sum + Number(item.quantity || 0), 0)
    })
    dialogVisible.value = false
  } catch (_error) {
    ElMessage.warning('请先完成每条产品的收货判断')
  }
}
</script>
<style scoped lang="scss">
.approver-nodes-container {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  width: 100%;
}
.approver-node-item {
  flex: 0 0 calc(33.333% - 12px);
  min-width: 220px;
  padding: 12px;
  background-color: #fff;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}
.approver-node-header {
  margin-bottom: 8px;
}
.approver-node-label {
  font-size: 13px;
  font-weight: 500;
  color: #606266;
}
.inline-form-item {
  margin-bottom: 0;
}
.file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
}
.file-tag {
  margin-right: 0;
}
@media (max-width: 1200px) {
  .approver-node-item {
    flex: 0 0 calc(50% - 8px);
  }
}
@media (max-width: 768px) {
  .approver-node-item {
    flex: 0 0 100%;
  }
}
</style>