<template>
|
<div class="punishment-manage">
|
<!-- 餐饮店铺行政处罚卡片 -->
|
<el-card class="mb-4">
|
<template #header>
|
<div class="card-header">
|
<span>餐饮店铺行政处罚</span>
|
<div class="filter-group">
|
<FYOptionTime
|
class="m-r-8"
|
:initValue="false"
|
type="daterange"
|
v-model:value="punishmentDateRange"
|
style="width: 300px; margin-bottom: 0px"
|
:shortcuts="shortcuts"
|
></FYOptionTime>
|
<el-button type="success" icon="Plus" @click="addPunishment">新增处罚</el-button>
|
<el-button type="info" icon="Upload" @click="importPunishment">批量导入</el-button>
|
</div>
|
</div>
|
</template>
|
|
<!-- 图表展示 -->
|
<div class="chart-container">
|
<div class="chart-item">
|
<div class="chart-header">
|
<h3>处罚数趋势</h3>
|
<div class="chart-summary">处罚总数: {{ punishmentStats.totalCount }}</div>
|
</div>
|
<div ref="dailyPunishmentChart" class="chart"></div>
|
</div>
|
<div class="chart-item">
|
<h3>店铺类型处罚分布</h3>
|
<div ref="shopTypePunishmentChart" class="chart"></div>
|
</div>
|
</div>
|
|
<!-- 处罚记录表格 -->
|
<el-table
|
:data="filteredPunishmentData"
|
table-layout="fixed"
|
:show-overflow-tooltip="true"
|
height="400px"
|
border
|
>
|
<el-table-column prop="shopName" label="处罚店铺" />
|
<el-table-column prop="punishmentItem" label="处罚事项" width="180" />
|
<el-table-column prop="punishmentTime" label="处罚时间" width="180" />
|
<el-table-column prop="punishmentReason" label="处罚理由" width="200" />
|
<el-table-column prop="punishmentResult" label="处罚结果" width="150" />
|
<el-table-column prop="punishmentDepartment" label="处罚部门" width="150" />
|
<el-table-column width="250">
|
<template #header>
|
<el-input v-model="punishmentKeyword" placeholder="关键字搜索" style="width: 120px" />
|
</template>
|
<template #default="scope">
|
<el-button size="small" type="primary" @click="editPunishment(scope.row)"
|
>编辑</el-button
|
>
|
<el-button size="small" type="danger" @click="deletePunishment(scope.row.id)"
|
>删除</el-button
|
>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination
|
v-model:current-page="punishmentPagination.currentPage"
|
v-model:page-size="punishmentPagination.pageSize"
|
:page-sizes="[10, 20, 50, 100]"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="punishmentPagination.total"
|
@size-change="handlePunishmentSizeChange"
|
@current-change="handlePunishmentCurrentChange"
|
/>
|
</div>
|
</el-card>
|
|
<!-- 新增/编辑处罚对话框 -->
|
<el-dialog v-model="punishmentDialogVisible" title="处罚信息" width="600px">
|
<el-form :model="punishmentForm" label-width="100px">
|
<el-form-item label="处罚店铺">
|
<el-input v-model="punishmentForm.shopName" />
|
</el-form-item>
|
<el-form-item label="处罚事项">
|
<el-input v-model="punishmentForm.punishmentItem" />
|
</el-form-item>
|
<el-form-item label="处罚时间">
|
<el-date-picker v-model="punishmentForm.punishmentTime" type="datetime" />
|
</el-form-item>
|
<el-form-item label="处罚理由">
|
<el-input v-model="punishmentForm.punishmentReason" type="textarea" />
|
</el-form-item>
|
<el-form-item label="处罚结果">
|
<el-input v-model="punishmentForm.punishmentResult" />
|
</el-form-item>
|
<el-form-item label="处罚部门">
|
<el-input v-model="punishmentForm.punishmentDepartment" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="punishmentDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="savePunishment">保存</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
|
<!-- 处罚批量导入对话框 -->
|
<el-dialog v-model="punishmentImportDialogVisible" title="处罚批量导入" width="600px">
|
<div class="import-container">
|
<p class="import-tip">请选择要导入的Excel文件</p>
|
<el-upload
|
class="upload-demo"
|
action="#"
|
drag
|
:auto-upload="false"
|
:on-change="handlePunishmentFileChange"
|
:file-list="punishmentImportFileList"
|
accept=".xlsx,.xls"
|
:limit="1"
|
:on-exceed="handleExceed"
|
>
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
<div class="el-upload__text">拖动文件或<em>点击上传</em></div>
|
<template #tip>
|
<div class="el-upload__tip">只能上传Excel文件,且不超过5MB</div>
|
</template>
|
</el-upload>
|
</div>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="punishmentImportDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="confirmPunishmentImport">导入</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
|
import * as echarts from 'echarts'
|
import dayjs from 'dayjs'
|
import { ElMessage } from 'element-plus'
|
|
// 时间范围快捷选项
|
const dayStart = dayjs('2023-08-01').startOf('date')
|
const dayEnd = dayStart.endOf('date')
|
const shortcuts = [
|
{
|
text: '今天',
|
value: [dayStart.toDate(), dayEnd.toDate()],
|
},
|
{
|
text: '本周',
|
value: [dayStart.startOf('week').toDate(), dayEnd.endOf('week').toDate()],
|
},
|
{
|
text: '上周',
|
value: [dayStart.day(-7).toDate(), dayEnd.day(-1).toDate()],
|
},
|
{
|
text: '本月',
|
value: [dayStart.startOf('month').toDate(), dayEnd.endOf('month').toDate()],
|
},
|
{
|
text: '上月',
|
value: [
|
dayStart.subtract(1, 'month').startOf('month').toDate(),
|
dayEnd.subtract(1, 'month').endOf('month').toDate(),
|
],
|
},
|
{
|
text: '本季度',
|
value: [dayStart.startOf('quarter').toDate(), dayEnd.endOf('quarter').toDate()],
|
},
|
{
|
text: '上季度',
|
value: [
|
dayStart.subtract(1, 'quarter').startOf('quarter').toDate(),
|
dayEnd.subtract(1, 'quarter').endOf('quarter').toDate(),
|
],
|
},
|
{
|
text: '去年',
|
value: [
|
dayStart.subtract(1, 'year').startOf('year').toDate(),
|
dayEnd.subtract(1, 'year').endOf('year').toDate(),
|
],
|
},
|
{
|
text: '今年',
|
value: [dayStart.startOf('year').toDate(), dayEnd.endOf('year').toDate()],
|
},
|
]
|
|
// 图表引用
|
const dailyPunishmentChart = ref(null)
|
const shopTypePunishmentChart = ref(null)
|
|
// 行政处罚数据
|
const punishmentDateRange = ref([
|
dayStart.startOf('month').toDate(),
|
dayEnd.endOf('month').toDate(),
|
])
|
const punishmentKeyword = ref('')
|
const punishmentStats = ref({
|
totalCount: 120,
|
})
|
const punishmentTableData = ref([])
|
const punishmentPagination = ref({
|
currentPage: 1,
|
pageSize: 10,
|
total: 0,
|
})
|
|
// 过滤后的处罚数据
|
const filteredPunishmentData = computed(() => {
|
if (!punishmentKeyword.value) {
|
return punishmentTableData.value
|
}
|
const keyword = punishmentKeyword.value.toLowerCase()
|
return punishmentTableData.value.filter((item) => {
|
return Object.values(item).some((value) => {
|
return String(value).toLowerCase().includes(keyword)
|
})
|
})
|
})
|
|
// 对话框状态
|
const punishmentDialogVisible = ref(false)
|
const punishmentImportDialogVisible = ref(false)
|
|
// 导入文件列表
|
const punishmentImportFileList = ref([])
|
|
// 表单数据
|
const punishmentForm = ref({
|
id: '',
|
shopName: '',
|
punishmentItem: '',
|
punishmentTime: '',
|
punishmentReason: '',
|
punishmentResult: '',
|
punishmentDepartment: '',
|
})
|
|
const searchPunishment = () => {
|
// 模拟搜索处罚数据
|
console.log('搜索处罚数据', {
|
dateRange: punishmentDateRange.value,
|
keyword: punishmentKeyword.value,
|
})
|
|
// 计算时间范围
|
const startTime = punishmentDateRange.value[0]
|
const endTime = punishmentDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
const months = daysDiff / 30
|
|
// 生成新的模拟数据
|
const totalCount = Math.floor(10 * months) + Math.floor(Math.random() * 3)
|
punishmentStats.value = {
|
totalCount,
|
}
|
|
// 生成新的处罚记录
|
const newData = []
|
const shopNames = [
|
'味美餐厅',
|
'香辣小龙虾',
|
'鲜味馆',
|
'烧烤达人',
|
'川菜馆',
|
'西餐厅',
|
'日料店',
|
'火锅店',
|
]
|
const punishmentItems = ['油烟超标排放', '未安装油烟净化设备', '设备未正常运行', '噪声污染']
|
const departments = ['徐汇区环保局', '长宁区环保局', '静安区环保局', '普陀区环保局']
|
|
for (let i = 0; i < totalCount; i++) {
|
// 生成在时间范围内的随机时间
|
const randomDays = Math.floor(Math.random() * (daysDiff + 1))
|
const randomTime = startDate
|
.add(randomDays, 'day')
|
.add(Math.floor(Math.random() * 24), 'hour')
|
.add(Math.floor(Math.random() * 60), 'minute')
|
|
newData.push({
|
id: i + 1,
|
shopName: shopNames[Math.floor(Math.random() * shopNames.length)],
|
punishmentItem: punishmentItems[Math.floor(Math.random() * punishmentItems.length)],
|
punishmentTime: randomTime.format('YYYY-MM-DD HH:mm'),
|
punishmentReason:
|
punishmentItems[Math.floor(Math.random() * punishmentItems.length)] + '的违规行为',
|
punishmentResult: '罚款' + (Math.floor(Math.random() * 5) + 1) * 1000 + '元',
|
punishmentDepartment: departments[Math.floor(Math.random() * departments.length)],
|
})
|
}
|
|
punishmentTableData.value = newData
|
punishmentPagination.value.total = newData.length
|
// 重新初始化图表以更新数据
|
initCharts()
|
}
|
|
// 监听处罚日期范围变化
|
watch(
|
() => punishmentDateRange.value,
|
() => {
|
searchPunishment()
|
},
|
{ deep: true },
|
)
|
|
const addPunishment = () => {
|
// 重置表单
|
punishmentForm.value = {
|
id: '',
|
shopName: '',
|
punishmentItem: '',
|
punishmentTime: '',
|
punishmentReason: '',
|
punishmentResult: '',
|
punishmentDepartment: '',
|
}
|
punishmentDialogVisible.value = true
|
}
|
|
const editPunishment = (row) => {
|
// 填充表单
|
punishmentForm.value = { ...row }
|
punishmentDialogVisible.value = true
|
}
|
|
const savePunishment = () => {
|
// 模拟保存数据
|
console.log('保存处罚数据', punishmentForm.value)
|
punishmentDialogVisible.value = false
|
// 这里可以添加实际的数据保存逻辑
|
}
|
|
const deletePunishment = (id) => {
|
// 模拟删除数据
|
console.log('删除处罚数据', id)
|
// 这里可以添加实际的数据删除逻辑
|
}
|
|
const importPunishment = () => {
|
// 打开导入对话框
|
punishmentImportFileList.value = []
|
punishmentImportDialogVisible.value = true
|
}
|
|
const handlePunishmentFileChange = (file, fileList) => {
|
punishmentImportFileList.value = fileList
|
console.log('选择的处罚文件:', file)
|
}
|
|
const confirmPunishmentImport = () => {
|
if (punishmentImportFileList.value.length === 0) {
|
ElMessage.warning('请选择要导入的文件')
|
return
|
}
|
|
const file = punishmentImportFileList.value[0]
|
console.log('开始导入处罚数据:', file.name)
|
|
// 预留导入逻辑
|
// 这里将实现实际的文件解析和数据导入
|
|
punishmentImportDialogVisible.value = false
|
ElMessage.success('导入操作已触发,预留导入逻辑')
|
}
|
|
const handleExceed = (files, fileList) => {
|
ElMessage.warning('只能上传一个文件')
|
}
|
|
const handlePunishmentSizeChange = (size) => {
|
punishmentPagination.value.pageSize = size
|
// 这里可以添加实际的分页逻辑
|
}
|
|
const handlePunishmentCurrentChange = (current) => {
|
punishmentPagination.value.currentPage = current
|
// 这里可以添加实际的分页逻辑
|
}
|
|
// 初始化图表
|
const initCharts = () => {
|
// 每日处罚数量图
|
if (dailyPunishmentChart.value && punishmentDateRange.value.length > 0) {
|
const chart = echarts.init(dailyPunishmentChart.value)
|
|
// 计算时间范围并生成x轴数据
|
const startTime = punishmentDateRange.value[0]
|
const endTime = punishmentDateRange.value[1]
|
const startDate = dayjs(startTime)
|
const endDate = dayjs(endTime)
|
const daysDiff = endDate.diff(startDate, 'day')
|
|
let xAxisData = []
|
let seriesData = []
|
|
// 处理处罚数据
|
const punishmentData = punishmentTableData.value
|
const dateFormat = daysDiff <= 30 ? 'MM/DD' : 'YYYY/MM'
|
|
// 生成日期范围
|
if (daysDiff <= 30) {
|
// 同一个月内,按日显示
|
for (let i = 0; i <= daysDiff; i++) {
|
const date = startDate.add(i, 'day')
|
xAxisData.push(date.format(dateFormat))
|
// 计算该日期的处罚数量
|
const count = punishmentData.filter((item) => {
|
const itemDate = dayjs(item.punishmentTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
} else {
|
// 超过一个月,按月显示
|
const startMonth = startDate.startOf('month')
|
const endMonth = endDate.endOf('month')
|
const monthsDiff = endMonth.diff(startMonth, 'month')
|
|
for (let i = 0; i <= monthsDiff; i++) {
|
const date = startMonth.add(i, 'month')
|
xAxisData.push(date.format(dateFormat))
|
// 计算该月份的处罚数量
|
const count = punishmentData.filter((item) => {
|
const itemDate = dayjs(item.punishmentTime)
|
return itemDate.format(dateFormat) === date.format(dateFormat)
|
}).length
|
seriesData.push(count)
|
}
|
}
|
|
chart.setOption({
|
xAxis: {
|
type: 'category',
|
data: xAxisData,
|
},
|
yAxis: {
|
type: 'value',
|
},
|
series: [
|
{
|
data: seriesData,
|
type: 'bar',
|
},
|
],
|
})
|
}
|
|
// 店铺类型处罚分布图
|
if (shopTypePunishmentChart.value) {
|
const chart = echarts.init(shopTypePunishmentChart.value)
|
chart.setOption({
|
series: [
|
{
|
type: 'pie',
|
data: [
|
{ value: 50, name: '中餐' },
|
{ value: 30, name: '烧烤' },
|
{ value: 20, name: '西餐' },
|
{ value: 15, name: '其他' },
|
],
|
label: {
|
show: true,
|
formatter: '{b}: {d}%',
|
},
|
},
|
],
|
})
|
}
|
}
|
|
// 监听窗口大小变化
|
const handleResize = () => {
|
// 重新调整图表大小
|
if (dailyPunishmentChart.value) {
|
echarts.init(dailyPunishmentChart.value).resize()
|
}
|
if (shopTypePunishmentChart.value) {
|
echarts.init(shopTypePunishmentChart.value).resize()
|
}
|
}
|
|
// 生命周期
|
onMounted(() => {
|
searchPunishment()
|
initCharts()
|
window.addEventListener('resize', handleResize)
|
})
|
|
// 组件卸载时清理事件监听
|
onUnmounted(() => {
|
cleanup()
|
})
|
|
// 清理
|
const cleanup = () => {
|
window.removeEventListener('resize', handleResize)
|
}
|
</script>
|
|
<style scoped>
|
.punishment-manage {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 100vh;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.filter-group {
|
display: flex;
|
align-items: center;
|
}
|
|
.chart-container {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 30px;
|
}
|
|
.chart-item {
|
flex: 1;
|
background-color: #fff;
|
padding: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
|
.chart-item h3 {
|
margin-bottom: 15px;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.chart-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
}
|
|
.chart-summary {
|
font-size: 14px;
|
color: #606266;
|
}
|
|
.chart {
|
height: 300px;
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: flex-end;
|
}
|
|
.import-container {
|
padding: 20px;
|
}
|
|
.import-tip {
|
margin-bottom: 20px;
|
color: #606266;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
}
|
</style>
|