gaoluyang
2025-05-20 7aa0e0f68d2a3d1268a64e49ab47c0c046265e84
1.采购台账-开发联调
2.表格合计方法封装
已修改3个文件
已添加3个文件
425 ■■■■ 文件已修改
src/api/basicData/product.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/summarizeTable.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 261 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/invoiceEntry/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 105 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/basicData/product.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// äº§å“ç»´æŠ¤é¡µé¢æŽ¥å£
import request from '@/utils/request'
// äº§å“æ ‘查询
export function productTreeList(query) {
    return request({
        url: '/basic/product/list',
        method: 'get',
        params: query
    })
}
src/main.js
@@ -46,6 +46,7 @@
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { getToken } from "@/utils/auth";
import { summarizeTable } from "@/utils/summarizeTable.js";
const app = createApp(App)
@@ -54,6 +55,7 @@
app.config.globalProperties.download = download
app.config.globalProperties.parseTime = parseTime
app.config.globalProperties.resetForm = resetForm
app.config.globalProperties.summarizeTable = summarizeTable
app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange
app.config.globalProperties.selectDictLabel = selectDictLabel
src/utils/summarizeTable.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
/**
 * é€šç”¨çš„表格合计方法
 * @param {Object} param
 * @param {Array} param.columns è¡¨æ ¼åˆ—配置
 * @param {Array} param.data æ•°æ®æº
 * @param {Array<string>} summaryProps éœ€è¦æ±‡æ€»çš„字段名数组
 * @returns {Array} åˆè®¡è¡Œæ•°æ®
 */
const summarizeTable = (param, summaryProps) => {
    const { columns, data } = param;
    const sums = [];
    columns.forEach((column, index) => {
        if (index === 0) {
            sums[index] = '合计';
            return;
        }
        const prop = column.property;
        if (summaryProps.includes(prop)) {
            const values = data.map(item => Number(item[prop]));
            // åªå¯¹æœ‰æ•ˆæ•°å­—进行求和
            if (!values.every(isNaN)) {
                const sum = values.reduce((acc, val) => (!isNaN(val) ? acc + val : acc), 0);
                sums[index] = parseFloat(sum).toFixed(2);
            } else {
                sums[index] = '';
            }
        } else {
            sums[index] = '';
        }
    });
    return sums;
};
// å¯¼å‡ºå‡½æ•°ä¾›å…¶ä»–文件使用
export { summarizeTable };
src/views/basicData/product/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,261 @@
<template>
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 240px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
            clearable
            prefix-icon="Search"
        />
        <el-button type="primary" @click="openProDia('add')" style="margin-left: 10px">新增产品</el-button>
      </div>
      <div>
        <el-tree ref="tree" v-loading="treeLoad" :data="list"
                 :default-expanded-keys="expandedKeys" :draggable="true" :filter-node-method="filterNode"
                 :props="{ children: 'children', label: 'label' }" highlight-current node-key="label"
                 style="height: calc(100vh - 190px);overflow-y: scroll;scrollbar-width: none;">
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span>{{ node.label }}</span>
              <div>
                <el-button type="primary" link @click="openProDia('edit', data)">
                  ç¼–辑
                </el-button>
                <!-- ä¿®æ”¹æ­¤å¤„ -->
                <el-button
                    v-if="!node.childNodes.length"
                    style="margin-left: 4px"
                    type="danger"
                    link
                    @click="remove(node, data)"
                >
                  åˆ é™¤
                </el-button>
              </div>
            </div>
          </template>
        </el-tree>
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="modelDia = true">新增规格型号</el-button>
        <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain>删除</el-button>
      </div>
      <PIMTable :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" :handleSelectionChange="handleSelectionChange"
                :tableLoading="tableLoading" @pagination="pagination" :total="total"></PIMTable>
    </div>
    <el-dialog :visible.sync="productDia" title="产品" width="400px">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:" prop="productName">
              <el-input v-model="form.productName" 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="closeProDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {ElMessageBox} from "element-plus";
