<template>
|
<div class="app-container strategy-control">
|
<el-tabs v-model="activeTab" type="border-card" class="main-tabs" @tab-change="handleTabChange">
|
<!-- 价格策略配置 -->
|
<el-tab-pane label="价格策略配置" name="priceStrategy">
|
<el-card class="box-card">
|
<el-row :gutter="20" class="search-row">
|
<el-col :span="6">
|
<el-select v-model="priceSearchForm.customerName" placeholder="请选择客户" clearable>
|
<el-option label="全部客户" value=""></el-option>
|
<el-option label="华东建材集团" value="华东建材集团"></el-option>
|
<el-option label="长江混凝土公司" value="长江混凝土公司"></el-option>
|
<el-option label="浦江水泥制品厂" value="浦江水泥制品厂"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="priceSearchForm.productType" placeholder="请选择水泥类型" clearable>
|
<el-option label="全部类型" value=""></el-option>
|
<el-option label="普通硅酸盐水泥" value="普通硅酸盐水泥"></el-option>
|
<el-option label="矿渣硅酸盐水泥" value="矿渣硅酸盐水泥"></el-option>
|
<el-option label="复合硅酸盐水泥" value="复合硅酸盐水泥"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="priceSearchForm.strategyType" placeholder="策略类型" clearable>
|
<el-option label="全部策略" value=""></el-option>
|
<el-option label="专属价格" value="专属价格"></el-option>
|
<el-option label="阶梯报价" value="阶梯报价"></el-option>
|
<el-option label="促销折扣" value="促销折扣"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-button type="primary" @click="searchPriceStrategy">查询</el-button>
|
<el-button @click="resetPriceSearch">重置</el-button>
|
<el-button type="primary" @click="handleAddPriceStrategy">新增策略</el-button>
|
</el-col>
|
</el-row>
|
|
<el-table :data="priceStrategyList" border stripe v-loading="priceLoading" height="calc(100vh - 26em)">
|
<el-table-column prop="id" label="ID" width="60" align="center"/>
|
<el-table-column prop="strategyNo" label="策略编号" width="150"/>
|
<el-table-column prop="strategyType" label="策略类型" width="100">
|
<template #default="scope">
|
<el-tag :type="getStrategyTypeColor(scope.row.strategyType)">
|
{{ scope.row.strategyType }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="customerName" label="客户名称" width="180"/>
|
<el-table-column prop="productName" label="产品名称" width="200"/>
|
<el-table-column prop="specification" label="规格型号" width="120"/>
|
<el-table-column prop="basePrice" label="基础价格" width="100">
|
<template #default="scope">
|
¥{{ scope.row.basePrice }}/吨
|
</template>
|
</el-table-column>
|
<el-table-column prop="strategyPrice" label="策略价格" width="120">
|
<template #default="scope">
|
<span style="color: #f56c6c; font-weight: bold;">
|
{{ scope.row.strategyPrice }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="validPeriod" label="有效期" width="200">
|
<template #default="scope">
|
{{ scope.row.startDate }} 至 {{ scope.row.endDate }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="status" label="状态" width="80">
|
<template #default="scope">
|
<el-tag :type="scope.row.status === '生效中' ? 'success' : 'info'">
|
{{ scope.row.status }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
<template #default="scope">
|
<el-button link type="primary" @click="handleViewPriceStrategy(scope.row)">查看</el-button>
|
<el-button link type="primary" @click="handleEditPriceStrategy(scope.row)">编辑</el-button>
|
<el-button link type="danger" @click="handleDeletePriceStrategy(scope.row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<pagination
|
:total="pricePagination.total"
|
:page="pricePagination.currentPage"
|
:limit="pricePagination.pageSize"
|
@pagination="handlePricePageChange"
|
/>
|
</el-card>
|
</el-tab-pane>
|
|
<!-- 合同执行监控 -->
|
<el-tab-pane label="合同执行监控" name="contractMonitor">
|
<el-card class="box-card">
|
<!-- 统计概览 -->
|
<el-row :gutter="20" class="stats-row">
|
<el-col :span="6">
|
<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">{{ contractStats.totalContracts }}</div>
|
<div class="stat-label">合同总数</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #f0f9ff;">
|
<el-icon :size="30" color="#67c23a"><Van /></el-icon>
|
</div>
|
<div class="stat-content">
|
<div class="stat-value">{{ contractStats.deliveryRate }}%</div>
|
<div class="stat-label">交付完成率</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #fef0f0;">
|
<el-icon :size="30" color="#e6a23c"><Tickets /></el-icon>
|
</div>
|
<div class="stat-content">
|
<div class="stat-value">{{ contractStats.invoiceRate }}%</div>
|
<div class="stat-label">发票开具率</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-card">
|
<div class="stat-icon" style="background: #f4f4f5;">
|
<el-icon :size="30" color="#f56c6c"><Wallet /></el-icon>
|
</div>
|
<div class="stat-content">
|
<div class="stat-value">{{ contractStats.paymentRate }}%</div>
|
<div class="stat-label">回款完成率</div>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<!-- 搜索区域 -->
|
<el-row :gutter="20" class="search-row">
|
<el-col :span="6">
|
<el-input v-model="contractSearchForm.contractNo" placeholder="请输入合同编号" clearable>
|
<template #prefix>
|
<el-icon><Search /></el-icon>
|
</template>
|
</el-input>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="contractSearchForm.customerName" placeholder="请选择客户" clearable>
|
<el-option label="华东建材集团" value="华东建材集团"></el-option>
|
<el-option label="长江混凝土公司" value="长江混凝土公司"></el-option>
|
<el-option label="浦江水泥制品厂" value="浦江水泥制品厂"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="contractSearchForm.executionStatus" placeholder="执行状态" clearable>
|
<el-option label="待执行" value="待执行"></el-option>
|
<el-option label="执行中" value="执行中"></el-option>
|
<el-option label="已完成" value="已完成"></el-option>
|
<el-option label="异常" value="异常"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-button type="primary" @click="searchContract">查询</el-button>
|
<el-button @click="resetContractSearch">重置</el-button>
|
</el-col>
|
</el-row>
|
|
<!-- 合同列表 -->
|
<el-table :data="contractList" border stripe v-loading="contractLoading" height="calc(100vh - 36em)">
|
<el-table-column type="expand">
|
<template #default="scope">
|
<div class="contract-detail-expand">
|
<el-steps :active="getContractStep(scope.row)" align-center>
|
<el-step title="订单确认" :description="scope.row.orderDate">
|
<template #icon>
|
<el-icon :color="scope.row.orderStatus === '已完成' ? '#67c23a' : '#909399'">
|
<Check v-if="scope.row.orderStatus === '已完成'" />
|
<Clock v-else />
|
</el-icon>
|
</template>
|
</el-step>
|
<el-step title="货物交付" :description="`${scope.row.deliveryProgress}%`">
|
<template #icon>
|
<el-icon :color="scope.row.deliveryProgress === 100 ? '#67c23a' : '#409eff'">
|
<Check v-if="scope.row.deliveryProgress === 100" />
|
<Van v-else />
|
</el-icon>
|
</template>
|
</el-step>
|
<el-step title="发票开具" :description="`${scope.row.invoiceProgress}%`">
|
<template #icon>
|
<el-icon :color="scope.row.invoiceProgress === 100 ? '#67c23a' : '#e6a23c'">
|
<Check v-if="scope.row.invoiceProgress === 100" />
|
<Tickets v-else />
|
</el-icon>
|
</template>
|
</el-step>
|
<el-step title="款项收回" :description="`${scope.row.paymentProgress}%`">
|
<template #icon>
|
<el-icon :color="scope.row.paymentProgress === 100 ? '#67c23a' : '#f56c6c'">
|
<Check v-if="scope.row.paymentProgress === 100" />
|
<Wallet v-else />
|
</el-icon>
|
</template>
|
</el-step>
|
</el-steps>
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column prop="contractNo" label="合同编号" width="150"/>
|
<el-table-column prop="customerName" label="客户名称" width="180"/>
|
<el-table-column prop="contractAmount" label="合同金额" width="120">
|
<template #default="scope">
|
¥{{ scope.row.contractAmount.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="signDate" label="签订日期" width="120"/>
|
<el-table-column label="执行进度" width="150">
|
<template #default="scope">
|
<el-progress
|
:percentage="scope.row.executionProgress"
|
:color="getProgressColor(scope.row.executionProgress)"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column prop="deliveryProgress" label="交付进度" width="100">
|
<template #default="scope">
|
{{ scope.row.deliveryProgress }}%
|
</template>
|
</el-table-column>
|
<el-table-column prop="invoiceProgress" label="开票进度" width="100">
|
<template #default="scope">
|
{{ scope.row.invoiceProgress }}%
|
</template>
|
</el-table-column>
|
<el-table-column prop="paymentProgress" label="回款进度" width="100">
|
<template #default="scope">
|
{{ scope.row.paymentProgress }}%
|
</template>
|
</el-table-column>
|
<el-table-column prop="executionStatus" label="执行状态" width="100">
|
<template #default="scope">
|
<el-tag :type="getExecutionStatusType(scope.row.executionStatus)">
|
{{ scope.row.executionStatus }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
<template #default="scope">
|
<el-button link type="primary" @click="handleViewContract(scope.row)">查看详情</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<pagination
|
:total="contractPagination.total"
|
:page="contractPagination.currentPage"
|
:limit="contractPagination.pageSize"
|
@pagination="handleContractPageChange"
|
/>
|
</el-card>
|
</el-tab-pane>
|
|
<!-- 历史比价分析 -->
|
<el-tab-pane label="历史比价分析" name="priceComparison">
|
<el-card class="box-card">
|
<el-row :gutter="20" class="search-row">
|
<el-col :span="6">
|
<el-select v-model="compareSearchForm.productName" placeholder="请选择产品" clearable>
|
<el-option label="P.O 42.5普通硅酸盐水泥" value="P.O 42.5普通硅酸盐水泥"></el-option>
|
<el-option label="P.S 32.5矿渣硅酸盐水泥" value="P.S 32.5矿渣硅酸盐水泥"></el-option>
|
<el-option label="P.C 32.5复合硅酸盐水泥" value="P.C 32.5复合硅酸盐水泥"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="8">
|
<el-date-picker
|
v-model="compareSearchForm.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="YYYY-MM-DD"
|
style="width: 100%"
|
/>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="compareSearchForm.region" placeholder="销售区域" clearable>
|
<el-option label="华东地区" value="华东地区"></el-option>
|
<el-option label="华南地区" value="华南地区"></el-option>
|
<el-option label="华北地区" value="华北地区"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="4">
|
<el-button type="primary" @click="searchPriceComparison">查询</el-button>
|
<el-button @click="resetCompareSearch">重置</el-button>
|
</el-col>
|
</el-row>
|
|
<!-- 价格趋势图 -->
|
<div class="chart-container">
|
<div ref="priceChartRef" style="width: 100%; height: 350px;"></div>
|
</div>
|
|
<!-- 历史价格列表 -->
|
<el-table :data="priceComparisonList" border stripe v-loading="compareLoading" style="margin-top: 20px;">
|
<el-table-column prop="date" label="日期" width="120"/>
|
<el-table-column prop="productName" label="产品名称" width="200"/>
|
<el-table-column prop="specification" label="规格" width="120"/>
|
<el-table-column prop="customerName" label="客户" width="180"/>
|
<el-table-column prop="region" label="区域" width="100"/>
|
<el-table-column prop="quantity" label="数量(吨)" width="100" align="right">
|
<template #default="scope">
|
{{ scope.row.quantity.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="price" label="成交单价" width="100">
|
<template #default="scope">
|
¥{{ scope.row.price }}/吨
|
</template>
|
</el-table-column>
|
<el-table-column prop="totalAmount" label="成交金额" width="120">
|
<template #default="scope">
|
¥{{ scope.row.totalAmount.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="priceChange" label="价格变动" width="100">
|
<template #default="scope">
|
<span :style="{ color: scope.row.priceChange > 0 ? '#f56c6c' : scope.row.priceChange < 0 ? '#67c23a' : '#909399' }">
|
{{ scope.row.priceChange > 0 ? '+' : '' }}{{ scope.row.priceChange }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="remark" label="备注" show-overflow-tooltip/>
|
</el-table>
|
</el-card>
|
</el-tab-pane>
|
|
<!-- 利润分析 -->
|
<el-tab-pane label="利润分析" name="profitAnalysis">
|
<el-card class="box-card">
|
<!-- 利润统计卡片 -->
|
<el-row :gutter="20" class="profit-stats-row">
|
<el-col :span="8">
|
<div class="profit-card">
|
<div class="profit-header">总销售额</div>
|
<div class="profit-value">¥{{ profitStats.totalSales.toLocaleString() }}</div>
|
<div class="profit-footer">
|
<span>较上月</span>
|
<span :class="profitStats.salesGrowth > 0 ? 'growth-up' : 'growth-down'">
|
{{ profitStats.salesGrowth > 0 ? '+' : '' }}{{ profitStats.salesGrowth }}%
|
</span>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8">
|
<div class="profit-card">
|
<div class="profit-header">总成本</div>
|
<div class="profit-value">¥{{ profitStats.totalCost.toLocaleString() }}</div>
|
<div class="profit-footer">
|
<span>成本率</span>
|
<span class="cost-rate">{{ profitStats.costRate }}%</span>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8">
|
<div class="profit-card">
|
<div class="profit-header">毛利润</div>
|
<div class="profit-value profit-highlight">¥{{ profitStats.grossProfit.toLocaleString() }}</div>
|
<div class="profit-footer">
|
<span>毛利率</span>
|
<span class="gross-profit-rate">{{ profitStats.grossProfitRate }}%</span>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<!-- 搜索区域 -->
|
<el-row :gutter="20" class="search-row">
|
<el-col :span="6">
|
<el-select v-model="profitSearchForm.productType" placeholder="产品类型" clearable>
|
<el-option label="普通硅酸盐水泥" value="普通硅酸盐水泥"></el-option>
|
<el-option label="矿渣硅酸盐水泥" value="矿渣硅酸盐水泥"></el-option>
|
<el-option label="复合硅酸盐水泥" value="复合硅酸盐水泥"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="6">
|
<el-select v-model="profitSearchForm.customerName" placeholder="客户名称" clearable>
|
<el-option label="华东建材集团" value="华东建材集团"></el-option>
|
<el-option label="长江混凝土公司" value="长江混凝土公司"></el-option>
|
<el-option label="浦江水泥制品厂" value="浦江水泥制品厂"></el-option>
|
</el-select>
|
</el-col>
|
<el-col :span="8">
|
<el-date-picker
|
v-model="profitSearchForm.dateRange"
|
type="monthrange"
|
range-separator="至"
|
start-placeholder="开始月份"
|
end-placeholder="结束月份"
|
value-format="YYYY-MM"
|
style="width: 100%"
|
/>
|
</el-col>
|
<el-col :span="4">
|
<el-button type="primary" @click="searchProfit">查询</el-button>
|
<el-button @click="resetProfitSearch">重置</el-button>
|
</el-col>
|
</el-row>
|
|
<!-- 利润分析图表 -->
|
<div class="chart-container">
|
<div ref="profitChartRef" style="width: 100%; height: 350px;"></div>
|
</div>
|
|
<!-- 利润明细表 -->
|
<el-table :data="profitAnalysisList" border stripe v-loading="profitLoading" style="margin-top: 20px;" show-summary :summary-method="getProfitSummary">
|
<el-table-column prop="orderNo" label="订单编号" width="150"/>
|
<el-table-column prop="customerName" label="客户名称" width="180"/>
|
<el-table-column prop="productName" label="产品名称" width="200"/>
|
<el-table-column prop="quantity" label="数量(吨)" width="100" align="right">
|
<template #default="scope">
|
{{ scope.row.quantity.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="salesPrice" label="销售单价" width="100">
|
<template #default="scope">
|
¥{{ scope.row.salesPrice }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="costPrice" label="成本单价" width="100">
|
<template #default="scope">
|
¥{{ scope.row.costPrice }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="salesAmount" label="销售金额" width="120" align="right">
|
<template #default="scope">
|
¥{{ scope.row.salesAmount.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="costAmount" label="成本金额" width="120" align="right">
|
<template #default="scope">
|
¥{{ scope.row.costAmount.toLocaleString() }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="grossProfit" label="毛利润" width="120" align="right">
|
<template #default="scope">
|
<span :style="{ color: scope.row.grossProfit > 0 ? '#67c23a' : '#f56c6c', fontWeight: 'bold' }">
|
¥{{ scope.row.grossProfit.toLocaleString() }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="grossProfitRate" label="毛利率" width="100">
|
<template #default="scope">
|
<el-tag :type="getProfitRateType(scope.row.grossProfitRate)">
|
{{ scope.row.grossProfitRate }}%
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="orderDate" label="订单日期" width="120"/>
|
</el-table>
|
|
<pagination
|
:total="profitPagination.total"
|
:page="profitPagination.currentPage"
|
:limit="profitPagination.pageSize"
|
@pagination="handleProfitPageChange"
|
/>
|
</el-card>
|
</el-tab-pane>
|
</el-tabs>
|
|
<!-- 价格策略对话框 -->
|
<el-dialog v-model="priceStrategyDialogVisible" :title="priceStrategyDialogTitle" width="900px" :close-on-click-modal="false">
|
<el-form :model="priceStrategyForm" :rules="priceStrategyRules" ref="priceStrategyFormRef" label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="策略类型" prop="strategyType">
|
<el-select v-model="priceStrategyForm.strategyType" placeholder="请选择策略类型" style="width: 100%;">
|
<el-option label="专属价格" value="专属价格"></el-option>
|
<el-option label="阶梯报价" value="阶梯报价"></el-option>
|
<el-option label="促销折扣" value="促销折扣"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="客户名称" prop="customerName">
|
<el-select v-model="priceStrategyForm.customerName" placeholder="请选择客户" style="width: 100%;">
|
<el-option label="华东建材集团" value="华东建材集团"></el-option>
|
<el-option label="长江混凝土公司" value="长江混凝土公司"></el-option>
|
<el-option label="浦江水泥制品厂" value="浦江水泥制品厂"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="产品名称" prop="productName">
|
<el-select v-model="priceStrategyForm.productName" placeholder="请选择产品" style="width: 100%;">
|
<el-option label="P.O 42.5普通硅酸盐水泥" value="P.O 42.5普通硅酸盐水泥"></el-option>
|
<el-option label="P.S 32.5矿渣硅酸盐水泥" value="P.S 32.5矿渣硅酸盐水泥"></el-option>
|
<el-option label="P.C 32.5复合硅酸盐水泥" value="P.C 32.5复合硅酸盐水泥"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="规格型号" prop="specification">
|
<el-input v-model="priceStrategyForm.specification" placeholder="请输入规格型号" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="基础价格(元/吨)" prop="basePrice">
|
<el-input-number v-model="priceStrategyForm.basePrice" :min="0" :precision="2" style="width: 100%;" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="策略价格" prop="strategyPrice">
|
<el-input v-model="priceStrategyForm.strategyPrice" placeholder="如: ¥350/吨 或 9折" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="生效日期" prop="startDate">
|
<el-date-picker
|
v-model="priceStrategyForm.startDate"
|
type="date"
|
placeholder="选择生效日期"
|
style="width: 100%"
|
value-format="YYYY-MM-DD"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="失效日期" prop="endDate">
|
<el-date-picker
|
v-model="priceStrategyForm.endDate"
|
type="date"
|
placeholder="选择失效日期"
|
style="width: 100%"
|
value-format="YYYY-MM-DD"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="策略说明" prop="description">
|
<el-input type="textarea" v-model="priceStrategyForm.description" :rows="3" placeholder="请输入策略说明" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="priceStrategyDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="handleSavePriceStrategy">保存</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, nextTick, watch } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { Document, Van, Tickets, Wallet, Check, Clock, Search } from '@element-plus/icons-vue'
|
import * as echarts from 'echarts'
|
import Pagination from '@/components/PIMTable/Pagination.vue'
|
|
// 活动标签页
|
const activeTab = ref('priceStrategy')
|
|
// ========== 价格策略配置 ==========
|
const priceLoading = ref(false)
|
const priceSearchForm = reactive({
|
customerName: '',
|
productType: '',
|
strategyType: ''
|
})
|
|
const priceStrategyList = ref([
|
{
|
id: 1,
|
strategyNo: 'PS202501001',
|
strategyType: '专属价格',
|
customerName: '华东建材集团',
|
productName: 'P.O 42.5普通硅酸盐水泥',
|
specification: '50kg/袋',
|
basePrice: 380,
|
strategyPrice: '¥350/吨',
|
startDate: '2025-01-01',
|
endDate: '2025-12-31',
|
status: '生效中',
|
description: '战略合作客户专属优惠价格'
|
},
|
{
|
id: 2,
|
strategyNo: 'PS202501002',
|
strategyType: '阶梯报价',
|
customerName: '长江混凝土公司',
|
productName: 'P.S 32.5矿渣硅酸盐水泥',
|
specification: '50kg/袋',
|
basePrice: 320,
|
strategyPrice: '500吨以上9折',
|
startDate: '2025-01-01',
|
endDate: '2025-06-30',
|
status: '生效中',
|
description: '大批量采购阶梯优惠'
|
},
|
{
|
id: 3,
|
strategyNo: 'PS202501003',
|
strategyType: '促销折扣',
|
customerName: '浦江水泥制品厂',
|
productName: 'P.C 32.5复合硅酸盐水泥',
|
specification: '50kg/袋',
|
basePrice: 300,
|
strategyPrice: '8.5折',
|
startDate: '2025-01-15',
|
endDate: '2025-02-28',
|
status: '生效中',
|
description: '春节促销活动'
|
},
|
{
|
id: 4,
|
strategyNo: 'PS202412015',
|
strategyType: '专属价格',
|
customerName: '华东建材集团',
|
productName: 'P.C 32.5复合硅酸盐水泥',
|
specification: '50kg/袋',
|
basePrice: 300,
|
strategyPrice: '¥285/吨',
|
startDate: '2024-10-01',
|
endDate: '2024-12-31',
|
status: '已过期',
|
description: '第四季度专属价格'
|
}
|
])
|
|
const pricePagination = reactive({
|
total: 4,
|
currentPage: 1,
|
pageSize: 10
|
})
|
|
const priceStrategyDialogVisible = ref(false)
|
const priceStrategyDialogTitle = ref('新增价格策略')
|
const priceStrategyForm = reactive({
|
strategyType: '',
|
customerName: '',
|
productName: '',
|
specification: '',
|
basePrice: 0,
|
strategyPrice: '',
|
startDate: '',
|
endDate: '',
|
description: ''
|
})
|
|
const priceStrategyRules = {
|
strategyType: [{ required: true, message: '请选择策略类型', trigger: 'change' }],
|
customerName: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
productName: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
basePrice: [{ required: true, message: '请输入基础价格', trigger: 'blur' }],
|
strategyPrice: [{ required: true, message: '请输入策略价格', trigger: 'blur' }],
|
startDate: [{ required: true, message: '请选择生效日期', trigger: 'change' }],
|
endDate: [{ required: true, message: '请选择失效日期', trigger: 'change' }]
|
}
|
|
const priceStrategyFormRef = ref()
|
|
// ========== 合同执行监控 ==========
|
const contractLoading = ref(false)
|
const contractStats = reactive({
|
totalContracts: 48,
|
deliveryRate: 87.5,
|
invoiceRate: 82.3,
|
paymentRate: 75.6
|
})
|
|
const contractSearchForm = reactive({
|
contractNo: '',
|
customerName: '',
|
executionStatus: ''
|
})
|
|
const contractList = ref([
|
{
|
id: 1,
|
contractNo: 'CT202501001',
|
customerName: '华东建材集团',
|
contractAmount: 2850000,
|
signDate: '2025-01-05',
|
executionProgress: 85,
|
deliveryProgress: 90,
|
invoiceProgress: 85,
|
paymentProgress: 75,
|
executionStatus: '执行中',
|
orderStatus: '已完成',
|
orderDate: '2025-01-05'
|
},
|
{
|
id: 2,
|
contractNo: 'CT202501002',
|
customerName: '长江混凝土公司',
|
contractAmount: 1650000,
|
signDate: '2025-01-08',
|
executionProgress: 95,
|
deliveryProgress: 100,
|
invoiceProgress: 100,
|
paymentProgress: 85,
|
executionStatus: '执行中',
|
orderStatus: '已完成',
|
orderDate: '2025-01-08'
|
},
|
{
|
id: 3,
|
contractNo: 'CT202501003',
|
customerName: '浦江水泥制品厂',
|
contractAmount: 980000,
|
signDate: '2025-01-12',
|
executionProgress: 60,
|
deliveryProgress: 65,
|
invoiceProgress: 60,
|
paymentProgress: 50,
|
executionStatus: '执行中',
|
orderStatus: '已完成',
|
orderDate: '2025-01-12'
|
},
|
{
|
id: 4,
|
contractNo: 'CT202412028',
|
customerName: '华东建材集团',
|
contractAmount: 3200000,
|
signDate: '2024-12-15',
|
executionProgress: 100,
|
deliveryProgress: 100,
|
invoiceProgress: 100,
|
paymentProgress: 100,
|
executionStatus: '已完成',
|
orderStatus: '已完成',
|
orderDate: '2024-12-15'
|
},
|
{
|
id: 5,
|
contractNo: 'CT202501004',
|
customerName: '长江混凝土公司',
|
contractAmount: 750000,
|
signDate: '2025-01-20',
|
executionProgress: 25,
|
deliveryProgress: 30,
|
invoiceProgress: 20,
|
paymentProgress: 0,
|
executionStatus: '异常',
|
orderStatus: '已完成',
|
orderDate: '2025-01-20'
|
}
|
])
|
|
const contractPagination = reactive({
|
total: 5,
|
currentPage: 1,
|
pageSize: 10
|
})
|
|
// ========== 历史比价分析 ==========
|
const compareLoading = ref(false)
|
const compareSearchForm = reactive({
|
productName: '',
|
dateRange: [],
|
region: ''
|
})
|
|
const priceComparisonList = ref([
|
{ date: '2025-01-20', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '华东建材集团', region: '华东地区', quantity: 5000, price: 350, totalAmount: 1750000, priceChange: 0, remark: '长期合作客户' },
|
{ date: '2025-01-15', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '浦东新区建筑公司', region: '华东地区', quantity: 3000, price: 365, totalAmount: 1095000, priceChange: +15, remark: '现款现货' },
|
{ date: '2025-01-10', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '长江混凝土公司', region: '华东地区', quantity: 8000, price: 345, totalAmount: 2760000, priceChange: -5, remark: '大批量优惠' },
|
{ date: '2025-01-05', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '江苏工程集团', region: '华东地区', quantity: 4500, price: 360, totalAmount: 1620000, priceChange: +10, remark: '工程项目专用' },
|
{ date: '2024-12-28', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '华东建材集团', region: '华东地区', quantity: 6000, price: 355, totalAmount: 2130000, priceChange: +5, remark: '年底备货' },
|
{ date: '2024-12-20', productName: 'P.O 42.5普通硅酸盐水泥', specification: '50kg/袋', customerName: '上海市政工程', region: '华东地区', quantity: 10000, price: 340, totalAmount: 3400000, priceChange: -10, remark: '政府项目' }
|
])
|
|
const priceChartRef = ref(null)
|
let priceChart = null
|
|
// ========== 利润分析 ==========
|
const profitLoading = ref(false)
|
const profitStats = reactive({
|
totalSales: 15680000,
|
totalCost: 11256000,
|
grossProfit: 4424000,
|
grossProfitRate: 28.2,
|
salesGrowth: 12.5,
|
costRate: 71.8
|
})
|
|
const profitSearchForm = reactive({
|
productType: '',
|
customerName: '',
|
dateRange: []
|
})
|
|
const profitAnalysisList = ref([
|
{ orderNo: 'SO202501015', customerName: '华东建材集团', productName: 'P.O 42.5普通硅酸盐水泥', quantity: 5000, salesPrice: 350, costPrice: 245, salesAmount: 1750000, costAmount: 1225000, grossProfit: 525000, grossProfitRate: 30.0, orderDate: '2025-01-20' },
|
{ orderNo: 'SO202501012', customerName: '长江混凝土公司', productName: 'P.S 32.5矿渣硅酸盐水泥', quantity: 3500, salesPrice: 288, costPrice: 210, salesAmount: 1008000, costAmount: 735000, grossProfit: 273000, grossProfitRate: 27.1, orderDate: '2025-01-18' },
|
{ orderNo: 'SO202501008', customerName: '浦江水泥制品厂', productName: 'P.C 32.5复合硅酸盐水泥', quantity: 2800, salesPrice: 255, costPrice: 185, salesAmount: 714000, costAmount: 518000, grossProfit: 196000, grossProfitRate: 27.5, orderDate: '2025-01-15' },
|
{ orderNo: 'SO202501005', customerName: '华东建材集团', productName: 'P.O 42.5普通硅酸盐水泥', quantity: 6000, salesPrice: 350, costPrice: 248, salesAmount: 2100000, costAmount: 1488000, grossProfit: 612000, grossProfitRate: 29.1, orderDate: '2025-01-10' },
|
{ orderNo: 'SO202501003', customerName: '江苏工程集团', productName: 'P.O 42.5普通硅酸盐水泥', quantity: 4500, salesPrice: 360, costPrice: 250, salesAmount: 1620000, costAmount: 1125000, grossProfit: 495000, grossProfitRate: 30.6, orderDate: '2025-01-08' },
|
{ orderNo: 'SO202412025', customerName: '长江混凝土公司', productName: 'P.S 32.5矿渣硅酸盐水泥', quantity: 8000, salesPrice: 290, costPrice: 215, salesAmount: 2320000, costAmount: 1720000, grossProfit: 600000, grossProfitRate: 25.9, orderDate: '2024-12-28' }
|
])
|
|
const profitPagination = reactive({
|
total: 6,
|
currentPage: 1,
|
pageSize: 10
|
})
|
|
const profitChartRef = ref(null)
|
let profitChart = null
|
|
// ========== 方法 ==========
|
|
// 价格策略相关方法
|
const getStrategyTypeColor = (type) => {
|
const colorMap = {
|
'专属价格': 'success',
|
'阶梯报价': 'primary',
|
'促销折扣': 'warning'
|
}
|
return colorMap[type] || 'info'
|
}
|
|
const searchPriceStrategy = () => {
|
priceLoading.value = true
|
setTimeout(() => {
|
priceLoading.value = false
|
}, 500)
|
}
|
|
const resetPriceSearch = () => {
|
priceSearchForm.customerName = ''
|
priceSearchForm.productType = ''
|
priceSearchForm.strategyType = ''
|
}
|
|
const handleAddPriceStrategy = () => {
|
priceStrategyDialogTitle.value = '新增价格策略'
|
resetPriceStrategyForm()
|
priceStrategyDialogVisible.value = true
|
}
|
|
const handleViewPriceStrategy = (row) => {
|
ElMessage.info('查看策略详情: ' + row.strategyNo)
|
}
|
|
const handleEditPriceStrategy = (row) => {
|
priceStrategyDialogTitle.value = '编辑价格策略'
|
Object.assign(priceStrategyForm, row)
|
priceStrategyDialogVisible.value = true
|
}
|
|
const handleDeletePriceStrategy = (row) => {
|
ElMessageBox.confirm('确认删除该价格策略吗?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
ElMessage.success('删除成功')
|
})
|
}
|
|
const resetPriceStrategyForm = () => {
|
Object.keys(priceStrategyForm).forEach(key => {
|
if (key === 'basePrice') {
|
priceStrategyForm[key] = 0
|
} else {
|
priceStrategyForm[key] = ''
|
}
|
})
|
}
|
|
const handleSavePriceStrategy = () => {
|
priceStrategyFormRef.value.validate((valid) => {
|
if (valid) {
|
ElMessage.success('保存成功')
|
priceStrategyDialogVisible.value = false
|
}
|
})
|
}
|
|
const handlePricePageChange = (val) => {
|
pricePagination.currentPage = val.page
|
pricePagination.pageSize = val.limit
|
}
|
|
// 合同执行监控相关方法
|
const getExecutionStatusType = (status) => {
|
const statusMap = {
|
'待执行': 'info',
|
'执行中': 'primary',
|
'已完成': 'success',
|
'异常': 'danger'
|
}
|
return statusMap[status] || 'info'
|
}
|
|
const getProgressColor = (percentage) => {
|
if (percentage < 30) return '#f56c6c'
|
if (percentage < 70) return '#e6a23c'
|
return '#67c23a'
|
}
|
|
const getContractStep = (row) => {
|
if (row.paymentProgress === 100) return 4
|
if (row.invoiceProgress === 100) return 3
|
if (row.deliveryProgress === 100) return 2
|
if (row.orderStatus === '已完成') return 1
|
return 0
|
}
|
|
const searchContract = () => {
|
contractLoading.value = true
|
setTimeout(() => {
|
contractLoading.value = false
|
}, 500)
|
}
|
|
const resetContractSearch = () => {
|
contractSearchForm.contractNo = ''
|
contractSearchForm.customerName = ''
|
contractSearchForm.executionStatus = ''
|
}
|
|
const handleViewContract = (row) => {
|
ElMessage.info('查看合同详情: ' + row.contractNo)
|
}
|
|
const handleContractPageChange = (val) => {
|
contractPagination.currentPage = val.page
|
contractPagination.pageSize = val.limit
|
}
|
|
// 历史比价分析相关方法
|
const searchPriceComparison = () => {
|
compareLoading.value = true
|
setTimeout(() => {
|
compareLoading.value = false
|
initPriceChart()
|
}, 500)
|
}
|
|
const resetCompareSearch = () => {
|
compareSearchForm.productName = ''
|
compareSearchForm.dateRange = []
|
compareSearchForm.region = ''
|
}
|
|
const initPriceChart = () => {
|
if (!priceChartRef.value) return
|
|
if (priceChart) {
|
priceChart.dispose()
|
}
|
|
priceChart = echarts.init(priceChartRef.value)
|
|
const option = {
|
title: {
|
text: '水泥价格趋势分析',
|
left: 'center'
|
},
|
tooltip: {
|
trigger: 'axis',
|
formatter: '{b}<br/>{a}: ¥{c}/吨'
|
},
|
legend: {
|
data: ['P.O 42.5普通硅酸盐水泥'],
|
top: 30
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
data: ['2024-12-20', '2024-12-28', '2025-01-05', '2025-01-10', '2025-01-15', '2025-01-20']
|
},
|
yAxis: {
|
type: 'value',
|
name: '价格(元/吨)',
|
min: 330,
|
max: 370
|
},
|
series: [
|
{
|
name: 'P.O 42.5普通硅酸盐水泥',
|
type: 'line',
|
data: [340, 355, 360, 345, 365, 350],
|
smooth: true,
|
itemStyle: {
|
color: '#409eff'
|
},
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
|
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
|
])
|
}
|
}
|
]
|
}
|
|
priceChart.setOption(option)
|
}
|
|
// 利润分析相关方法
|
const searchProfit = () => {
|
profitLoading.value = true
|
setTimeout(() => {
|
profitLoading.value = false
|
initProfitChart()
|
}, 500)
|
}
|
|
const resetProfitSearch = () => {
|
profitSearchForm.productType = ''
|
profitSearchForm.customerName = ''
|
profitSearchForm.dateRange = []
|
}
|
|
const getProfitRateType = (rate) => {
|
if (rate >= 30) return 'success'
|
if (rate >= 25) return 'warning'
|
return 'danger'
|
}
|
|
const getProfitSummary = (param) => {
|
const { columns, data } = param
|
const sums = []
|
columns.forEach((column, index) => {
|
if (index === 0) {
|
sums[index] = '合计'
|
return
|
}
|
if (['quantity', 'salesAmount', 'costAmount', 'grossProfit'].includes(column.property)) {
|
const values = data.map(item => Number(item[column.property]))
|
if (!values.every(value => isNaN(value))) {
|
const total = values.reduce((prev, curr) => {
|
const value = Number(curr)
|
if (!isNaN(value)) {
|
return prev + curr
|
} else {
|
return prev
|
}
|
}, 0)
|
sums[index] = column.property === 'quantity' ? total.toLocaleString() : '¥' + total.toLocaleString()
|
}
|
} else if (column.property === 'grossProfitRate') {
|
// 计算平均毛利率
|
const totalSales = data.reduce((sum, item) => sum + item.salesAmount, 0)
|
const totalProfit = data.reduce((sum, item) => sum + item.grossProfit, 0)
|
sums[index] = ((totalProfit / totalSales) * 100).toFixed(1) + '%'
|
}
|
})
|
return sums
|
}
|
|
const initProfitChart = () => {
|
if (!profitChartRef.value) return
|
|
if (profitChart) {
|
profitChart.dispose()
|
}
|
|
profitChart = echarts.init(profitChartRef.value)
|
|
const option = {
|
title: {
|
text: '销售与利润趋势分析',
|
left: 'center'
|
},
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross',
|
crossStyle: {
|
color: '#999'
|
}
|
}
|
},
|
legend: {
|
data: ['销售金额', '成本金额', '毛利润', '毛利率'],
|
top: 30
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: [
|
{
|
type: 'category',
|
data: ['2024-12', '2025-01'],
|
axisPointer: {
|
type: 'shadow'
|
}
|
}
|
],
|
yAxis: [
|
{
|
type: 'value',
|
name: '金额(万元)',
|
axisLabel: {
|
formatter: '{value}'
|
}
|
},
|
{
|
type: 'value',
|
name: '毛利率(%)',
|
min: 0,
|
max: 40,
|
axisLabel: {
|
formatter: '{value}%'
|
}
|
}
|
],
|
series: [
|
{
|
name: '销售金额',
|
type: 'bar',
|
data: [820, 950],
|
itemStyle: {
|
color: '#409eff'
|
}
|
},
|
{
|
name: '成本金额',
|
type: 'bar',
|
data: [605, 670],
|
itemStyle: {
|
color: '#e6a23c'
|
}
|
},
|
{
|
name: '毛利润',
|
type: 'bar',
|
data: [215, 280],
|
itemStyle: {
|
color: '#67c23a'
|
}
|
},
|
{
|
name: '毛利率',
|
type: 'line',
|
yAxisIndex: 1,
|
data: [26.2, 29.5],
|
itemStyle: {
|
color: '#f56c6c'
|
}
|
}
|
]
|
}
|
|
profitChart.setOption(option)
|
}
|
|
const handleProfitPageChange = (val) => {
|
profitPagination.currentPage = val.page
|
profitPagination.pageSize = val.limit
|
}
|
|
// 生命周期
|
onMounted(() => {
|
// 组件挂载后不立即初始化图表,等待用户切换到对应标签页
|
})
|
|
// 监听标签页切换
|
watch(activeTab, (newVal) => {
|
nextTick(() => {
|
if (newVal === 'priceComparison') {
|
initPriceChart()
|
} else if (newVal === 'profitAnalysis') {
|
initProfitChart()
|
}
|
})
|
})
|
|
const handleTabChange = () => {
|
// 标签页切换处理
|
}
|
</script>
|
|
<style scoped>
|
.strategy-control {
|
padding: 0;
|
}
|
|
.main-tabs {
|
border: none;
|
box-shadow: none;
|
}
|
|
.main-tabs :deep(.el-tabs__content) {
|
padding: 0;
|
}
|
|
.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;
|
}
|
|
/* 合同详情展开样式 */
|
.contract-detail-expand {
|
padding: 30px 60px;
|
background: #f5f7fa;
|
}
|
|
.contract-detail-expand :deep(.el-step__title) {
|
font-size: 14px;
|
}
|
|
.contract-detail-expand :deep(.el-step__description) {
|
font-size: 12px;
|
margin-top: 4px;
|
}
|
|
/* 利润统计卡片 */
|
.profit-stats-row {
|
margin-bottom: 24px;
|
}
|
|
.profit-card {
|
padding: 24px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
border-radius: 12px;
|
color: #fff;
|
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
}
|
|
.profit-card:nth-child(2) .profit-card {
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
}
|
|
.profit-card:nth-child(3) .profit-card {
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
}
|
|
.profit-header {
|
font-size: 14px;
|
opacity: 0.9;
|
margin-bottom: 12px;
|
}
|
|
.profit-value {
|
font-size: 32px;
|
font-weight: bold;
|
margin-bottom: 12px;
|
}
|
|
.profit-highlight {
|
color: #fff;
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
}
|
|
.profit-footer {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
font-size: 13px;
|
opacity: 0.9;
|
}
|
|
.growth-up {
|
color: #fff;
|
font-weight: bold;
|
}
|
|
.growth-down {
|
color: #ffd04b;
|
font-weight: bold;
|
}
|
|
.cost-rate, .gross-profit-rate {
|
font-weight: bold;
|
}
|
|
/* 图表容器 */
|
.chart-container {
|
margin: 20px 0;
|
padding: 20px;
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
</style>
|