src/api/procurementManagement/procurementLedger.js
@@ -114,4 +114,12 @@ method: "delete", data: id, }); } // 查询采购详情 export function getPurchaseByCode(id) { return request({ url: "/purchase/ledger/getPurchaseByCode", method: "get", params: id, }); } src/views/basicData/supplierManage/components/BlacklistTab.vue
@@ -499,7 +499,7 @@ type: "warning", }) .then(() => { proxy.download("/system/supplier/export", {}, "供应商档案.xlsx"); proxy.download("/system/supplier/export", { isWhite: 1 }, "供应商档案.xlsx"); }) .catch(() => { proxy.$modal.msg("已取消"); @@ -559,6 +559,10 @@ onMounted(() => { getList(); }); defineExpose({ getList, }); </script> src/views/basicData/supplierManage/components/HomeTab.vue
@@ -505,7 +505,7 @@ type: "warning", }) .then(() => { proxy.download("/system/supplier/export", {}, "供应商档案.xlsx"); proxy.download("/system/supplier/export", { isWhite: 0 }, "供应商档案.xlsx"); }) .catch(() => { proxy.$modal.msg("已取消"); @@ -565,5 +565,9 @@ onMounted(() => { getList(); }); defineExpose({ getList, }); </script> src/views/basicData/supplierManage/index.vue
@@ -1,12 +1,12 @@ <!-- 在你的主页面中 --> <template> <div class="app-container"> <el-tabs v-model="activeTab" type="card"> <el-tabs v-model="activeTab" @tab-change="handleTabChange"> <el-tab-pane label="正常供应商" name="home"> <HomeTab /> <HomeTab ref="homeTab" /> </el-tab-pane> <el-tab-pane label="黑名单" name="blacklist"> <BlacklistTab /> <BlacklistTab ref="blacklistTab" /> </el-tab-pane> </el-tabs> </div> @@ -27,21 +27,17 @@ activeTab: 'home' } }, watch: { activeTab(newVal) { if (newVal === 'home') { this.$refs.homeTab && this.$refs.homeTab.getList() } else if (newVal === 'blacklist') { this.$refs.blacklistTab && this.$refs.blacklistTab.getList() } } methods: { handleTabChange(tabName) { this.activeTab = tabName this.$nextTick(() => { if (tabName === 'home') { this.$refs.homeTab && this.$refs.homeTab.getList && this.$refs.homeTab.getList() } else if (tabName === 'blacklist') { this.$refs.blacklistTab && this.$refs.blacklistTab.getList && this.$refs.blacklistTab.getList() } }) }, } } </script> <style> .main-container :deep(.el-tabs__item.is-active) { color: #1883f6 !important; border-bottom: 2px solid #409EFF; } </style> src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -2,7 +2,7 @@ <div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增审批流程' : '编辑审批流程'" :title="operationType === 'approval' ? '审批' : '详情'" width="700px" @close="closeDia" > @@ -32,9 +32,9 @@ </el-form-item> </el-col> </el-row> <el-row v-if="!isQuotationApproval"> <el-row v-if="!isQuotationApproval && !isPurchaseApproval"> <el-col :span="24"> <el-form-item label="审批事由:" prop="approveReason"> <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" disabled/> </el-form-item> </el-col> @@ -74,7 +74,7 @@ </el-row> </el-form> <!-- 报价审批:展示报价详情(复用销售报价“查看详情对话框”内容结构) --> <!-- 报价审批:展示报价详情(复用销售报价"查看详情对话框"内容结构) --> <div v-if="isQuotationApproval" style="margin: 10px 0 18px;"> <el-divider content-position="left">报价详情</el-divider> <el-skeleton :loading="quotationLoading" animated> @@ -115,6 +115,53 @@ <div v-if="currentQuotation.remark" style="margin-top: 20px;"> <h4>备注</h4> <p>{{ currentQuotation.remark }}</p> </div> </template> </template> </el-skeleton> </div> <!-- 采购审批:展示采购详情 --> <div v-if="isPurchaseApproval" style="margin: 10px 0 18px;"> <el-divider content-position="left">采购详情</el-divider> <el-skeleton :loading="purchaseLoading" animated> <template #template> <el-skeleton-item variant="h3" style="width: 30%" /> <el-skeleton-item variant="text" style="width: 100%" /> <el-skeleton-item variant="text" style="width: 100%" /> </template> <template #default> <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" description="未查询到对应采购详情" /> <template v-else> <el-descriptions :column="2" border> <el-descriptions-item label="采购合同号">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item> <el-descriptions-item label="供应商名称">{{ currentPurchase.supplierName }}</el-descriptions-item> <el-descriptions-item label="项目名称">{{ currentPurchase.projectName }}</el-descriptions-item> <el-descriptions-item label="销售合同号">{{ currentPurchase.salesContractNo }}</el-descriptions-item> <el-descriptions-item label="签订日期">{{ currentPurchase.executionDate }}</el-descriptions-item> <el-descriptions-item label="录入日期">{{ currentPurchase.entryDate }}</el-descriptions-item> <el-descriptions-item label="付款方式">{{ currentPurchase.paymentMethod }}</el-descriptions-item> <el-descriptions-item label="合同金额" :span="2"> <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} </span> </el-descriptions-item> </el-descriptions> <div style="margin-top: 20px;"> <h4>产品明细</h4> <el-table :data="currentPurchase.productData || []" border style="width: 100%"> <el-table-column prop="productCategory" label="产品名称" /> <el-table-column prop="specificationModel" label="规格型号" /> <el-table-column prop="unit" label="单位" /> <el-table-column prop="quantity" label="数量" /> <el-table-column prop="taxInclusiveUnitPrice" label="含税单价"> <template #default="scope">¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> </el-table-column> <el-table-column prop="taxInclusiveTotalPrice" label="含税总价"> <template #default="scope">¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> </el-table-column> </el-table> </div> </template> </template> @@ -188,6 +235,7 @@ import {userListNoPageByTenantId} from "@/api/system/user.js"; import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue' import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; const emit = defineEmits(['close']) const { proxy } = getCurrentInstance() @@ -207,7 +255,10 @@ const userList = ref([]) const quotationLoading = ref(false) const currentQuotation = ref({}) const purchaseLoading = ref(false) const currentPurchase = ref({}) const isQuotationApproval = computed(() => Number(props.approveType) === 6) const isPurchaseApproval = computed(() => Number(props.approveType) === 5) const data = reactive({ form: { @@ -247,6 +298,7 @@ operationType.value = type; dialogFormVisible.value = true; currentQuotation.value = {} currentPurchase.value = {} userListNoPageByTenantId().then((res) => { userList.value = res.data; }); @@ -277,7 +329,7 @@ }); }); // 报价审批:用审批事由字段承载的“报价单号”去查报价列表 // 报价审批:用审批事由字段承载的"报价单号"去查报价列表 if (isQuotationApproval.value) { const quotationNo = row?.approveReason; if (quotationNo) { @@ -287,6 +339,22 @@ currentQuotation.value = records[0] || {} }).finally(() => { quotationLoading.value = false }) } } // 采购审批:用审批事由字段承载的"采购合同号"去查采购详情 if (isPurchaseApproval.value) { const purchaseContractNumber = row?.approveReason; if (purchaseContractNumber) { purchaseLoading.value = true getPurchaseByCode({ purchaseContractNumber }).then((res) => { currentPurchase.value = res }).catch((err) => { console.error('查询采购详情失败:', err) proxy.$modal.msgError('查询采购详情失败') }).finally(() => { purchaseLoading.value = false }) } } @@ -341,6 +409,8 @@ dialogFormVisible.value = false; quotationLoading.value = false currentQuotation.value = {} purchaseLoading.value = false currentPurchase.value = {} emit('close') }; defineExpose({ src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -35,7 +35,7 @@ </el-row> <el-row> <el-col :span="24"> <el-form-item :label="props.approveType == 5 ? '采购说明:' : '审批事由:'" prop="approveReason"> <el-form-item :label="props.approveType == 5 ? '采购合同号:' : '审批事由:'" prop="approveReason"> <el-input v-model="form.approveReason" placeholder="请输入" clearable type="textarea" /> </el-form-item> </el-col> src/views/collaborativeApproval/approvalProcess/index.vue
@@ -113,6 +113,7 @@ const isLeaveType = currentApproveType.value === 2; // 请假管理 const isReimburseType = currentApproveType.value === 4; // 报销管理 const isQuotationType = currentApproveType.value === 6; // 报价审批 const isPurchaseType = currentApproveType.value === 5; // 采购审批 // 基础列配置 const baseColumns = [ @@ -159,7 +160,7 @@ width: 220 }, { label: isQuotationType ? "报价单号" : "审批事由", label: isQuotationType ? "报价单号" : isPurchaseType ? "采购合同号" : "审批事由", prop: "approveReason", width: 200 }, src/views/inventoryManagement/stockManagement/Import.vue
@@ -8,6 +8,7 @@ :disabled="upload.isUploading" :showTip="true" @success="handleFileSuccess" :downloadTemplate="downloadTemplate" /> <template #footer> <div class="dialog-footer"> @@ -19,7 +20,7 @@ </template> <script setup> import {computed, reactive} from "vue"; import {computed, getCurrentInstance, reactive} from "vue"; import { getToken } from "@/utils/auth.js"; import { FileUpload } from "@/components/Upload"; import { ElMessage } from "element-plus"; @@ -27,6 +28,8 @@ defineOptions({ name: "导入库存", }); const { proxy } = getCurrentInstance() const props = defineProps({ visible: { @@ -80,6 +83,10 @@ } }; const downloadTemplate = () => { proxy.download("/stockInventory/downloadStockInventory", {}, "库存导入模板.xlsx"); } const closeModal = () => { isShow.value = false; }; src/views/inventoryManagement/stockManagement/Qualified.vue
@@ -11,7 +11,6 @@ </div> <div> <el-button type="primary" @click="isShowNewModal = true">新增库存</el-button> <el-button @click="importTemplate">下载导入模板</el-button> <el-button type="info" plain icon="Upload" @click="isShowImportModal = true"> 导入库存 </el-button> @@ -161,10 +160,6 @@ }).catch(() => { proxy.$modal.msg("已取消") }) } const importTemplate =() =>{ proxy.download("/stockInventory/downloadStockInventory", {}, "库存导入模板.xlsx"); } onMounted(() => { src/views/inventoryManagement/stockReport/index.vue
@@ -14,17 +14,17 @@ <el-option label="月报" value="monthly" /> <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-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" @@ -47,99 +47,99 @@ 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>--> <!-- <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="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="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"> @@ -147,87 +147,88 @@ <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 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" align="center" label="序号" type="index" width="60" /> <el-table-column label="入库时间" prop="createTime" width="200" show-overflow-tooltip v-if="!searchForm.reportType === 'inout'" /> <el-table-column label="入库批次" prop="inboundBatches" width="240" show-overflow-tooltip v-if="!searchForm.reportType === 'inout'" /> <el-table-column label="产品大类" prop="productName" show-overflow-tooltip /> <el-table-column label="规格型号" prop="model" show-overflow-tooltip /> <el-table-column label="单位" prop="unit" show-overflow-tooltip /> <el-table-column label="入库数量" prop="totalStockIn" align="center" v-if="searchForm.reportType === 'inout'" /> <el-table-column label="入库数量" prop="stockInNum" align="center" v-else /> <el-table-column label="出库数量" prop="totalStockOut" width="100" align="center" /> <el-table-column label="现在库存" prop="currentStock" align="center" /> <el-table-column label="来源" prop="recordType" v-if="!searchForm.reportType === 'inout'" show-overflow-tooltip> <template #default="scope"> {{ getRecordType(scope.row.recordType) }} </template> </el-table-column> <el-table-column label="入库人" prop="createBy" width="80" v-if="!searchForm.reportType === 'inout'" show-overflow-tooltip /> <el-table-column label="入库时间" prop="createTime" width="200" show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" /> <el-table-column label="入库批次" prop="inboundBatches" width="240" show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" /> <el-table-column label="产品大类" prop="productName" show-overflow-tooltip /> <el-table-column label="规格型号" prop="model" show-overflow-tooltip /> <el-table-column label="单位" prop="unit" show-overflow-tooltip /> <el-table-column label="入库数量" prop="totalStockIn" align="center" v-if="searchForm.reportType === 'inout'" /> <el-table-column label="入库数量" prop="stockInNum" align="center" v-else /> <el-table-column label="出库数量" prop="totalStockOut" width="100" align="center" v-if="searchForm.reportType === 'inout'" /> <el-table-column label="现在库存" prop="currentStock" align="center" /> <el-table-column label="来源" prop="recordType" v-if="searchForm.reportType !== 'inout'" show-overflow-tooltip> <template #default="scope"> {{ getRecordType(scope.row.recordType) }} </template> </el-table-column> <el-table-column label="入库人" prop="createBy" width="80" v-if="searchForm.reportType !== 'inout'" show-overflow-tooltip /> </el-table> </el-card> </div> @@ -306,7 +307,7 @@ if (!validateSearchForm()) { return } tableLoading.value = true try { const params = getQueryParams() @@ -324,7 +325,7 @@ // nextTick(() => { // initCharts() // }) } } catch (error) { ElMessage.error('查询失败:' + error.message) @@ -394,7 +395,7 @@ startDate: "", endDate: "" } if (searchForm.reportType === 'daily') { params.reportDate = searchForm.singleDate } else if (searchForm.reportType === 'monthly') { @@ -404,7 +405,7 @@ params.startDate = searchForm.dateRange[0] params.endDate = searchForm.dateRange[1] } return params } @@ -426,7 +427,7 @@ if (!validateSearchForm()) { return } try { const params = getQueryParams() // const response = await exportStockReport(params) @@ -441,7 +442,7 @@ // link.click() // document.body.removeChild(link) // window.URL.revokeObjectURL(url) // ElMessage.success('导出成功') } catch (error) { ElMessage.error('导出失败:' + error.message) @@ -451,7 +452,7 @@ // 初始化图表 const initCharts = () => { if (!reportData.value.chartData) return initTrendChart() initComparisonChart() } @@ -459,7 +460,7 @@ // 初始化趋势图 const initTrendChart = () => { if (!trendChart.value) return const chart = echarts.init(trendChart.value) const option = { title: { @@ -496,7 +497,7 @@ // 初始化对比图 const initComparisonChart = () => { if (!comparisonChart.value) return const chart = echarts.init(comparisonChart.value) const option = { title: { @@ -543,7 +544,7 @@ 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], src/views/salesManagement/deliveryLedger/index.vue
@@ -51,13 +51,13 @@ link type="primary" size="small" :disabled="!isApproved(scope.row.status)" :disabled="isApproving(scope.row.status)" @click="openForm('edit', scope.row)">编辑</el-button> <el-button link type="danger" size="small" :disabled="!isApproved(scope.row.status)" :disabled="isApproving(scope.row.status)" @click="handleDeleteSingle(scope.row)">删除</el-button> </template> </el-table-column> @@ -284,9 +284,9 @@ // 打开弹框 const openForm = async (type, row) => { // 编辑时检查审核状态 if (type === 'edit' && row && !isApproved(row.status)) { proxy.$modal.msgWarning("只能编辑审核通过的数据"); // 编辑时检查审核状态,只有审核中不能编辑 if (type === 'edit' && row && isApproving(row.status)) { proxy.$modal.msgWarning("审核中的数据不能编辑"); return; } @@ -430,10 +430,10 @@ return; } // 检查选中的行是否都是"审核通过"状态 const notApprovedRows = selectedRows.value.filter(row => !isApproved(row.status)); if (notApprovedRows.length > 0) { proxy.$modal.msgWarning("只能删除审核通过的数据"); // 检查选中的行是否有"审核中"状态 const approvingRows = selectedRows.value.filter(row => isApproving(row.status)); if (approvingRows.length > 0) { proxy.$modal.msgWarning("审核中的数据不能删除"); return; } @@ -456,9 +456,9 @@ // 单个删除 const handleDeleteSingle = (row) => { // 检查是否为"审核通过"状态 if (!isApproved(row.deliveryLedger)) { proxy.$modal.msgWarning("只能删除审核通过的数据"); // 检查是否为"审核中"状态 if (isApproving(row.status)) { proxy.$modal.msgWarning("审核中的数据不能删除"); return; } @@ -635,6 +635,20 @@ return statusStr === '审核通过' || statusStr === '3'; }; // 检查审核状态是否为"审核中" const isApproving = (status) => { if (status === null || status === undefined || status === '') { return false; } // 如果是数字,1 表示审核中 if (typeof status === 'number') { return status === 1; } // 如果是字符串 const statusStr = String(status).trim(); return statusStr === '审核中' || statusStr === '1'; }; onMounted(() => { getList(); }); src/views/salesManagement/indicatorStats/index.vue
@@ -1,82 +1,161 @@ <template> <div class="app-container indicator-stats"> <el-card class="box-card"> <!-- KPI 汇总 --> <el-row :gutter="20" class="stats-row"> <el-col :span="8"> <div class="stat-card"> <div class="stat-icon" style="background: #ecf5ff;"> <el-icon :size="30" color="#409eff"><Document /></el-icon> </div> <div class="stat-content"> <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div> <div class="stat-label">订单数量</div> <!-- KPI 汇总 --> <el-row :gutter="20" class="stats-row"> <el-col :xs="24" :sm="12" :md="8"> <div class="stat-card stat-card-blue"> <div class="stat-icon-wrapper"> <div class="stat-icon"> <el-icon :size="32"><Document /></el-icon> </div> </div> </el-col> <el-col :span="8"> <div class="stat-card"> <div class="stat-icon" style="background: #f0f9ff;"> <el-icon :size="30" color="#67c23a"><Tickets /></el-icon> </div> <div class="stat-content"> <div class="stat-value">¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div> <div class="stat-label">销售额</div> <div class="stat-content"> <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div> <div class="stat-label">订单数量</div> </div> <div class="stat-bg-decoration"></div> </div> </el-col> <el-col :xs="24" :sm="12" :md="8"> <div class="stat-card stat-card-green"> <div class="stat-icon-wrapper"> <div class="stat-icon"> <el-icon :size="32"><Tickets /></el-icon> </div> </div> </el-col> <el-col :span="8"> <div class="stat-card"> <div class="stat-icon" style="background: #fef0f0;"> <el-icon :size="30" color="#e6a23c"><Van /></el-icon> </div> <div class="stat-content"> <div class="stat-value">{{ indicatorKpis.shipRate }}%</div> <div class="stat-label">发货率</div> <div class="stat-content"> <div class="stat-value">¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div> <div class="stat-label">销售额</div> </div> <div class="stat-bg-decoration"></div> </div> </el-col> <el-col :xs="24" :sm="12" :md="8"> <div class="stat-card stat-card-orange"> <div class="stat-icon-wrapper"> <div class="stat-icon"> <el-icon :size="32"><Van /></el-icon> </div> </div> </el-col> </el-row> <div class="stat-content"> <div class="stat-value">{{ indicatorKpis.shipRate }}%</div> <div class="stat-label">发货率</div> </div> <div class="stat-bg-decoration"></div> </div> </el-col> </el-row> <!-- 维度筛选 --> <el-row :gutter="20" class="search-row"> <el-col :span="6"> <el-tree-select v-model="indicatorFilter.productCategory" placeholder="产品类别" clearable check-strictly :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-col> <el-col :span="6"> <el-select v-model="indicatorFilter.customerName" placeholder="客户" clearable filterable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName" /> </el-select> </el-col> <el-col :span="6"> <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 100%" /> </el-col> <el-col :span="6" style="text-align: right;"> <el-button type="primary" @click="applyIndicatorFilter">查询</el-button> <el-button @click="resetIndicatorFilter">重置</el-button> </el-col> </el-row> <!-- 图表区 --> <div class="chart-container"> <div ref="indicatorChartRef" class="chart-wrapper"></div> <!-- 图表区(包含筛选条件) --> <el-card class="chart-card" shadow="hover"> <template #header> <div class="card-header"> <div class="header-left"> <span class="card-title">销售趋势分析</span> <span class="card-subtitle">筛选条件仅影响下方图表数据</span> </div> </div> </template> <!-- 图表筛选条件 --> <div class="chart-filter-section"> <el-row :gutter="16" class="search-row"> <el-col :xs="24" :sm="12" :md="6"> <div class="filter-item"> <label class="filter-label">产品类别</label> <el-tree-select v-model="indicatorFilter.productCategory" placeholder="请选择产品类别" clearable check-strictly :data="productOptions" :render-after-expand="false" style="width: 100%" /> </div> </el-col> <el-col :xs="24" :sm="12" :md="6"> <div class="filter-item"> <label class="filter-label">客户</label> <el-select v-model="indicatorFilter.customerName" placeholder="请选择客户" clearable filterable style="width: 100%" > <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName" /> </el-select> </div> </el-col> <el-col :xs="24" :sm="12" :md="6"> <div class="filter-item"> <label class="filter-label">日期范围</label> <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 100%" /> </div> </el-col> <el-col :xs="24" :sm="12" :md="6"> <div class="filter-item filter-buttons"> <el-button type="primary" :loading="loading" @click="applyIndicatorFilter"> <el-icon><Search /></el-icon> 查询图表 </el-button> <el-button @click="resetIndicatorFilter"> <el-icon><Refresh /></el-icon> 重置 </el-button> </div> </el-col> </el-row> </div> <!-- 业绩统计(团队维度,无个人姓名) --> <el-table v-if="showTeamPerformance" :data="teamPerformanceList" border stripe style="margin-top: 20px;"> <el-table-column prop="team" label="销售团队"/> <el-table-column prop="orderCount" label="订单数"/> <el-table-column prop="salesAmount" label="销售额"> <!-- 图表展示区 --> <div class="chart-container" v-loading="loading"> <div ref="indicatorChartRef" class="chart-wrapper"></div> </div> </el-card> <!-- 业绩统计(团队维度,无个人姓名) --> <el-card v-if="showTeamPerformance" class="table-card" shadow="hover"> <template #header> <div class="card-header"> <span class="card-title">团队业绩统计</span> </div> </template> <el-table :data="teamPerformanceList" border stripe style="width: 100%" :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }" > <el-table-column prop="team" label="销售团队" min-width="120"/> <el-table-column prop="orderCount" label="订单数" align="right" min-width="100"/> <el-table-column prop="salesAmount" label="销售额" align="right" min-width="140"> <template #default="scope">¥{{ scope.row.salesAmount.toLocaleString() }}</template> </el-table-column> <el-table-column prop="shipRate" label="发货率"> <template #default="scope">{{ scope.row.shipRate }}</template> <el-table-column prop="shipRate" label="发货率" align="right" min-width="100"> <template #default="scope">{{ scope.row.shipRate }}%</template> </el-table-column> <el-table-column prop="attainment" label="目标达成率"> <el-table-column prop="attainment" label="目标达成率" align="center" min-width="120"> <template #default="scope"> <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'"> <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'" effect="dark" > {{ scope.row.attainment }}% </el-tag> </template> @@ -88,7 +167,7 @@ <script setup> import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' import { Document, Van, Tickets } from '@element-plus/icons-vue' import { Document, Van, Tickets, Search, Refresh } from '@element-plus/icons-vue' import * as echarts from 'echarts' import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats' import { productTreeList } from '@/api/basicData/product.js' @@ -325,10 +404,8 @@ } const applyIndicatorFilter = async () => { await Promise.all([ fetchTotalStatistics(), fetchStatisticsTable() ]) // 筛选条件只影响图表数据,不影响KPI汇总 await fetchStatisticsTable() } const resetIndicatorFilter = () => { @@ -368,31 +445,313 @@ }) </script> <style scoped> <style scoped lang="scss"> .indicator-stats { padding: 0; padding: 20px; background: #f5f7fa; min-height: calc(100vh - 84px); } .box-card { border: none; box-shadow: none; } .search-row { margin-bottom: 20px; } .stats-row { margin-bottom: 24px; } .stat-card { display: flex; align-items: center; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .stat-icon { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; border-radius: 8px; margin-right: 16px; } .stat-content { flex: 1; } .stat-value { font-size: 28px; font-weight: bold; color: #303133; margin-bottom: 4px; } .stat-label { font-size: 14px; color: #909399; } .chart-container { margin: 20px 0; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); .page-header { margin-bottom: 24px; padding: 20px 0; .page-title { font-size: 24px; font-weight: 600; color: #303133; margin: 0 0 8px 0; } .page-desc { font-size: 14px; color: #909399; margin: 0; } } .stats-row { margin-bottom: 24px; } .stat-card { position: relative; display: flex; align-items: center; padding: 24px; background: #fff; border-radius: 12px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); transition: all 0.3s ease; overflow: hidden; &:hover { transform: translateY(-4px); box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12); } .stat-icon-wrapper { margin-right: 20px; .stat-icon { width: 64px; height: 64px; display: flex; align-items: center; justify-content: center; border-radius: 12px; transition: all 0.3s ease; } } .stat-content { flex: 1; z-index: 1; .stat-value { font-size: 32px; font-weight: 700; color: #303133; margin-bottom: 8px; line-height: 1.2; } .stat-label { font-size: 14px; color: #909399; font-weight: 500; } } .stat-bg-decoration { position: absolute; right: -20px; top: -20px; width: 120px; height: 120px; border-radius: 50%; opacity: 0.1; z-index: 0; } &.stat-card-blue { .stat-icon { background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); color: #fff; } .stat-bg-decoration { background: #409eff; } } &.stat-card-green { .stat-icon { background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); color: #fff; } .stat-bg-decoration { background: #67c23a; } } &.stat-card-orange { .stat-icon { background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%); color: #fff; } .stat-bg-decoration { background: #e6a23c; } } } .chart-card, .table-card { margin-bottom: 20px; border-radius: 12px; border: none; :deep(.el-card__header) { padding: 18px 20px; border-bottom: 1px solid #ebeef5; background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); } :deep(.el-card__body) { padding: 0; } } .card-header { display: flex; align-items: center; justify-content: space-between; .header-left { display: flex; flex-direction: column; gap: 4px; } .card-title { font-size: 16px; font-weight: 600; color: #303133; } .card-subtitle { font-size: 12px; color: #909399; font-weight: normal; } } .chart-filter-section { padding: 20px; background: #fafbfc; border-bottom: 1px solid #ebeef5; margin-bottom: 0; } .search-row { .filter-item { margin-bottom: 0; .filter-label { display: block; font-size: 13px; color: #606266; margin-bottom: 8px; font-weight: 500; } &.filter-buttons { display: flex; align-items: flex-end; gap: 10px; padding-top: 28px; .el-button { flex: 1; font-size: 14px; } } } } .chart-container { width: 100%; overflow: hidden; position: relative; padding: 20px; background: #fff; .chart-wrapper { width: 100%; height: 420px; min-width: 0; } } .chart-wrapper { width: 100%; height: 360px; min-width: 0; .table-card { :deep(.el-table) { border-radius: 8px; overflow: hidden; } :deep(.el-table__header-wrapper) { .el-table__header { th { background: #f5f7fa; color: #606266; font-weight: 600; } } } :deep(.el-table__body-wrapper) { .el-table__body { tr:hover { background-color: #f5f7fa; } } } } // 响应式设计 @media (max-width: 768px) { .indicator-stats { padding: 12px; } .stat-card { padding: 20px; .stat-content .stat-value { font-size: 24px; } .stat-icon-wrapper .stat-icon { width: 56px; height: 56px; } } .chart-filter-section { padding: 16px; } .search-row { .filter-item.filter-buttons { padding-top: 0; margin-top: 12px; } } .chart-container { padding: 16px; .chart-wrapper { height: 320px; } } .card-header { .header-left { .card-title { font-size: 15px; } .card-subtitle { font-size: 11px; } } } } @media (max-width: 576px) { .page-header { .page-title { font-size: 20px; } .page-desc { font-size: 12px; } } .stat-card { flex-direction: column; text-align: center; .stat-icon-wrapper { margin-right: 0; margin-bottom: 12px; } } } </style> src/views/salesManagement/salesLedger/index.vue
@@ -6,10 +6,6 @@ <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> </el-form-item> <el-form-item label="客户合同号:"> <el-input v-model="searchForm.customerContractNo" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> </el-form-item> <el-form-item label="销售合同号:"> <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" @change="handleQuery" /> @@ -61,14 +57,20 @@ type="danger">不足</el-tag> </template> </el-table-column> <el-table-column label="发货状态" prop="shippingStatus" width="140" align="center" show-overflow-tooltip /> <el-table-column label="发货状态" width="140" align="center"> <template #default="scope"> <el-tag :type="getShippingStatusType(scope.row)" size="small"> {{ getShippingStatusText(scope.row) }} </el-tag> </template> </el-table-column> <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip /> <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip /> <el-table-column label="发货车牌" minWidth="100px" align="center"> <template #default="scope"> <div> <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag> <el-tag v-else type="info">未发货</el-tag> <el-tag v-else type="info">-</el-tag> </div> </template> </el-table-column> @@ -94,8 +96,8 @@ <el-button link type="primary" size="small" :disabled="scope.row.approveStatus !== 1 || !!scope.row.shippingDate || !!scope.row.shippingCarNumber" size="small" :disabled="!canShip(scope.row)" @click="openDeliveryForm(scope.row)"> 发货 </el-button> @@ -106,7 +108,6 @@ </el-table-column> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column label="销售合同号" prop="salesContractNo" width="180" show-overflow-tooltip /> <el-table-column label="客户合同号" prop="customerContractNo" width="180" show-overflow-tooltip /> <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip /> <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip /> <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip /> @@ -148,11 +149,6 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客户合同号:" prop="customerContractNo"> <el-input v-model="form.customerContractNo" placeholder="请输入" clearable :disabled="operationType === 'view'"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="客户名称:" prop="customerId"> <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'"> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> @@ -163,17 +159,22 @@ </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="项目名称:" prop="projectName"> <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="项目名称:" prop="projectName"> <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="签订日期:" prop="executionDate"> <el-date-picker style="width: 100%" v-model="form.executionDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="请选择" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="付款方式"> <el-input v-model="form.paymentMethod" placeholder="请输入" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> @@ -195,7 +196,6 @@ </el-form-item> </el-col> </el-row> <el-row> <el-form-item label="产品信息:" prop="entryDate"> <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button> @@ -1186,19 +1186,15 @@ const openProductForm = async (type, row, index) => { productOperationType.value = type; productForm.value = {}; modelOptions.value = []; // 清空规格型号选项 proxy.resetForm("productFormRef"); // 确保产品大类数据已加载 const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); if (type === "edit") { productForm.value = { ...row }; productIndex.value = index; // 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表 try { const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const categoryId = findNodeIdByLabel(options, productForm.value.productCategory); if (categoryId) { const models = await modelList({ id: categoryId }); @@ -1215,6 +1211,8 @@ // 加载失败时保持可编辑,不中断弹窗 console.error("加载产品规格型号失败", e); } } else { getProductOptions() } productFormVisible.value = true; }; @@ -1884,6 +1882,92 @@ isCalculating.value = false; }; /** * 获取发货状态文本 * @param row 行数据 */ const getShippingStatusText = (row) => { // 如果已发货(有发货日期或车牌号),显示"已发货" if (row.shippingDate || row.shippingCarNumber) { return '已发货'; } // 获取发货状态字段 const status = row.shippingStatus; // 如果状态为空或未定义,默认为"待发货" if (status === null || status === undefined || status === '') { return '待发货'; } // 状态是字符串 const statusStr = String(status).trim(); const statusTextMap = { '待发货': '待发货', '待审核': '待审核', '审核中': '审核中', '审核拒绝': '审核拒绝', '审核通过': '审核通过', '已发货': '已发货' }; return statusTextMap[statusStr] || '待发货'; }; /** * 获取发货状态标签类型(颜色) * @param row 行数据 */ const getShippingStatusType = (row) => { // 如果已发货(有发货日期或车牌号),显示绿色 if (row.shippingDate || row.shippingCarNumber) { return 'success'; } // 获取发货状态字段 const status = row.shippingStatus; // 如果状态为空或未定义,默认为灰色(待发货) if (status === null || status === undefined || status === '') { return 'info'; } // 状态是字符串 const statusStr = String(status).trim(); const typeTextMap = { '待发货': 'info', '待审核': 'info', '审核中': 'warning', '审核拒绝': 'danger', '审核通过': 'success', '已发货': 'success' }; return typeTextMap[statusStr] || 'info'; }; /** * 判断是否可以发货 * 只有在产品状态是充足,发货状态是待发货和审核拒绝的时候才可以发货 * @param row 行数据 */ const canShip = (row) => { // 产品状态必须是充足(approveStatus === 1) if (row.approveStatus !== 1) { return false; } // 获取发货状态 const shippingStatus = row.shippingStatus; // 如果已发货(有发货日期或车牌号),不能再次发货 if (row.shippingDate || row.shippingCarNumber) { return false; } // 发货状态必须是"待发货"或"审核拒绝" const statusStr = shippingStatus ? String(shippingStatus).trim() : ''; return statusStr === '待发货' || statusStr === '审核拒绝'; }; /** * 下载文件 * * @param row 下载文件的相关信息对象 @@ -1900,15 +1984,12 @@ // 打开发货弹框 const openDeliveryForm = (row) => { // 校验:只有产品状态为充足且未发货时才能发货 if (row.approveStatus !== 1) { proxy.$modal.msgWarning("产品状态不足,无法发货"); // 检查是否可以发货 if (!canShip(row)) { proxy.$modal.msgWarning("只有在产品状态是充足,发货状态是待发货或审核拒绝的时候才可以发货"); return; } if (row.shippingDate || row.shippingCarNumber) { proxy.$modal.msgWarning("该产品已发货,无法重复发货"); return; } currentDeliveryRow.value = row; deliveryForm.value = { type: "货车",