zhizhijie
2026-10-06 bee9e18c3af284ff432a4472f1d409f5a6dc09a7
traffic-audit-web/src/views/SystemConfig.vue
New file
@@ -0,0 +1,304 @@
<template>
  <el-container direction="vertical">
    <app-header title="交通统计报表审核系统 - 系统配置"></app-header>
    <el-container>
      <el-aside width="200px">
        <el-menu ref="sideMenu" router :default-active="$route.path" @select="onMenuSelect">
          <el-menu-item index="/dashboard">工作台</el-menu-item>
          <el-menu-item index="/import">数据导入</el-menu-item>
          <el-menu-item index="/data">数据查看</el-menu-item>
          <el-menu-item index="/explain">企业解释</el-menu-item>
          <el-menu-item index="/audit">审核结果</el-menu-item>
          <el-menu-item index="/report">报表生成</el-menu-item>
          <el-submenu index="/special-stats">
            <template slot="title">专项统计</template>
            <el-menu-item index="/holiday-stat">节假日</el-menu-item>
          </el-submenu>
          <el-menu-item index="/rules">规则管理</el-menu-item>
          <el-menu-item index="/users">用户管理</el-menu-item>
          <el-menu-item v-if="operatorIsAdmin" index="/system-config">系统配置</el-menu-item>
        </el-menu>
      </el-aside>
      <el-main>
        <div class="page-scroll">
          <h3>系统配置</h3>
          <el-tabs v-model="activeTab" @tab-click="loadActiveTab">
            <el-tab-pane label="节假日管理" name="holidays">
              <div class="toolbar">
                <el-select v-model="holidayQuery.year" clearable placeholder="年份" style="width:130px" @change="loadHolidays">
                  <el-option v-for="year in years" :key="year" :label="year + '年'" :value="year"></el-option>
                </el-select>
                <el-select v-model="holidayQuery.holidayType" clearable placeholder="节假日分类" style="width:150px" @change="loadHolidays">
                  <el-option v-for="item in holidayTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
                </el-select>
                <el-button type="primary" icon="el-icon-search" @click="loadHolidays">查询</el-button>
                <el-button type="success" icon="el-icon-plus" @click="openHolidayDialog()">新增节假日</el-button>
              </div>
              <el-table :data="holidays" border stripe size="small" v-loading="loadingHolidays" style="width:100%">
                <el-table-column prop="year" label="年份" width="100" align="center"></el-table-column>
                <el-table-column label="节假日分类" width="140">
                  <template slot-scope="scope">{{ labelOf(holidayTypeOptions, scope.row.holidayType) }}</template>
                </el-table-column>
                <el-table-column prop="holidayDate" label="日期" width="150" align="center"></el-table-column>
                <el-table-column label="日期类型" width="120" align="center">
                  <template slot-scope="scope">{{ dayIndexLabel(scope.row.dayIndex) }}</template>
                </el-table-column>
                <el-table-column label="操作" width="150" align="center">
                  <template slot-scope="scope">
                    <el-button type="text" size="mini" @click="openHolidayDialog(scope.row)">编辑</el-button>
                    <el-button type="text" size="mini" class="danger-text" @click="removeHoliday(scope.row)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </el-tab-pane>
            <el-tab-pane label="客运系数管理" name="coefficients">
              <div class="toolbar">
                <el-select v-model="coefficientQuery.year" clearable placeholder="年份" style="width:130px" @change="loadCoefficients">
                  <el-option v-for="year in years" :key="year" :label="year + '年'" :value="year"></el-option>
                </el-select>
                <el-button type="primary" icon="el-icon-search" @click="loadCoefficients">查询</el-button>
                <el-button type="success" icon="el-icon-plus" @click="openCoefficientDialog()">新增客运系数</el-button>
              </div>
              <el-table :data="coefficients" border stripe size="small" v-loading="loadingCoefficients" style="width:100%">
                <el-table-column prop="year" label="年份" width="120" align="center"></el-table-column>
                <el-table-column label="日期分类" width="160">
                  <template slot-scope="scope">{{ labelOf(dateCategoryOptions, scope.row.dateCategory) }}</template>
                </el-table-column>
                <el-table-column label="客运系数" width="160" align="center">
                  <template slot-scope="scope">{{ formatCoefficient(scope.row.coefficient) }}</template>
                </el-table-column>
                <el-table-column label="操作" width="150" align="center">
                  <template slot-scope="scope">
                    <el-button type="text" size="mini" @click="openCoefficientDialog(scope.row)">编辑</el-button>
                    <el-button type="text" size="mini" class="danger-text" @click="removeCoefficient(scope.row)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </el-tab-pane>
          </el-tabs>
        </div>
        <el-dialog :title="holidayForm.id ? '编辑节假日' : '新增节假日'" :visible.sync="holidayDialog" width="520px" v-dialog-drag>
          <el-form ref="holidayForm" :model="holidayForm" :rules="holidayRules" label-width="110px">
            <el-form-item label="年份" prop="year">
              <el-select v-model="holidayForm.year" style="width:100%" @change="onHolidayYearChange">
                <el-option v-for="year in years" :key="year" :label="year + '年'" :value="year"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="节假日分类" prop="holidayType">
              <el-select v-model="holidayForm.holidayType" style="width:100%">
                <el-option v-for="item in holidayTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="日期" prop="holidayDate">
              <el-date-picker v-model="holidayForm.holidayDate" type="date" value-format="yyyy-MM-dd"
                              style="width:100%" placeholder="选择日期"></el-date-picker>
            </el-form-item>
            <el-form-item label="日期类型" prop="dayIndex">
              <el-select v-model="holidayForm.dayIndex" style="width:100%">
                <el-option label="前一日" :value="0"></el-option>
                <el-option v-for="day in 31" :key="day" :label="'第' + day + '日'" :value="day"></el-option>
              </el-select>
            </el-form-item>
          </el-form>
          <span slot="footer">
            <el-button @click="holidayDialog = false">取消</el-button>
            <el-button type="primary" :loading="saving" @click="saveHoliday">保存</el-button>
          </span>
        </el-dialog>
        <el-dialog :title="coefficientForm.id ? '编辑客运系数' : '新增客运系数'" :visible.sync="coefficientDialog" width="520px" v-dialog-drag>
          <el-form ref="coefficientForm" :model="coefficientForm" :rules="coefficientRules" label-width="110px">
            <el-form-item label="年份" prop="year">
              <el-select v-model="coefficientForm.year" style="width:100%">
                <el-option v-for="year in years" :key="year" :label="year + '年'" :value="year"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="日期分类" prop="dateCategory">
              <el-select v-model="coefficientForm.dateCategory" style="width:100%">
                <el-option v-for="item in dateCategoryOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="客运系数" prop="coefficient">
              <el-input-number v-model="coefficientForm.coefficient" :min="0.01" :precision="4" :step="0.1" style="width:100%"></el-input-number>
            </el-form-item>
          </el-form>
          <span slot="footer">
            <el-button @click="coefficientDialog = false">取消</el-button>
            <el-button type="primary" :loading="saving" @click="saveCoefficient">保存</el-button>
          </span>
        </el-dialog>
      </el-main>
    </el-container>
  </el-container>