import {productTreeList} from "@/api/basicData/product.js";
const { proxy } = getCurrentInstance()
const search = ref('')
const treeLoad = ref(false)
const list = ref([])
const expandedKeys = ref([])
const tableColumn = ref([
  {
    label: '规格型号',
    prop: 'model',
  },
  {
    label: '单位',
    prop: 'model',
  },
  {
    dataType: "action",
    label: "操作",
    align: 'center',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm('edit', row);
        },
      },
    ],
  },
])
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
const selectedRows = ref([])
const page = reactive({
  current: 1,
  size: 10,
})
const productDia = ref(false)
const modelDia = ref(false)
const data = reactive({
  form: {
    productName: '',
  },
  rules: {
    productName: [{ required: true, message: "请输入", trigger: "blur" }],
  }
})
const { form, rules } = toRefs(data)
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList().then(res => {
    list.value = res
    list.value.forEach((a) => {
      expandedKeys.value.push(a.label);
    });
    treeLoad.value = false;
  }).catch(err => {
    treeLoad.value = false;
  })
}
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
}
const openProDia = (type, data) => {
  productDia.value = true
  console.log('openEditDia', data)
}
const submitForm = () => {
}
const closeProDia = () => {
  proxy.$refs.formRef.resetFields();
  productDia.value = false;
}
const remove = (value) => {
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// åˆ é™¤
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '删除提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    tableLoading.value = true
    delCustomer(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    }).finally(() => {
      tableLoading.value = false
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
// æŸ¥è¯¢è§„格型号
const pagination = ({ current, limit }) => {
  page.current = current;
  page.size = limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  listCustomer({...searchForm.value, ...page}).then(res => {
    tableLoading.value = false
    tableData.value = res.rows
    total.value = res.total
  })
}
// è°ƒç”¨tree过滤方法 ä¸­æ–‡è‹±è¿‡æ»¤
const filterNode = (value, data, node) => {
  if (!value) {    //如果数据为空,则返回true,显示所有的数据项
    return true
  }
  // æŸ¥è¯¢åˆ—表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase()
  return chooseNode(val, data, node) // è°ƒç”¨è¿‡æ»¤äºŒå±‚方法
}
// è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹ (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true
  }
  const level = node.level
  // å¦‚果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false
  }
  // å…ˆå–当前节点的父节点
  let parentData = node.parent
  // éåŽ†å½“å‰èŠ‚ç‚¹çš„çˆ¶èŠ‚ç‚¹
  let index = 0
  while (index < level - 1) {
    // å¦‚果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true
    }
    // å¦åˆ™çš„话再往上一层做匹配
    parentData = parentData.parent
    index++
  }
  // æ²¡åŒ¹é…åˆ°è¿”回false
  return false
}
getProductTreeList()
</script>
<style scoped>
.product-view {
  display: flex;
}
.left {
  width: 380px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
</style>
src/views/procurementManagement/invoiceEntry/index.vue
@@ -54,7 +54,6 @@
        <el-table-column label="采购合同号" prop="purchaseContractNumber" show-overflow-tooltip/>
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip/>
        <el-table-column label="供应商名称" prop="supplierName" show-overflow-tooltip/>
        <el-table-column label="业务员" prop="businessPerson" show-overflow-tooltip/>
        <el-table-column label="项目名称" prop="projectName" show-overflow-tooltip/>
        <el-table-column label="合同金额(元)" prop="contractAmount" show-overflow-tooltip/>
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
@@ -91,15 +90,6 @@
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input v-model="form.projectName" placeholder="自动填充" clearable disabled/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="业务员:" prop="businessPersonId">
              <el-select v-model="form.businessPersonId" placeholder="自动填充" clearable disabled>
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"/>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -337,7 +327,6 @@
    form.value.projectName = res.projectName
    productData.value = res.productData
    form.value.supplierName = res.supplierName
    form.value.businessPersonId = res.businessPersonId
  })
}
// æäº¤è¡¨å•
src/views/procurementManagement/procurementLedger/index.vue
@@ -40,9 +40,9 @@
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" />
              <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" />
              <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" />
              <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>
@@ -50,9 +50,8 @@
        <el-table-column label="采购合同号" prop="purchaseContractNumber" show-overflow-tooltip/>
        <el-table-column label="销售合同号" prop="salesContractNo" show-overflow-tooltip/>
        <el-table-column label="供应商名称" prop="supplierName" show-overflow-tooltip/>
        <el-table-column label="业务员" prop="businessPerson" show-overflow-tooltip/>
        <el-table-column label="项目名称" prop="projectName" show-overflow-tooltip/>
        <el-table-column label="合同金额(元)" prop="contractAmount" show-overflow-tooltip/>
        <el-table-column label="合同金额(元)" prop="contractAmount" show-overflow-tooltip :formatter="formattedNumber"/>
        <el-table-column label="录入人" prop="recorderName" show-overflow-tooltip/>
        <el-table-column label="录入日期" prop="entryDate" show-overflow-tooltip/>
        <el-table-column fixed="right" label="操作" min-width="60" align="center">
@@ -96,20 +95,6 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="业务员:" prop="businessPersonId">
              <el-select v-model="form.businessPersonId" placeholder="请选择" clearable @change="setPhone">
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"/>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="电话:" prop="phoneNumber">
              <el-input v-model="form.phoneNumber" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="录入人:" prop="recorderId">
              <el-select v-model="form.recorderId" placeholder="请选择" clearable disabled>
                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId"/>
