| New file |
| | |
| | | <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> |