</template>
<script>
import api from '@/api'
import AppHeader from '@/components/AppHeader.vue'
export default {
  components: { AppHeader },
  name: 'SystemConfig',
  data() {
    return {
      activeTab: 'holidays',
      years: [2019, 2024, 2025, 2026, 2027, 2028, 2029, 2030],
      holidayTypeOptions: [],
      dateCategoryOptions: [],
      holidayQuery: { year: 2026, holidayType: 'NATIONAL_DAY' },
      coefficientQuery: { year: 2026 },
      holidays: [],
      coefficients: [],
      loadingHolidays: false,
      loadingCoefficients: false,
      saving: false,
      holidayDialog: false,
      coefficientDialog: false,
      holidayForm: { id: null, year: 2026, holidayType: 'NATIONAL_DAY', holidayDate: '', dayIndex: 1 },
      coefficientForm: { id: null, year: 2026, dateCategory: 'HOLIDAY', coefficient: 2.4 },
      holidayRules: {
        year: [{ required: true, message: '请选择年份', trigger: 'change' }],
        holidayType: [{ required: true, message: '请选择节假日分类', trigger: 'change' }],
        holidayDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
        dayIndex: [{ required: true, message: '请选择日期类型', trigger: 'change' }]
      },
      coefficientRules: {
        year: [{ required: true, message: '请选择年份', trigger: 'change' }],
        dateCategory: [{ required: true, message: '请选择日期分类', trigger: 'change' }],
        coefficient: [{ required: true, message: '请输入客运系数', trigger: 'change' }]
      }
    }
  },
  mounted() {
    this.loadOptions()
    this.loadHolidays()
    this.loadCoefficients()
  },
  methods: {
    dayIndexLabel(value) {
      if (Number(value) === 0) return '前一日'
      return value === null || value === undefined || value === '' ? '—' : '第' + value + '日'
    },
    loadOptions() {
      api.get('/holiday/config/options').then(res => {
        const data = res.data || {}
        this.holidayTypeOptions = this.mapOptions(data.holidayTypes)
        this.dateCategoryOptions = this.mapOptions(data.dateCategories)
      })
    },
    mapOptions(map) {
      if (!map) return []
      return Object.keys(map).map(key => ({ value: key, label: map[key] }))
    },
    labelOf(options, value) {
      const item = (options || []).find(x => x.value === value)
      return item ? item.label : value
    },
    loadActiveTab() {
      if (this.activeTab === 'holidays') this.loadHolidays()
      else this.loadCoefficients()
    },
    loadHolidays() {
      this.loadingHolidays = true
      api.get('/holiday/config/holidays', { params: this.holidayQuery })
        .then(res => { this.holidays = res.data || [] })
        .catch(e => { if (!e || !e._toast) this.$message.error('节假日加载失败') })
        .finally(() => { this.loadingHolidays = false })
    },
    loadCoefficients() {
      this.loadingCoefficients = true
      api.get('/holiday/config/coefficients', { params: this.coefficientQuery })
        .then(res => { this.coefficients = res.data || [] })
        .catch(e => { if (!e || !e._toast) this.$message.error('客运系数加载失败') })
        .finally(() => { this.loadingCoefficients = false })
    },
    openHolidayDialog(row) {
      this.holidayDialog = true
      this.$nextTick(() => {
        this.holidayForm = row ? Object.assign({}, row) : { id: null, year: 2026, holidayType: 'NATIONAL_DAY', holidayDate: '', dayIndex: 1 }
        if (this.$refs.holidayForm) this.$refs.holidayForm.clearValidate()
      })
    },
    onHolidayYearChange(year) {
      if (this.holidayForm.holidayDate && Number(this.holidayForm.holidayDate.slice(0, 4)) !== year) {
        this.holidayForm.holidayDate = ''
      }
    },
    saveHoliday() {
      this.$refs.holidayForm.validate(valid => {
        if (!valid) return
        this.saving = true
        api.post('/holiday/config/holidays/save', this.holidayForm)
          .then(res => {
            if (res.code !== 200) throw new Error(res.message || '保存失败')
            this.$message.success('保存成功')
            this.holidayDialog = false
            this.loadHolidays()
          })
          .catch(err => this.showError(err, '保存失败'))
          .finally(() => { this.saving = false })
      })
    },
    removeHoliday(row) {
      this.$confirm('确定删除该节假日日期吗?', '删除确认', { type: 'warning' })
        .then(() => api.post('/holiday/config/holidays/delete', null, { params: { id: row.id } }))
        .then(res => {
          if (res.code !== 200) throw new Error(res.message || '删除失败')
          this.$message.success('已删除')
          this.loadHolidays()
        })
        .catch(err => { if (err !== 'cancel') this.showError(err, '删除失败') })
    },
    openCoefficientDialog(row) {
      this.coefficientDialog = true
      this.$nextTick(() => {
        this.coefficientForm = row ? Object.assign({}, row) : { id: null, year: 2026, dateCategory: 'HOLIDAY', coefficient: 2.4 }
        if (this.$refs.coefficientForm) this.$refs.coefficientForm.clearValidate()
      })
    },
    saveCoefficient() {
      this.$refs.coefficientForm.validate(valid => {
        if (!valid) return
        this.saving = true
        api.post('/holiday/config/coefficients/save', this.coefficientForm)
          .then(res => {
            if (res.code !== 200) throw new Error(res.message || '保存失败')
            this.$message.success('保存成功')
            this.coefficientDialog = false
            this.loadCoefficients()
          })
          .catch(err => this.showError(err, '保存失败'))
          .finally(() => { this.saving = false })
      })
    },
    removeCoefficient(row) {
      this.$confirm('确定删除该客运系数吗?', '删除确认', { type: 'warning' })
        .then(() => api.post('/holiday/config/coefficients/delete', null, { params: { id: row.id } }))
        .then(res => {
          if (res.code !== 200) throw new Error(res.message || '删除失败')
          this.$message.success('已删除')
          this.loadCoefficients()
        })
        .catch(err => { if (err !== 'cancel') this.showError(err, '删除失败') })
    },
    formatCoefficient(value) {
      if (value === null || value === undefined || value === '') return '—'
      const number = Number(value)
      if (!Number.isFinite(number)) return value
      return number.toFixed(15).replace(/0+$/, '').replace(/\.$/, '')
    },
    showError(err, fallback) {
      if (err && err._toast) return
      const data = err && err.response && err.response.data
      this.$message.error((data && data.message) || (err && err.message) || fallback)
    }
  }
}
</script>
<style scoped>
.toolbar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
.danger-text { color: #f56c6c; }
</style>