@@ -137,7 +122,7 @@
            <el-button plain type="danger" @click="deleteProduct">删除</el-button>
          </el-form-item>
        </el-row>
        <el-table :data="productData" border @selection-change="productSelected">
        <el-table :data="productData" border @selection-change="productSelected" show-summary :summary-method="summarizeProTable">
          <el-table-column align="center" type="selection" width="55" />
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="产品大类" prop="productCategory" />
@@ -145,12 +130,12 @@
          <el-table-column label="单位" prop="unit" />
          <el-table-column label="数量" prop="quantity" />
          <el-table-column label="税率(%)" prop="taxRate" />
          <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" />
          <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" />
          <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" />
          <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber"/>
          <el-table-column label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber"/>
          <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber"/>
          <el-table-column fixed="right" label="操作" min-width="60" align="center">
            <template #default="scope">
              <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row);">编辑</el-button>
              <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row, scope.$index);">编辑</el-button>
            </template>
          </el-table-column>
        </el-table>
@@ -229,7 +214,7 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice">
              <el-input v-model="productForm.taxInclusiveUnitPrice" placeholder="请输入" clearable/>
              <el-input-number v-model="productForm.taxInclusiveUnitPrice" :precision="2" :step="0.1" clearable style="width: 100%"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -245,12 +230,12 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice">
              <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请输入" clearable/>
              <el-input-number v-model="productForm.taxInclusiveTotalPrice" :precision="2" :step="0.1" clearable style="width: 100%"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice">
              <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请输入" clearable/>
              <el-input-number v-model="productForm.taxExclusiveTotalPrice" :precision="2" :step="0.1" clearable style="width: 100%"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -325,8 +310,6 @@
    recorderId: '',
    entryDate: '',
    productData: [],
    businessPersonId: '',
    phoneNumber: '',
    supplierName: '',
    supplierId: '',
  },
@@ -334,8 +317,6 @@
    purchaseContractNumber: [{ required: true, message: "请输入", trigger: "blur" }],
    salesLedgerId: [{ required: true, message: "请选择", trigger: "change" }],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    businessPersonId: [{ required: true, message: "请选择", trigger: "change" }],
    phoneNumber: [{ required: true, message: "请输入", trigger: "blur" }],
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
  }
})
@@ -343,6 +324,7 @@
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false)
const productOperationType = ref('')
const productOperationIndex = ref('')
const currentId = ref('')
const productFormData = reactive({
  productForm: {
@@ -376,7 +358,9 @@
  headers: { Authorization: "Bearer " + getToken() },
})
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
@@ -431,50 +415,12 @@
}
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = (param) => {
  const { columns, data } = param;
  const sums = [];
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = '合计';
      return;
    }
    const prop = column.property;
    if (['contractAmount'].includes(prop)) {
      const values = data.map(item => Number(item[prop]));
      if (!values.every(value => isNaN(value))) {
        sums[index] = values.reduce((acc, val) => (!isNaN(val) ? acc + val : acc), 0);
      } else {
        sums[index] = '';
      }
    } else {
      sums[index] = '';
    }
  })
  return sums;
  return proxy.summarizeTable(param, ['contractAmount']);
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeChildrenTable = (param) => {
  const { columns, data } = param;
  const sums = [];
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = '合计';
      return;
    }
    const prop = column.property;
    if (['taxInclusiveUnitPrice', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice'].includes(prop)) {
      const values = data.map(item => Number(item[prop]));
      if (!values.every(value => isNaN(value))) {
        sums[index] = values.reduce((acc, val) => (!isNaN(val) ? acc + val : acc), 0);
      } else {
        sums[index] = '';
      }
    } else {
      sums[index] = '';
    }
  });
  return sums;
}
const summarizeProTable = (param) => {
  return proxy.summarizeTable(param, ['taxInclusiveUnitPrice', 'taxInclusiveTotalPrice', 'taxExclusiveTotalPrice']);
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type
@@ -505,10 +451,6 @@
    })
  }
  dialogFormVisible.value = true
}
// èµ‹å€¼ç”µè¯
const setPhone = (id) => {
  form.value.phoneNumber = userList.value.find(u => u.userId === id)?.phonenumber || '';
}
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
@@ -577,8 +519,9 @@
  dialogFormVisible.value = false
}
// æ‰“开产品弹框
const openProductForm = (type, row) => {
const openProductForm = (type, row, index) => {
  productOperationType.value = type
  productOperationIndex.value = index
  productForm.value = {}
  proxy.resetForm("productFormRef")
  if (type === 'edit') {
@@ -593,7 +536,11 @@
      if (operationType.value === "edit") {
        submitProductEdit()
      } else {
        if (productOperationType.value === 'add') {
        productData.value.push({...productForm.value})
        } else {
          productData.value[productOperationIndex.value] = {...productForm.value}
        }
        closeProductDia()
      }
    }