traffic-audit-web/src/views/DataImport.vue
@@ -103,11 +103,12 @@
              <div v-show="sideOpen" class="side-content">
                <div class="side-head">
                  <span>本月导入进度({{ period || '未选择报表期' }})</span>
                  <span class="side-head-btns">
                    <el-button type="text" icon="el-icon-refresh" @click="loadData">刷新</el-button>
                    <el-button type="text" icon="el-icon-delete" style="color:#F56C6C" :loading="clearing"
                               :disabled="!period || clearing || importing" @click="clearPeriodData">清空</el-button>
                  </span>
                </div>
                <div v-if="period" class="side-danger">
                  <el-button size="mini" type="danger" plain icon="el-icon-delete"
                             :disabled="clearing || importing" @click="openClearDialog">清空数据…</el-button>
                  <span class="side-danger-tip">按报表期清库,用于整月重导</span>
                </div>
                <div v-if="!period" class="tip">请先在上方选择报表期</div>
                <div v-else class="overview-grid">
@@ -123,6 +124,32 @@
            </div>
          </div>
        </div>
        <el-dialog title="清空数据" :visible.sync="clearDialog" width="560px" append-to-body :close-on-click-modal="false">
          <div class="clear-dlg">
            <div class="clear-period">报表期:<b>{{ period }}</b></div>
            <el-radio-group v-model="clearScope">
              <el-radio label="all">清空该报表期全部已导入数据</el-radio>
              <el-radio label="types">只清空指定类型</el-radio>
            </el-radio-group>
            <div v-if="clearScope === 'types'" class="clear-types">
              <el-select v-model="clearTypes" multiple filterable collapse-tags placeholder="选择要清空的数据类型" style="width:100%">
                <el-option-group v-for="g in clearableGroups()" :key="g.key" :label="g.name">
                  <el-option v-for="t in g.types" :key="t.value" :value="t.value"
                             :label="t.label + '(' + overviewState(t.value).text + ')'"></el-option>
                </el-option-group>
              </el-select>
              <div class="tip" style="margin-top:6px">可多选;未导入的类型没有数据可删。</div>
            </div>
            <div class="clear-warn">
              将删除所选范围内该报表期的数据库数据,并同步删除对应审核结果、已审核标记与导入批次记录(投资项目主档保留)。此操作不可撤销,请确认已有备份或准备重新导入。
            </div>
          </div>
          <span slot="footer">
            <el-button @click="clearDialog = false">取消</el-button>
            <el-button type="danger" :loading="clearing"
                       :disabled="clearScope === 'types' && !clearTypes.length" @click="confirmClear">确认清空</el-button>
          </span>
        </el-dialog>
      </el-main>
    </el-container>
  </el-container>
@@ -163,6 +190,9 @@
      cityDirLoading: false,
      importing: false,
      clearing: false,
      clearDialog: false,
      clearScope: 'all',
      clearTypes: [],
      sideOpen: false,
      result: '',
      error: '',
@@ -525,19 +555,35 @@
        .catch(e => { if (!e || !e._toast) this.$message.error('导入记录加载失败,请刷新重试') })
        .finally(() => { this.batchLoading = false })
    },
    clearPeriodData() {
    openClearDialog() {
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      if (this.importing) { this.$message.warning('正在导入数据,请等待导入完成后再清空'); return }
      this.clearScope = 'all'
      this.clearTypes = []
      this.clearDialog = true
    },
    clearableGroups() {
      return this.groups.filter(g => !g.hidden)
    },
    confirmClear() {
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      if (this.clearing) return
      this.$confirm(
        '将清空 ' + this.period + ' 报表期在数据库中的全部已导入数据,同时删除该报表期的审核结果与导入批次记录(便于整月重新导入)。\n此操作不可撤销,是否继续?',
        '清空 ' + this.period + ' 数据',
        { type: 'warning', confirmButtonText: '确认清空', cancelButtonText: '取消' }
      ).then(() => {
        this.clearing = true
        api.post('/import/clear', null, { params: { period: this.period }, timeout: 600000 })
      if (this.clearScope === 'types' && !this.clearTypes.length) { this.$message.warning('请选择要清空的数据类型'); return }
      const scopeText = this.clearScope === 'all'
        ? '该报表期全部已导入数据'
        : '以下 ' + this.clearTypes.length + ' 个类型:' + this.clearTypes.map(t => this.typeLabel(t)).join('、')
      this.$confirm('将清空 ' + this.period + ' 的' + scopeText + ',并同步删除对应审核结果、已审核标记与导入批次记录。此操作不可撤销,是否继续?',
        '清空确认', { type: 'warning', confirmButtonText: '确认清空', cancelButtonText: '取消' })
          .then(() => {
            this.$message.success('已清空 ' + this.period + ' 报表期的数据,可以重新导入')
          this.clearing = true
          const params = { period: this.period }
          if (this.clearScope === 'types') params.types = this.clearTypes.join(',')
          return api.post('/import/clear', null, { params, timeout: 600000 })
            .then(res => {
              const d = (res && res.data) || {}
              this.$message.success('已清空 ' + this.period + ' 数据:业务数据 ' + (d.dataRows || 0) +
                ' 行、审核结果 ' + (d.auditRows || 0) + ' 行、导入批次 ' + (d.batchRows || 0) + ' 条,可以重新导入')
              this.clearDialog = false
            this.loadData()
          })
          .catch(e => {
@@ -618,8 +664,12 @@
.import-side.side-open .side-toggle-text { writing-mode: horizontal-tb; letter-spacing: normal; margin-top: 0; }
.side-content { padding: 8px 12px 12px; max-height: calc(100vh - 220px); overflow-y: auto; overscroll-behavior: contain; }
.side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-weight: 600; margin-bottom: 6px; }
.side-head-btns { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.side-head-btns .el-button + .el-button { margin-left: 0; }
.side-danger { display: flex; align-items: center; gap: 8px; margin: 4px 0 10px; padding: 6px 8px; background: #FEF0F0; border: 1px solid #FDE2E2; border-radius: 4px; }
.side-danger-tip { font-size: 12px; color: #F56C6C; line-height: 1.3; }
.clear-dlg { line-height: 1.7; }
.clear-period { margin-bottom: 6px; }
.clear-types { margin-top: 10px; }
.clear-warn { margin-top: 12px; padding: 8px 10px; background: #FEF0F0; border: 1px solid #FDE2E2; border-radius: 4px; color: #F56C6C; font-size: 12px; line-height: 1.6; }
.import-side .overview-grid { display: block; }
.import-side .ov-row { display: block; margin-bottom: 6px; }
.import-side .ov-group { width: auto; }