You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
571 lines
18 KiB
571 lines
18 KiB
<template>
|
|
<div class="customer-css part-price-analysis">
|
|
<el-form :inline="true" label-position="top" class="pi-search-form">
|
|
<el-form-item label="开始时间" required>
|
|
<el-date-picker
|
|
v-model="searchData.startDate"
|
|
type="date"
|
|
format="yyyy-MM-dd"
|
|
value-format="yyyy-MM-dd"
|
|
placeholder="开始时间"
|
|
style="width: 150px"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="结束时间">
|
|
<el-date-picker
|
|
v-model="searchData.endDate"
|
|
type="date"
|
|
format="yyyy-MM-dd"
|
|
value-format="yyyy-MM-dd"
|
|
placeholder="结束时间"
|
|
style="width: 150px"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item required>
|
|
<span style="cursor: pointer" slot="label" @click="getBaseList(2017)"><a href="javascript:void(0)">产品编码</a></span>
|
|
<el-input
|
|
v-model="searchData.partNo"
|
|
placeholder="产品编码"
|
|
style="width: 160px"
|
|
clearable
|
|
@keyup.enter.native="handleSearch"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<span style="cursor: pointer" slot="label" @click="getBaseList(520)"><a href="javascript:void(0)">供应商编码</a></span>
|
|
<el-input
|
|
v-model="searchData.supplierId"
|
|
placeholder="不填则按产品查询"
|
|
style="width: 160px"
|
|
clearable
|
|
@keyup.enter.native="handleSearch"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label=" ">
|
|
<el-button type="primary" class="customer-bun-min" :loading="loading" @click="handleSearch">查询</el-button>
|
|
<el-button class="customer-bun-min" @click="resetSearch">重置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-form v-if="showProductInfo" :inline="true" label-position="top" class="pi-product-form">
|
|
<el-form-item label="产品编码">
|
|
<el-input :value="displayText(productInfo.partNo || searchData.partNo)" disabled style="width: 160px" />
|
|
</el-form-item>
|
|
<el-form-item label="产品名称">
|
|
<el-input :value="displayText(productInfo.partDesc)" disabled style="width: 220px" />
|
|
</el-form-item>
|
|
<el-form-item label="Item">
|
|
<el-input :value="displayText(productInfo.item)" disabled style="width: 120px" />
|
|
</el-form-item>
|
|
<el-form-item label="SKU">
|
|
<el-input :value="displayText(productInfo.sku)" disabled style="width: 120px" />
|
|
</el-form-item>
|
|
<el-form-item label="物料分类">
|
|
<el-input :value="displayText(productInfo.categoryName)" disabled style="width: 160px" />
|
|
</el-form-item>
|
|
<el-form-item label="物料二级分类">
|
|
<el-input :value="displayText(productInfo.categoryLevel2Name)" disabled style="width: 160px" />
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-table
|
|
:data="dataList"
|
|
:height="tableHeight"
|
|
border
|
|
stripe
|
|
v-loading="loading"
|
|
style="width: 100%"
|
|
empty-text="请填写查询条件后点击查询"
|
|
>
|
|
<el-table-column fixed="left" label="操作" width="90" header-align="center" align="center">
|
|
<template slot-scope="scope">
|
|
<el-link style="cursor: pointer" @click="openPriceSheet(scope.row)">价格表</el-link>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="price" label="价格" min-width="80" header-align="center" align="right">
|
|
<template slot-scope="scope">
|
|
{{ formatMoney(scope.row.price) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="priceChangeRate" label="涨跌" min-width="100" header-align="center" align="center">
|
|
<template slot-scope="scope">
|
|
<span :class="getPriceChangeClass(scope.row.priceChangeRate)">
|
|
{{ scope.row.priceChangeRate || '-' }}
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="priceDate" label="价格日期" min-width="160" header-align="center" align="center">
|
|
<template slot-scope="scope">
|
|
{{ formatDateOnly(scope.row.priceDate) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="supplierId" label="供应商编码" min-width="120" header-align="center" align="left" show-overflow-tooltip />
|
|
<el-table-column prop="supplierName" label="供应商名称" min-width="200" header-align="center" align="left" show-overflow-tooltip />
|
|
</el-table>
|
|
|
|
<el-card shadow="never" class="chart-card">
|
|
<div slot="header" class="chart-card__header">
|
|
<span>价格趋势(供应商历史价格)</span>
|
|
</div>
|
|
<div id="partPriceChart" class="chart-box" :style="{ height: chartHeight + 'px' }"></div>
|
|
</el-card>
|
|
|
|
<el-dialog
|
|
title="价格表"
|
|
:visible.sync="priceSheetDialogVisible"
|
|
width="600px"
|
|
:close-on-click-modal="false"
|
|
v-drag
|
|
append-to-body
|
|
>
|
|
<el-form :inline="true" label-position="top" class="pi-price-sheet-form">
|
|
<el-form-item label="产品">
|
|
<el-input :value="displayText(priceSheetHeader.productText)" disabled style="width: 190px" />
|
|
</el-form-item>
|
|
<el-form-item label="价格日期">
|
|
<el-input :value="displayText(formatDateOnly(priceSheetHeader.priceDate))" disabled style="width: 80px" />
|
|
</el-form-item>
|
|
<!-- <el-form-item label="供应商编码">-->
|
|
<!-- <el-input :value="displayText(priceSheetHeader.supplierId)" disabled style="width: 100px" />-->
|
|
<!-- </el-form-item>-->
|
|
<el-form-item label="供应商名称">
|
|
<el-input :value="displayText(priceSheetHeader.supplierName)" disabled style="width: 180px" />
|
|
</el-form-item>
|
|
<el-form-item label="币种">
|
|
<el-input :value="displayText(priceSheetHeader.currency)" disabled style="width: 70px" />
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-table
|
|
:data="priceSheetDetailList"
|
|
:height="300"
|
|
border
|
|
stripe
|
|
v-loading="priceSheetLoading"
|
|
style="width: 100%"
|
|
>
|
|
<el-table-column prop="qtyStart" label="开始数量" min-width="80" header-align="center" align="right">
|
|
<template slot-scope="scope">
|
|
{{ formatNumber(scope.row.qtyStart) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="qtyEnd" label="结束数量" min-width="80" header-align="center" align="right">
|
|
<template slot-scope="scope">
|
|
{{ formatNumber(scope.row.qtyEnd) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="unitPrice" label="单价" min-width="80" header-align="center" align="right">
|
|
<template slot-scope="scope">
|
|
{{ formatNumber(scope.row.unitPrice) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="remark" label="备注" min-width="150" header-align="center" align="left" show-overflow-tooltip />
|
|
</el-table>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button type="primary" @click="priceSheetDialogVisible = false">关闭</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<Chooselist
|
|
ref="baseList"
|
|
@getBaseData="getBaseData">
|
|
</Chooselist>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import echarts from 'echarts'
|
|
import { searchPartPriceAnalysis } from '@/api/supplier/partPriceAnalysis'
|
|
import { listTierPriceHistoryDetail } from '@/api/supplier/supplierPartMoqPrice'
|
|
import Chooselist from '@/views/modules/common/Chooselist_eam'
|
|
|
|
function getDefaultStartDate () {
|
|
const date = new Date()
|
|
date.setMonth(date.getMonth() - 3)
|
|
const pad = num => String(num).padStart(2, '0')
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
|
}
|
|
|
|
function createEmptyProductInfo () {
|
|
return {
|
|
partNo: '',
|
|
partDesc: '',
|
|
item: '',
|
|
sku: '',
|
|
categoryName: '',
|
|
categoryLevel2Name: ''
|
|
}
|
|
}
|
|
|
|
export default {
|
|
name: 'PartPriceAnalysis',
|
|
components: {
|
|
Chooselist
|
|
},
|
|
data () {
|
|
return {
|
|
loading: false,
|
|
tagNo: '',
|
|
dataList: [],
|
|
searchData: {
|
|
site: this.$store.state.user.site,
|
|
startDate: getDefaultStartDate(),
|
|
endDate: '',
|
|
partNo: '',
|
|
supplierId: ''
|
|
},
|
|
showProductInfo: false,
|
|
productInfo: createEmptyProductInfo(),
|
|
priceSheetDialogVisible: false,
|
|
priceSheetLoading: false,
|
|
priceSheetHeader: {
|
|
productText: '',
|
|
priceDate: '',
|
|
supplierId: '',
|
|
supplierName: '',
|
|
currency: '',
|
|
historyMainId: null
|
|
},
|
|
priceSheetDetailList: [],
|
|
tableHeight: 280,
|
|
chartHeight: 320,
|
|
chartInstance: null
|
|
}
|
|
},
|
|
mounted () {
|
|
this.$nextTick(() => {
|
|
this.updateLayout()
|
|
this.initChart()
|
|
})
|
|
window.addEventListener('resize', this.handleResize)
|
|
},
|
|
activated () {
|
|
if (this.chartInstance) {
|
|
this.chartInstance.resize()
|
|
}
|
|
},
|
|
beforeDestroy () {
|
|
window.removeEventListener('resize', this.handleResize)
|
|
if (this.chartInstance) {
|
|
this.chartInstance.dispose()
|
|
this.chartInstance = null
|
|
}
|
|
},
|
|
methods: {
|
|
updateLayout () {
|
|
const availableHeight = window.innerHeight - 220
|
|
this.tableHeight = Math.max(220, Math.floor(availableHeight * 0.45))
|
|
this.chartHeight = Math.max(260, Math.floor(availableHeight * 0.5))
|
|
},
|
|
handleResize () {
|
|
this.updateLayout()
|
|
if (this.chartInstance) {
|
|
this.chartInstance.resize()
|
|
}
|
|
},
|
|
initChart () {
|
|
const dom = document.getElementById('partPriceChart')
|
|
if (!dom) {
|
|
return
|
|
}
|
|
if (this.chartInstance) {
|
|
this.chartInstance.dispose()
|
|
}
|
|
this.chartInstance = echarts.init(dom)
|
|
this.renderChart([])
|
|
},
|
|
resetSearch () {
|
|
this.searchData = {
|
|
site: this.$store.state.user.site,
|
|
startDate: getDefaultStartDate(),
|
|
endDate: '',
|
|
partNo: '',
|
|
supplierId: ''
|
|
}
|
|
this.showProductInfo = false
|
|
this.productInfo = createEmptyProductInfo()
|
|
this.dataList = []
|
|
this.renderChart([])
|
|
},
|
|
getBaseList (val) {
|
|
this.tagNo = val
|
|
this.$nextTick(() => {
|
|
let strVal = ''
|
|
let conSql = ''
|
|
if (val === 520) {
|
|
strVal = this.searchData.supplierId || ''
|
|
conSql = " and supplier_no <> '' "
|
|
} else if (val === 2017) {
|
|
strVal = this.searchData.partNo || ''
|
|
conSql = " and part_no <> '' "
|
|
}
|
|
this.$refs.baseList.init(val, strVal, conSql)
|
|
})
|
|
},
|
|
getBaseData (val) {
|
|
if (this.tagNo === 520) {
|
|
this.searchData.supplierId = val.supplier_no || val.code || ''
|
|
} else if (this.tagNo === 2017) {
|
|
this.searchData.partNo = val.part_no || val.code || ''
|
|
}
|
|
},
|
|
validateSearch () {
|
|
if (!this.searchData.startDate) {
|
|
this.$message.warning('请选择开始时间')
|
|
return false
|
|
}
|
|
if (!this.searchData.partNo || !this.searchData.partNo.trim()) {
|
|
this.$message.warning('请输入产品编码')
|
|
return false
|
|
}
|
|
if (this.searchData.endDate && this.searchData.startDate > this.searchData.endDate) {
|
|
this.$message.warning('开始时间不能大于结束时间')
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
handleSearch () {
|
|
if (!this.validateSearch()) {
|
|
return
|
|
}
|
|
this.loading = true
|
|
const params = {
|
|
site: this.$store.state.user.site,
|
|
startDate: this.searchData.startDate,
|
|
endDate: this.searchData.endDate || '',
|
|
partNo: this.searchData.partNo.trim(),
|
|
supplierId: this.searchData.supplierId ? this.searchData.supplierId.trim() : ''
|
|
}
|
|
searchPartPriceAnalysis(params).then(({ data }) => {
|
|
if (data && data.code === 0) {
|
|
this.showProductInfo = true
|
|
this.productInfo = Object.assign(createEmptyProductInfo(), data.productInfo || {}, {
|
|
partNo: (data.productInfo && data.productInfo.partNo) || params.partNo
|
|
})
|
|
this.dataList = data.list || []
|
|
this.renderChart(this.dataList)
|
|
if (this.dataList.length === 0) {
|
|
this.$message.info('未查询到历史价格记录')
|
|
}
|
|
} else {
|
|
this.$message.error((data && data.msg) || '查询失败')
|
|
}
|
|
this.loading = false
|
|
}).catch(() => {
|
|
this.loading = false
|
|
this.$message.error('请求失败')
|
|
})
|
|
},
|
|
openPriceSheet (row) {
|
|
if (!row || !row.historyMainId) {
|
|
this.$message.warning('当前记录未找到关联价格表')
|
|
return
|
|
}
|
|
const productText = [row.partNo, row.partDesc].filter(Boolean).join(' / ')
|
|
this.priceSheetHeader = {
|
|
productText,
|
|
priceDate: row.priceDate || '',
|
|
supplierId: row.supplierId || '',
|
|
supplierName: row.supplierName || '',
|
|
currency: row.currency || '',
|
|
historyMainId: row.historyMainId
|
|
}
|
|
this.priceSheetDetailList = []
|
|
this.priceSheetDialogVisible = true
|
|
this.priceSheetLoading = true
|
|
const params = {
|
|
site: this.$store.state.user.site,
|
|
historyMainId: row.historyMainId
|
|
}
|
|
listTierPriceHistoryDetail(params).then(({ data }) => {
|
|
if (data && data.code === 0) {
|
|
this.priceSheetDetailList = data.rows || []
|
|
} else {
|
|
this.priceSheetDetailList = []
|
|
this.$message.error((data && data.msg) || '获取价格表明细失败')
|
|
}
|
|
this.priceSheetLoading = false
|
|
}).catch(() => {
|
|
this.priceSheetLoading = false
|
|
this.priceSheetDetailList = []
|
|
this.$message.error('获取价格表明细失败')
|
|
})
|
|
},
|
|
renderChart (list) {
|
|
if (!this.chartInstance) {
|
|
this.initChart()
|
|
}
|
|
const chartData = [...list].sort((a, b) => {
|
|
return new Date(a.priceDate).getTime() - new Date(b.priceDate).getTime()
|
|
})
|
|
const xAxisData = chartData.map(item => this.formatChartDate(item.priceDate))
|
|
const seriesData = chartData.map(item => item.price)
|
|
const titleSuffix = this.searchData.supplierId
|
|
? `${this.searchData.partNo} / ${this.searchData.supplierId}`
|
|
: this.searchData.partNo
|
|
|
|
const option = {
|
|
title: {
|
|
text: chartData.length ? `产品价格趋势 - ${titleSuffix}` : '暂无数据',
|
|
left: 'center',
|
|
textStyle: {
|
|
fontSize: 14,
|
|
fontWeight: 'normal'
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
formatter: (params) => {
|
|
if (!params || !params.length) {
|
|
return ''
|
|
}
|
|
const index = params[0].dataIndex
|
|
const row = chartData[index] || {}
|
|
return [
|
|
`价格日期:${this.formatDateOnly(row.priceDate)}`,
|
|
`价格:${this.formatMoney(row.price)}`,
|
|
`供应商:${row.supplierId || '-'} ${row.supplierName || ''}`
|
|
].join('<br/>')
|
|
}
|
|
},
|
|
grid: {
|
|
left: '3%',
|
|
right: '4%',
|
|
bottom: '12%',
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
boundaryGap: false,
|
|
data: xAxisData,
|
|
axisLabel: {
|
|
rotate: xAxisData.length > 8 ? 35 : 0
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '价格',
|
|
scale: true
|
|
},
|
|
series: [{
|
|
name: '价格',
|
|
type: 'line',
|
|
smooth: true,
|
|
symbol: 'circle',
|
|
symbolSize: 6,
|
|
data: seriesData,
|
|
connectNulls: true
|
|
}]
|
|
}
|
|
this.chartInstance.setOption(option, true)
|
|
this.$nextTick(() => {
|
|
if (this.chartInstance) {
|
|
this.chartInstance.resize()
|
|
}
|
|
})
|
|
},
|
|
formatMoney (value) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return '-'
|
|
}
|
|
return new Intl.NumberFormat('zh-CN', {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).format(value)
|
|
},
|
|
displayText (value) {
|
|
return value === null || value === undefined || value === '' ? '-' : value
|
|
},
|
|
formatNumber (value) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return '-'
|
|
}
|
|
const num = Number(value)
|
|
if (Number.isNaN(num)) {
|
|
return value
|
|
}
|
|
return String(Object.is(num, -0) ? 0 : num)
|
|
},
|
|
getPriceChangeClass (value) {
|
|
if (!value || value === '-') {
|
|
return ''
|
|
}
|
|
if (value.startsWith('+')) {
|
|
return 'price-up'
|
|
}
|
|
if (value.startsWith('-')) {
|
|
return 'price-down'
|
|
}
|
|
return ''
|
|
},
|
|
formatChartDate (value) {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value
|
|
}
|
|
const pad = num => String(num).padStart(2, '0')
|
|
const year = String(date.getFullYear()).slice(-2)
|
|
return `${year}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
|
},
|
|
formatDateTime (value) {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value
|
|
}
|
|
const pad = num => String(num).padStart(2, '0')
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
},
|
|
formatDateOnly (value) {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value
|
|
}
|
|
const pad = num => String(num).padStart(2, '0')
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.part-price-analysis .chart-card {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.part-price-analysis .pi-product-form {
|
|
margin-top: 2px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.part-price-analysis .pi-price-sheet-form {
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.part-price-analysis .chart-card__header {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.part-price-analysis .chart-box {
|
|
width: 100%;
|
|
}
|
|
|
|
.part-price-analysis .price-up {
|
|
color: #f56c6c;
|
|
}
|
|
|
|
.part-price-analysis .price-down {
|
|
color: #67c23a;
|
|
}
|
|
</style>
|