|
@@ -1,25 +1,28 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="page">
|
|
<div class="page">
|
|
|
- <!-- 查询条件:站点 -> 分类 -> 省份 -> 城市 级联;物料/规格/含税按需求不提供筛选 -->
|
|
|
|
|
<el-form :inline="true" class="query-form" ref="searchForm" :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
|
|
<el-form :inline="true" class="query-form" ref="searchForm" :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
|
|
|
- <el-form-item label="站点" prop="siteCode">
|
|
|
|
|
- <el-select v-model="searchForm.siteCode" placeholder="全部" clearable style="width: 150px" @change="handleSiteChange">
|
|
|
|
|
- <el-option v-for="item in options.sites" :key="item.siteCode" :label="item.siteName" :value="item.siteCode"></el-option>
|
|
|
|
|
- </el-select>
|
|
|
|
|
|
|
+<!-- <el-form-item label="站点" prop="siteCode">-->
|
|
|
|
|
+<!-- <el-select v-model="searchForm.siteCode" placeholder="全部" clearable style="width: 150px" @change="handleSiteChange">-->
|
|
|
|
|
+<!-- <el-option v-for="item in options.sites" :key="item.siteCode" :label="item.siteName" :value="item.siteCode"></el-option>-->
|
|
|
|
|
+<!-- </el-select>-->
|
|
|
|
|
+<!-- </el-form-item>-->
|
|
|
|
|
+ <el-form-item label="物料" prop="materialName">
|
|
|
|
|
+ <el-input v-model="searchForm.materialName" placeholder="输入关键字模糊查询" clearable style="width: 180px"></el-input>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="分类" prop="category">
|
|
<el-form-item label="分类" prop="category">
|
|
|
- <el-select v-model="searchForm.category" placeholder="全部" clearable style="width: 140px" @change="handleCategoryChange">
|
|
|
|
|
|
|
+ <!-- filterable:输入文字即可从选项里过滤,不输入时仍按下拉选择 -->
|
|
|
|
|
+ <el-select v-model="searchForm.category" placeholder="全部" clearable filterable style="width: 140px" @change="handleCategoryChange">
|
|
|
<el-option v-for="item in options.categories" :key="item" :label="item" :value="item"></el-option>
|
|
<el-option v-for="item in options.categories" :key="item" :label="item" :value="item"></el-option>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item label="省份" prop="province">
|
|
<el-form-item label="省份" prop="province">
|
|
|
- <el-select v-model="searchForm.province" placeholder="全部" clearable style="width: 120px" @change="handleProvinceChange">
|
|
|
|
|
|
|
+ <el-select v-model="searchForm.province" placeholder="全部" clearable filterable style="width: 120px" @change="handleProvinceChange">
|
|
|
<el-option v-for="item in options.provinces" :key="item" :label="item" :value="item"></el-option>
|
|
<el-option v-for="item in options.provinces" :key="item" :label="item" :value="item"></el-option>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<!-- 城市按所选省份级联;站点没有城市信息时该下拉为空 -->
|
|
<!-- 城市按所选省份级联;站点没有城市信息时该下拉为空 -->
|
|
|
<el-form-item label="城市" prop="city">
|
|
<el-form-item label="城市" prop="city">
|
|
|
- <el-select v-model="searchForm.city" placeholder="全部" clearable style="width: 120px">
|
|
|
|
|
|
|
+ <el-select v-model="searchForm.city" placeholder="全部" clearable filterable style="width: 120px">
|
|
|
<el-option v-for="item in options.cities" :key="item" :label="item" :value="item"></el-option>
|
|
<el-option v-for="item in options.cities" :key="item" :label="item" :value="item"></el-option>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
@@ -42,16 +45,13 @@
|
|
|
</el-form>
|
|
</el-form>
|
|
|
|
|
|
|
|
<div class="jp-table">
|
|
<div class="jp-table">
|
|
|
- <!-- custom:列设置;refresh:右上角刷新按钮 -->
|
|
|
|
|
<vxe-toolbar :refresh="{query: refreshList}" custom>
|
|
<vxe-toolbar :refresh="{query: refreshList}" custom>
|
|
|
<template #buttons>
|
|
<template #buttons>
|
|
|
- <!-- 立即采集只对管理员角色显示(后端 @PreAuthorize 是最终防线) -->
|
|
|
|
|
|
|
+ <!-- 只用文字、不加图标:保证按钮内文字水平居中 -->
|
|
|
<el-button
|
|
<el-button
|
|
|
v-if="hasPermission('program:materialPrice:sync')"
|
|
v-if="hasPermission('program:materialPrice:sync')"
|
|
|
type="primary"
|
|
type="primary"
|
|
|
- icon="el-icon-refresh"
|
|
|
|
|
- :loading="syncing"
|
|
|
|
|
- @click="syncNow()">立即采集</el-button>
|
|
|
|
|
|
|
+ @click="openRangeDialog()">采集历史数据</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</vxe-toolbar>
|
|
</vxe-toolbar>
|
|
|
<div class="jp-table-body">
|
|
<div class="jp-table-body">
|
|
@@ -69,7 +69,6 @@
|
|
|
:menu-config="{}"
|
|
:menu-config="{}"
|
|
|
:data="dataList">
|
|
:data="dataList">
|
|
|
<vxe-column type="seq" width="60" title="序号"></vxe-column>
|
|
<vxe-column type="seq" width="60" title="序号"></vxe-column>
|
|
|
- <vxe-column title="站点" field="siteName" width="130" align="left"></vxe-column>
|
|
|
|
|
<vxe-column title="分类" field="category" width="100" align="left"></vxe-column>
|
|
<vxe-column title="分类" field="category" width="100" align="left"></vxe-column>
|
|
|
<!-- 省份所有站点都有;城市只有部分站点提供,缺失显示「—」 -->
|
|
<!-- 省份所有站点都有;城市只有部分站点提供,缺失显示「—」 -->
|
|
|
<vxe-column title="省份" field="province" width="100" align="left"></vxe-column>
|
|
<vxe-column title="省份" field="province" width="100" align="left"></vxe-column>
|
|
@@ -80,7 +79,6 @@
|
|
|
<vxe-column title="规格" width="110" align="left">
|
|
<vxe-column title="规格" width="110" align="left">
|
|
|
<template #default="scope">{{ scope.row.spec || '—' }}</template>
|
|
<template #default="scope">{{ scope.row.spec || '—' }}</template>
|
|
|
</vxe-column>
|
|
</vxe-column>
|
|
|
- <!-- 三类价格按站点不同:废废网只有价格,中果/回收商网只有最低/最高价,缺失显示「—」 -->
|
|
|
|
|
<vxe-column title="价格" width="100" align="right">
|
|
<vxe-column title="价格" width="100" align="right">
|
|
|
<template #default="scope">{{ formatPrice(scope.row.price) }}</template>
|
|
<template #default="scope">{{ formatPrice(scope.row.price) }}</template>
|
|
|
</vxe-column>
|
|
</vxe-column>
|
|
@@ -99,7 +97,6 @@
|
|
|
<el-tag v-else size="small" type="info">未声明</el-tag>
|
|
<el-tag v-else size="small" type="info">未声明</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</vxe-column>
|
|
</vxe-column>
|
|
|
- <!-- 备注、采集时间仅后端返回,页面不再展示 -->
|
|
|
|
|
<vxe-column title="数据日期" field="priceDate" width="110" align="center"></vxe-column>
|
|
<vxe-column title="数据日期" field="priceDate" width="110" align="center"></vxe-column>
|
|
|
<vxe-column title="来源" width="70" align="center">
|
|
<vxe-column title="来源" width="70" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
@@ -107,6 +104,7 @@
|
|
|
<span v-else>—</span>
|
|
<span v-else>—</span>
|
|
|
</template>
|
|
</template>
|
|
|
</vxe-column>
|
|
</vxe-column>
|
|
|
|
|
+ <vxe-column title="站点" field="siteName" width="130" align="left"></vxe-column>
|
|
|
<vxe-column title="操作" width="90" fixed="right" align="center">
|
|
<vxe-column title="操作" width="90" fixed="right" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
<el-button text type="primary" @click="showTrend(scope.row)">价格走势</el-button>
|
|
<el-button text type="primary" @click="showTrend(scope.row)">价格走势</el-button>
|
|
@@ -125,6 +123,65 @@
|
|
|
</vxe-pager>
|
|
</vxe-pager>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史区间采集:提交后后台执行,这里每 3 秒轮询一次进度;关闭弹窗不会中断采集 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ title="历史数据采集"
|
|
|
|
|
+ v-model="rangeDialogVisible"
|
|
|
|
|
+ width="520px"
|
|
|
|
|
+ :close-on-click-modal="false">
|
|
|
|
|
+ <!-- 采集中:进度条 + 已入库/跳过条数 -->
|
|
|
|
|
+ <div v-if="rangeRunning" class="range-running">
|
|
|
|
|
+ <el-progress :percentage="rangePercent" :stroke-width="14"></el-progress>
|
|
|
|
|
+ <div class="range-line">
|
|
|
|
|
+ 已完成 {{ rangeDone }} / {{ rangeTotal || '?' }} 个分片
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="range-line">当前:{{ rangeCurrentStage || '准备中' }}</div>
|
|
|
|
|
+ <div class="range-line">
|
|
|
|
|
+ 已入库 {{ rangeLog.savedCount || 0 }} 条,跳过 {{ rangeLog.failedCount || 0 }} 条
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="range-tip">采集在后台执行,关闭弹窗不会中断;可以稍后重新打开查看进度。</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 未在采集:选择区间后提交 -->
|
|
|
|
|
+ <div v-else class="range-form">
|
|
|
|
|
+ <el-form label-width="80px">
|
|
|
|
|
+ <el-form-item label="开始日期">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="rangeStartDate"
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ placeholder="请选择开始日期"
|
|
|
|
|
+ :disabled-date="disabledStartDate"
|
|
|
|
|
+ style="width: 220px">
|
|
|
|
|
+ </el-date-picker>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="结束日期">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="rangeEndDate"
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ placeholder="请选择结束日期"
|
|
|
|
|
+ :disabled-date="disabledEndDate"
|
|
|
|
|
+ style="width: 220px">
|
|
|
|
|
+ </el-date-picker>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <div class="range-tip">
|
|
|
|
|
+ 单次采集的时间总跨度不能超过 {{ maxRangeDays }} 天;结束日期最晚为昨天。区间采集只补历史数据;每天的增量数据由定时任务自动采集。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="lastRangeSummary" class="range-tip">{{ lastRangeSummary }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="rangeDialogVisible = false">关闭</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="!rangeRunning"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :loading="rangeSubmitting"
|
|
|
|
|
+ :disabled="!rangeStartDate || !rangeEndDate"
|
|
|
|
|
+ @click="submitRange()">开始采集</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
<MaterialPriceTrendDialog ref="trendDialog"></MaterialPriceTrendDialog>
|
|
<MaterialPriceTrendDialog ref="trendDialog"></MaterialPriceTrendDialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -139,6 +196,7 @@
|
|
|
// 查询条件;priceDates 为空数组时后端不会按日期过滤
|
|
// 查询条件;priceDates 为空数组时后端不会按日期过滤
|
|
|
searchForm: {
|
|
searchForm: {
|
|
|
siteCode: '',
|
|
siteCode: '',
|
|
|
|
|
+ materialName: '',
|
|
|
category: '',
|
|
category: '',
|
|
|
province: '',
|
|
province: '',
|
|
|
city: '',
|
|
city: '',
|
|
@@ -159,8 +217,68 @@
|
|
|
orders: []
|
|
orders: []
|
|
|
},
|
|
},
|
|
|
loading: false,
|
|
loading: false,
|
|
|
- // 采集按钮的 loading,避免重复点击
|
|
|
|
|
- syncing: false
|
|
|
|
|
|
|
+ // 单次区间采集允许的最大时间总跨度(天),与后端 material-price.range.max-days 保持一致
|
|
|
|
|
+ maxRangeDays: 180,
|
|
|
|
|
+ // 历史区间采集弹窗:开始/结束日期分开选
|
|
|
|
|
+ rangeDialogVisible: false,
|
|
|
|
|
+ rangeStartDate: '',
|
|
|
|
|
+ rangeEndDate: '',
|
|
|
|
|
+ rangeSubmitting: false,
|
|
|
|
|
+ // 后端锁或本进程标记为执行中
|
|
|
|
|
+ rangeRunning: false,
|
|
|
|
|
+ // 最近一次区间采集日志(material_price_sync_log)
|
|
|
|
|
+ rangeLog: {
|
|
|
|
|
+ batchNo: '',
|
|
|
|
|
+ status: '',
|
|
|
|
|
+ progressDone: 0,
|
|
|
|
|
+ progressTotal: 0,
|
|
|
|
|
+ currentStage: '',
|
|
|
|
|
+ savedCount: 0,
|
|
|
|
|
+ failedCount: 0,
|
|
|
|
|
+ errorMsg: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ // 本次正在观察的批次号;用来区分「刚提交的批次」和历史批次,避免重复提示
|
|
|
|
|
+ watchingBatchNo: '',
|
|
|
|
|
+ // 已经提示过结果的批次号
|
|
|
|
|
+ finishedBatchNo: '',
|
|
|
|
|
+ // 进度轮询定时器
|
|
|
|
|
+ pollTimer: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ // 进度条百分比:执行中最多显示 99%,避免没完全结束就显示 100%
|
|
|
|
|
+ rangePercent () {
|
|
|
|
|
+ const total = this.rangeLog.progressTotal || 0
|
|
|
|
|
+ if (!total) return 0
|
|
|
|
|
+ return Math.min(99, Math.floor((this.rangeLog.progressDone || 0) * 100 / total))
|
|
|
|
|
+ },
|
|
|
|
|
+ rangeDone () {
|
|
|
|
|
+ return this.rangeLog.progressDone || 0
|
|
|
|
|
+ },
|
|
|
|
|
+ rangeTotal () {
|
|
|
|
|
+ return this.rangeLog.progressTotal || 0
|
|
|
|
|
+ },
|
|
|
|
|
+ rangeCurrentStage () {
|
|
|
|
|
+ return this.rangeLog.currentStage || ''
|
|
|
|
|
+ },
|
|
|
|
|
+ // 弹窗里展示的「最近一次采集」摘要
|
|
|
|
|
+ lastRangeSummary () {
|
|
|
|
|
+ const log = this.rangeLog
|
|
|
|
|
+ if (!log || !log.batchNo || log.status === 'RUNNING') return ''
|
|
|
|
|
+ const text = { SUCCESS: '成功', PARTIAL: '部分失败', NO_DATA: '无数据', FAILED: '失败' }[log.status] || log.status
|
|
|
|
|
+ return `最近一次:${log.rangeStart || '?'} 至 ${log.rangeEnd || '?'},${text},入库 ${log.savedCount || 0} 条`
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ // 改开始日期后,如果已选的结束日期早于开始日期或跨度超限,就清掉让用户重选
|
|
|
|
|
+ rangeStartDate (value) {
|
|
|
|
|
+ if (!value || !this.rangeEndDate) return
|
|
|
|
|
+ const start = new Date(value + 'T00:00:00')
|
|
|
|
|
+ const latest = new Date(start.getTime() + (this.maxRangeDays - 1) * 24 * 3600 * 1000)
|
|
|
|
|
+ const end = new Date(this.rangeEndDate + 'T00:00:00')
|
|
|
|
|
+ if (end.getTime() < start.getTime() || end.getTime() > latest.getTime()) {
|
|
|
|
|
+ this.rangeEndDate = ''
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
materialPriceService: null,
|
|
materialPriceService: null,
|
|
@@ -173,10 +291,21 @@
|
|
|
},
|
|
},
|
|
|
mounted () {
|
|
mounted () {
|
|
|
this.refreshList()
|
|
this.refreshList()
|
|
|
|
|
+ // 页面刷新时可能刚好有区间任务在跑,恢复轮询
|
|
|
|
|
+ this.resumeRangeWatch()
|
|
|
},
|
|
},
|
|
|
// 项目启用了 keep-alive,从其它页面切回来时刷新一次
|
|
// 项目启用了 keep-alive,从其它页面切回来时刷新一次
|
|
|
activated () {
|
|
activated () {
|
|
|
this.refreshList()
|
|
this.refreshList()
|
|
|
|
|
+ this.resumeRangeWatch()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 离开页面时停止轮询,避免后台一直发请求
|
|
|
|
|
+ deactivated () {
|
|
|
|
|
+ this.stopPolling()
|
|
|
|
|
+ },
|
|
|
|
|
+ // Vue 3 用 beforeUnmount,写 beforeDestroy 不会触发,轮询定时器会留到页面销毁后
|
|
|
|
|
+ beforeUnmount () {
|
|
|
|
|
+ this.stopPolling()
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
// 拉取下拉选项:传 siteCode/category/province 做级联收窄
|
|
// 拉取下拉选项:传 siteCode/category/province 做级联收窄
|
|
@@ -246,30 +375,167 @@
|
|
|
this.loadOptions()
|
|
this.loadOptions()
|
|
|
this.refreshList()
|
|
this.refreshList()
|
|
|
},
|
|
},
|
|
|
- // 手动采集:确认后调用后端,部分站点失败仍算成功但会提示
|
|
|
|
|
- syncNow () {
|
|
|
|
|
- this.$confirm('将立即从三个网站抓取最新材料价格,可能耗时 1 分钟左右,是否继续?', '提示', {
|
|
|
|
|
- confirmButtonText: '确定',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- }).then(() => {
|
|
|
|
|
- this.syncing = true
|
|
|
|
|
- this.materialPriceService.sync().then((res) => {
|
|
|
|
|
- this.syncing = false
|
|
|
|
|
- if (res && res.code === 200) {
|
|
|
|
|
- const suffix = res.partial ? '(部分站点抓取失败)' : ''
|
|
|
|
|
- this.$message.success(`采集完成,入库 ${res.saved || 0} 条${suffix}`)
|
|
|
|
|
- this.tablePage.currentPage = 1
|
|
|
|
|
- this.loadOptions()
|
|
|
|
|
- this.refreshList()
|
|
|
|
|
- } else {
|
|
|
|
|
- this.$message.error((res && res.msg) || '采集失败')
|
|
|
|
|
|
|
+ // 开始日期限制:只能选昨天及更早;已选结束日期时,还要保证 start <= end 且跨度不超过 maxRangeDays
|
|
|
|
|
+ disabledStartDate (time) {
|
|
|
|
|
+ const today = new Date()
|
|
|
|
|
+ today.setHours(0, 0, 0, 0)
|
|
|
|
|
+ if (time.getTime() >= today.getTime()) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.rangeEndDate) {
|
|
|
|
|
+ const end = new Date(this.rangeEndDate + 'T00:00:00').getTime()
|
|
|
|
|
+ const earliest = end - (this.maxRangeDays - 1) * 24 * 3600 * 1000
|
|
|
|
|
+ return time.getTime() > end || time.getTime() < earliest
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ },
|
|
|
|
|
+ // 结束日期限制:必须早于今天(最晚昨天);已选开始日期时,还要落在 [start, start + maxRangeDays - 1] 之内
|
|
|
|
|
+ disabledEndDate (time) {
|
|
|
|
|
+ const today = new Date()
|
|
|
|
|
+ today.setHours(0, 0, 0, 0)
|
|
|
|
|
+ if (time.getTime() >= today.getTime()) {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.rangeStartDate) {
|
|
|
|
|
+ const start = new Date(this.rangeStartDate + 'T00:00:00').getTime()
|
|
|
|
|
+ const latest = start + (this.maxRangeDays - 1) * 24 * 3600 * 1000
|
|
|
|
|
+ return time.getTime() < start || time.getTime() > latest
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ },
|
|
|
|
|
+ // 打开历史采集弹窗:日期默认留空,必须手动选择;同时探测一次后端状态
|
|
|
|
|
+ openRangeDialog () {
|
|
|
|
|
+ this.rangeStartDate = ''
|
|
|
|
|
+ this.rangeEndDate = ''
|
|
|
|
|
+ this.rangeDialogVisible = true
|
|
|
|
|
+ this.fetchRangeStatus()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 提交区间采集:接口立即返回,之后靠轮询拿进度
|
|
|
|
|
+ submitRange () {
|
|
|
|
|
+ if (!this.rangeStartDate || !this.rangeEndDate) {
|
|
|
|
|
+ this.$message.warning('请选择开始日期和结束日期')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.rangeEndDate < this.rangeStartDate) {
|
|
|
|
|
+ this.$message.warning('结束日期不能早于开始日期')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 结束日期必须早于今天,今天的增量交给定时任务
|
|
|
|
|
+ const today = new Date()
|
|
|
|
|
+ today.setHours(0, 0, 0, 0)
|
|
|
|
|
+ if (new Date(this.rangeEndDate + 'T00:00:00').getTime() >= today.getTime()) {
|
|
|
|
|
+ this.$message.warning('结束日期必须早于今天')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const startDate = this.rangeStartDate
|
|
|
|
|
+ const endDate = this.rangeEndDate
|
|
|
|
|
+ // 后端会再校验一次跨度上限,这里提前给出提示
|
|
|
|
|
+ const days = Math.round((new Date(endDate + 'T00:00:00') - new Date(startDate + 'T00:00:00')) / 86400000) + 1
|
|
|
|
|
+ if (days > this.maxRangeDays) {
|
|
|
|
|
+ this.$message.warning(`单次采集跨度最多 ${this.maxRangeDays} 天,当前选择 ${days} 天`)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.rangeSubmitting = true
|
|
|
|
|
+ this.materialPriceService.syncRange(startDate, endDate).then((res) => {
|
|
|
|
|
+ this.rangeSubmitting = false
|
|
|
|
|
+ if (res && res.code === 200) {
|
|
|
|
|
+ // 记录本次批次,轮询结束后只对这个批次弹一次结果
|
|
|
|
|
+ this.watchingBatchNo = res.batchNo
|
|
|
|
|
+ this.finishedBatchNo = ''
|
|
|
|
|
+ this.rangeRunning = true
|
|
|
|
|
+ this.rangeLog = {
|
|
|
|
|
+ batchNo: res.batchNo,
|
|
|
|
|
+ status: 'RUNNING',
|
|
|
|
|
+ progressDone: res.progressDone || 0,
|
|
|
|
|
+ progressTotal: res.progressTotal || 0,
|
|
|
|
|
+ currentStage: '排队中',
|
|
|
|
|
+ savedCount: 0,
|
|
|
|
|
+ failedCount: 0,
|
|
|
|
|
+ errorMsg: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ this.startPolling()
|
|
|
|
|
+ this.$message.success('已提交,正在后台采集')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message.error((res && res.msg) || '提交失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.rangeSubmitting = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ // 每 3 秒查一次进度;间隔可按需调整
|
|
|
|
|
+ startPolling () {
|
|
|
|
|
+ this.stopPolling()
|
|
|
|
|
+ this.pollTimer = setInterval(() => {
|
|
|
|
|
+ this.fetchRangeStatus()
|
|
|
|
|
+ }, 3000)
|
|
|
|
|
+ },
|
|
|
|
|
+ stopPolling () {
|
|
|
|
|
+ if (this.pollTimer) {
|
|
|
|
|
+ clearInterval(this.pollTimer)
|
|
|
|
|
+ this.pollTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 查询最近一次区间采集状态;结束后停止轮询并提示结果
|
|
|
|
|
+ fetchRangeStatus () {
|
|
|
|
|
+ this.materialPriceService.syncStatus().then((res) => {
|
|
|
|
|
+ if (!res) return
|
|
|
|
|
+ // 天数上限以后端配置为准,失败时沿用本地默认值
|
|
|
|
|
+ if (res.maxDays) {
|
|
|
|
|
+ this.maxRangeDays = res.maxDays
|
|
|
|
|
+ }
|
|
|
|
|
+ this.rangeRunning = !!res.running
|
|
|
|
|
+ if (res.log) {
|
|
|
|
|
+ this.rangeLog = res.log
|
|
|
|
|
+ if (this.rangeRunning && !this.watchingBatchNo) {
|
|
|
|
|
+ this.watchingBatchNo = res.log.batchNo
|
|
|
}
|
|
}
|
|
|
- }).catch(() => {
|
|
|
|
|
- this.syncing = false
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.rangeRunning) {
|
|
|
|
|
+ if (!this.pollTimer) {
|
|
|
|
|
+ this.startPolling()
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.stopPolling()
|
|
|
|
|
+ this.handleRangeFinished()
|
|
|
|
|
+ }
|
|
|
}).catch(() => {})
|
|
}).catch(() => {})
|
|
|
},
|
|
},
|
|
|
|
|
+ // 页面激活时检查是否有区间任务在跑,有则恢复轮询
|
|
|
|
|
+ resumeRangeWatch () {
|
|
|
|
|
+ this.materialPriceService.syncRunning().then((res) => {
|
|
|
|
|
+ if (res && res.running) {
|
|
|
|
|
+ this.rangeRunning = true
|
|
|
|
|
+ this.watchingBatchNo = res.batchNo || this.watchingBatchNo
|
|
|
|
|
+ this.startPolling()
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(() => {})
|
|
|
|
|
+ },
|
|
|
|
|
+ // 区间采集结束:按状态提示,并刷新列表与下拉选项
|
|
|
|
|
+ handleRangeFinished () {
|
|
|
|
|
+ const log = this.rangeLog || {}
|
|
|
|
|
+ if (!log.batchNo || log.status === 'RUNNING' || log.batchNo === this.finishedBatchNo) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 不是本次观察的批次(比如打开页面时看到的旧批次),只记录不弹提示
|
|
|
|
|
+ if (log.batchNo !== this.watchingBatchNo) {
|
|
|
|
|
+ this.watchingBatchNo = log.batchNo
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.finishedBatchNo = log.batchNo
|
|
|
|
|
+ const saved = log.savedCount || 0
|
|
|
|
|
+ if (log.status === 'SUCCESS') {
|
|
|
|
|
+ this.$message.success(`区间采集完成,入库 ${saved} 条`)
|
|
|
|
|
+ } else if (log.status === 'PARTIAL') {
|
|
|
|
|
+ this.$message.warning(`区间采集完成(部分分片失败),入库 ${saved} 条`)
|
|
|
|
|
+ } else if (log.status === 'NO_DATA') {
|
|
|
|
|
+ this.$message.info('区间采集完成,所选区间没有抓取到数据')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message.error(`区间采集失败:${log.errorMsg || '未知原因'}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ this.tablePage.currentPage = 1
|
|
|
|
|
+ this.loadOptions()
|
|
|
|
|
+ this.refreshList()
|
|
|
|
|
+ },
|
|
|
// 打开走势弹窗,条件由当前行带入
|
|
// 打开走势弹窗,条件由当前行带入
|
|
|
showTrend (row) {
|
|
showTrend (row) {
|
|
|
this.$refs.trendDialog.init(row)
|
|
this.$refs.trendDialog.init(row)
|
|
@@ -285,3 +551,17 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+ .range-line {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .range-tip {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|