xyc
昨天 2f3c2d2851f7693357feb9e62a56a64670755c6a
traffic-audit-web/src/views/HolidayStat.vue
@@ -84,6 +84,69 @@
          <el-card shadow="never" style="margin-top:14px">
            <div slot="header">
              <span>往年国省道数据调整</span>
            </div>
            <div class="operation-row">
              <div class="operation-item">
                <span class="label">年份</span>
                <el-select v-model="adjustmentYear" style="width:120px" @change="loadAdjustments">
                  <el-option v-for="item in availableAdjustmentYears" :key="item" :label="item + '年'" :value="item"></el-option>
                </el-select>
              </div>
              <div v-if="operatorIsAdmin" class="operation-item">
                <el-button type="primary" icon="el-icon-plus" @click="openAdjustmentDialog()">新增调整</el-button>
              </div>
            </div>
            <el-table :data="adjustments" border stripe size="small" style="margin-top:12px" v-loading="adjustmentLoading">
              <el-table-column prop="year" label="年份" width="90" align="center"></el-table-column>
              <el-table-column prop="flowDate" label="日期" width="130" align="center"></el-table-column>
              <el-table-column label="调整前国省道车流量(万辆次)" width="240" align="right">
                <template slot-scope="s">{{ formatOriginalAdjustmentValue(s.row.originalValue) }}</template>
              </el-table-column>
              <el-table-column label="调整后国省道车流量(万辆次)" width="240" align="right">
                <template slot-scope="s">{{ formatAdjustmentValue(s.row.adjustedValue) }}</template>
              </el-table-column>
              <el-table-column prop="reason" label="调整原因" min-width="280" show-overflow-tooltip></el-table-column>
              <el-table-column label="更新时间" min-width="170"><template slot-scope="s">{{ formatDateTime(s.row.updatedAt) }}</template></el-table-column>
              <el-table-column v-if="operatorIsAdmin" label="操作" width="130" align="center">
                <template slot-scope="s">
                  <el-button type="text" @click="openAdjustmentDialog(s.row)">编辑</el-button>
                  <el-button type="text" class="danger-text" @click="deleteAdjustment(s.row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-dialog :title="adjustmentForm.id ? '编辑调整' : '新增调整'" :visible.sync="adjustmentDialogVisible" width="560px" v-dialog-drag>
              <el-form label-width="220px">
                <el-form-item label="年份">
                  <el-select v-model="adjustmentForm.year" style="width:100%" @change="onAdjustmentYearChange">
                    <el-option v-for="item in availableAdjustmentYears" :key="item" :label="item + '年'" :value="item"></el-option>
                  </el-select>
                </el-form-item>
                <el-form-item label="日期">
                  <el-date-picker v-model="adjustmentForm.flowDate" type="date" value-format="yyyy-MM-dd"
                                  style="width:100%" :picker-options="adjustmentDatePickerOptions" placeholder="选择日期"
                                  @change="onAdjustmentDateChange"></el-date-picker>
                </el-form-item>
                <el-form-item label="调整前国省道车流量(万辆次)">
                  <el-input :value="formatOriginalAdjustmentValue(adjustmentForm.originalValue)" readonly
                            :placeholder="adjustmentOriginalLoading ? '查询中...' : ''"></el-input>
                </el-form-item>
                <el-form-item label="调整后车流量(万辆次)">
                  <el-input-number v-model="adjustmentForm.adjustedValue" :min="0" :precision="4" :step="0.0001" controls-position="right" style="width:100%"></el-input-number>
                </el-form-item>
                <el-form-item label="调整原因">
                  <el-input v-model="adjustmentForm.reason" type="textarea" :rows="3" maxlength="500" show-word-limit></el-input>
                </el-form-item>
              </el-form>
              <div slot="footer">
                <el-button @click="adjustmentDialogVisible = false">取消</el-button>
                <el-button type="primary" :loading="adjustmentSaving" @click="saveAdjustment">保存</el-button>
              </div>
            </el-dialog>
          </el-card>
          <el-card shadow="never" style="margin-top:14px">
            <div slot="header">
              <span>观测站分日调查数据</span>
              <span class="hint">用于原始数据留存,不与上方节假日统计数据关联</span>
            </div>
