From 7032f3fb2ff26256e776e04a9bc62c8fab14379a Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期二, 06 十月 2026 02:27:35 +0800
Subject: [PATCH] fix(holiday): 调整弹窗补充车流量万辆次单位

---
 traffic-audit-web/src/views/HolidayStat.vue |  170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 169 insertions(+), 1 deletions(-)

diff --git a/traffic-audit-web/src/views/HolidayStat.vue b/traffic-audit-web/src/views/HolidayStat.vue
index aeceadc..cf226a1 100644
--- a/traffic-audit-web/src/views/HolidayStat.vue
+++ b/traffic-audit-web/src/views/HolidayStat.vue
@@ -52,6 +52,10 @@
                 <el-date-picker v-model="bulletinDate" type="date" value-format="yyyy-MM-dd" style="width:160px" placeholder="閫夋嫨鏃ユ湡"></el-date-picker>
                 <el-button type="warning" icon="el-icon-document" :loading="bulletinLoading" @click="downloadBulletin">涓嬭浇蹇姤</el-button>
               </div>
+              <div class="operation-item">
+                <span class="label">璧板娍鍥�</span>
+                <el-button type="primary" icon="el-icon-picture-outline" :loading="trendLoading" @click="downloadTrend">涓嬭浇璧板娍鍥�</el-button>
+              </div>
             </div>
           </el-card>
 
@@ -76,6 +80,61 @@
               <el-table-column label="闈炶惀涓氭�т汉鍛樻祦鍔ㄩ噺锛堜竾浜烘锛�" min-width="200" align="right"><template slot-scope="s">{{ formatNumber(s.row.nonBusinessPersonFlow) }}</template></el-table-column>
               <el-table-column label="鎬讳汉鍛樻祦鍔ㄩ噺锛堜竾浜烘锛�" min-width="180" align="right"><template slot-scope="s">{{ formatNumber(s.row.totalPersonFlow) }}</template></el-table-column>
             </el-table>
+          </el-card>
+
+          <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="230" 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="160px">
+                <el-form-item label="骞翠唤">
+                  <el-select v-model="adjustmentForm.year" style="width:100%" @change="adjustmentForm.flowDate = ''">
+                    <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="閫夋嫨鏃ユ湡"></el-date-picker>
+                </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">
@@ -169,6 +228,7 @@
       importing: false,
       exporting: false,
       bulletinLoading: false,
+      trendLoading: false,
       selectedFile: null,
       fileList: [],
       importMessage: '',
@@ -183,19 +243,41 @@
       observationDialogVisible: false,
       observationDialogDate: '',
       observationRows: [],
-      observationRowsLoading: false
+      observationRowsLoading: false,
+      adjustmentYear: new Date().getFullYear() - 1,
+      adjustments: [],
+      adjustmentLoading: false,
+      adjustmentDialogVisible: false,
+      adjustmentSaving: false,
+      adjustmentForm: {
+        id: null,
+        year: new Date().getFullYear() - 1,
+        flowDate: '',
+        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() {
@@ -220,6 +302,72 @@
         })
         .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 || '',
+        adjustedValue: source.adjustedValue === undefined || source.adjustedValue === null
+          ? null : Number(source.adjustedValue),
+        reason: source.reason || ''
+      }
+      this.adjustmentDialogVisible = true
+    },
+    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
@@ -320,6 +468,16 @@
         .catch(err => this.handleBlobError(err, '蹇姤鐢熸垚澶辫触'))
         .finally(() => { this.bulletinLoading = false })
     },
+    downloadTrend() {
+      this.trendLoading = true
+      api.get('/holiday/trend', { params: { year: this.year, holidayType: this.holidayType }, responseType: 'blob', timeout: 180000 })
+        .then(res => {
+          this.downloadBlob(res, this.year + '骞�' + this.holidayTypeLabel + '鍋囨湡鍏ㄧ渷缁煎悎杩愯緭瀹㈡祦閲忚蛋鍔垮浘.docx')
+          this.$message.success('璧板娍鍥句笅杞芥垚鍔�')
+        })
+        .catch(err => this.handleBlobError(err, '璧板娍鍥剧敓鎴愬け璐�'))
+        .finally(() => { this.trendLoading = false })
+    },
     downloadBlob(data, fileName) {
       const blob = data instanceof Blob ? data : new Blob([data])
       const url = window.URL.createObjectURL(blob)
@@ -418,6 +576,16 @@
         })
         .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)
+    },
+    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)

--
Gitblit v1.9.1