@@ -188,19 +251,44 @@
      observationDialogVisible: false,
      observationDialogDate: '',
      observationRows: [],
      observationRowsLoading: false
      observationRowsLoading: false,
      adjustmentYear: new Date().getFullYear() - 1,
      adjustments: [],
      adjustmentLoading: false,
      adjustmentDialogVisible: false,
      adjustmentSaving: false,
      adjustmentOriginalLoading: false,
      adjustmentOriginalRequestId: 0,
      adjustmentForm: {
        id: null,
        year: new Date().getFullYear() - 1,
        flowDate: '',
        originalValue: null,
        adjustedValue: null,
        reason: ''
      }
    }
  },
  computed: {
    holidayTypeLabel() {
      const item = this.holidayTypeOptions.find(x => x.value === this.holidayType)
      return item ? item.label : this.holidayType
    },
    availableAdjustmentYears() {
      const currentYear = new Date().getFullYear()
      return this.years.filter(item => item < currentYear)
    },
    adjustmentDatePickerOptions() {
      return {
        disabledDate: date => date.getFullYear() !== Number(this.adjustmentForm.year)
      }
    }
  },
  mounted() {
    this.loadOptions()
    this.loadOverview()
    this.loadObservationDates()
    this.loadAdjustments()
  },
  methods: {
    previousDateString() {
@@ -225,6 +313,110 @@
        })
        .catch(err => this.showError(err, '节假日数据加载失败'))
        .finally(() => { this.loading = false })
    },
    loadAdjustments() {
      this.adjustmentLoading = true
      api.get('/holiday/adjustments', { params: { year: this.adjustmentYear } })
        .then(res => {
          if (res.code !== 200) throw new Error(res.message || '加载失败')
          this.adjustments = res.data || []
        })
        .catch(err => this.showError(err, '往年国省道数据调整加载失败'))
        .finally(() => { this.adjustmentLoading = false })
    },
    openAdjustmentDialog(row) {
      const source = row || {}
      this.adjustmentForm = {
        id: source.id || null,
        year: source.year || this.adjustmentYear,
        flowDate: source.flowDate || '',
        originalValue: source.originalValue === undefined || source.originalValue === null
          ? null : Number(source.originalValue),
        adjustedValue: source.adjustedValue === undefined || source.adjustedValue === null
          ? null : Number(source.adjustedValue),
        reason: source.reason || ''
      }
      this.adjustmentDialogVisible = true
      this.loadAdjustmentOriginalValue()
    },
    onAdjustmentYearChange() {
      this.adjustmentOriginalRequestId += 1
      this.adjustmentOriginalLoading = false
      this.adjustmentForm.flowDate = ''
      this.adjustmentForm.originalValue = null
    },
    onAdjustmentDateChange() {
      this.loadAdjustmentOriginalValue()
    },
    loadAdjustmentOriginalValue() {
      const year = this.adjustmentForm.year
      const flowDate = this.adjustmentForm.flowDate
      const requestId = ++this.adjustmentOriginalRequestId
      this.adjustmentForm.originalValue = null
      if (!year || !flowDate) {
        this.adjustmentOriginalLoading = false
        return
      }
      this.adjustmentOriginalLoading = true
      api.get('/holiday/adjustments/source-value', { params: { year: year, flowDate: flowDate } })
        .then(res => {
          if (requestId !== this.adjustmentOriginalRequestId) return
          if (res.code !== 200) throw new Error(res.message || '调整前值加载失败')
          this.adjustmentForm.originalValue = res.data === null || res.data === undefined || res.data === ''
            ? null : Number(res.data)
        })
        .catch(err => {
          if (requestId !== this.adjustmentOriginalRequestId) return
          this.adjustmentForm.originalValue = null
          this.showError(err, '调整前值加载失败')
        })
        .finally(() => {
          if (requestId === this.adjustmentOriginalRequestId) this.adjustmentOriginalLoading = false
        })
    },
    saveAdjustment() {
      const form = this.adjustmentForm
      if (!form.year || !form.flowDate) {
        this.$message.warning('请选择年份和日期')
        return
      }
      if (form.adjustedValue === null || form.adjustedValue === undefined || Number(form.adjustedValue) < 0) {
        this.$message.warning('请输入不低于 0 的调整后车流量')
        return
      }
      if (!form.reason || !form.reason.trim()) {
        this.$message.warning('请填写调整原因')
        return
      }
      this.adjustmentSaving = true
      api.post('/holiday/adjustments/save', {
        id: form.id,
        year: form.year,
        flowDate: form.flowDate,
        adjustedValue: form.adjustedValue,
        reason: form.reason
      })
        .then(res => {
          if (res.code !== 200) throw new Error(res.message || '保存失败')
          this.adjustmentDialogVisible = false
          this.adjustmentYear = form.year
          this.loadAdjustments()
          this.loadOverview()
          this.$message.success('调整已保存')
        })
        .catch(err => this.showError(err, '保存失败'))
        .finally(() => { this.adjustmentSaving = false })
    },
    deleteAdjustment(row) {
      this.$confirm('确定删除 ' + row.flowDate + ' 的国省道数据调整吗?', '删除确认', { type: 'warning' })
        .then(() => api.post('/holiday/adjustments/delete', null, { params: { id: row.id } }))
        .then(res => {
          if (res && res.code !== 200) throw new Error(res.message || '删除失败')
          this.loadAdjustments()
          this.loadOverview()
          this.$message.success('调整已删除')
        })
        .catch(err => { if (err !== 'cancel' && err !== 'close') this.showError(err, '删除失败') })
    },
    onFileChange(file) {
      this.selectedFile = file.raw
@@ -433,6 +625,20 @@
        })
        .catch(err => { if (err !== 'cancel' && err !== 'close') this.showError(err, '删除失败') })
    },
    formatAdjustmentValue(value) {
      if (value === null || value === undefined || value === '') return '—'
      const number = Number(value)
      if (!Number.isFinite(number)) return value
      return number.toFixed(4)
    },
    formatOriginalAdjustmentValue(value) {
      if (value === null || value === undefined || value === '') return ''
      return this.formatAdjustmentValue(value)
    },
    formatDateTime(value) {
      if (!value) return '—'
      return String(value).replace('T', ' ').slice(0, 19)
    },
    formatNumber(value) {
      if (value === null || value === undefined || value === '') return '—'
      return Number(value).toFixed(2)