6 changed files with 1342 additions and 131 deletions
@ -0,0 +1,746 @@ |
|||
<template> |
|||
<div class="program-page"> |
|||
<!-- 顶部Tab切换 --> |
|||
<div class="tab-header"> |
|||
<div class="tab-item" :class="{ active: activeTab === 'create' }" @click="activeTab = 'create'"> |
|||
创建执法计划 |
|||
</div> |
|||
<div class="tab-item" :class="{ active: activeTab === 'query' }" @click="activeTab = 'query'"> |
|||
查询执法计划 |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 内容区域 --> |
|||
<div class="content-wrapper"> |
|||
<!-- 创建表单 --> |
|||
<div class="form-container" v-if="activeTab === 'create'"> |
|||
<div class="form-wrapper"> |
|||
<van-form @submit="submitForm"> |
|||
<!-- 计划名称 --> |
|||
<van-field v-model="form.planName" label="计划名称" placeholder="请选择计划名称" required is-link readonly |
|||
@click="showYearPicker = true" :rules="[{ required: true, message: '请选择计划名称' }]" /> |
|||
|
|||
<!-- 计划周期 --> |
|||
<van-field v-model="form.planPeriod" label="计划周期 " placeholder="请选择年/季/F/" required is-link |
|||
readonly @click="showPeriodPicker = true" class="period-field" |
|||
:rules="[{ required: true, message: '请选择计划周期' }]" /> |
|||
|
|||
<!-- 提示信息 --> |
|||
<div class="notice-bar"> |
|||
<van-icon name="info-o" /> |
|||
<span>深圳市司法局行政执法计划</span> |
|||
</div> |
|||
|
|||
<!-- 执法单位 --> |
|||
<van-field v-model="form.enforcementUnit" label="执法单位" placeholder="深圳市司法局" readonly |
|||
:rules="[{ required: true, message: '请选择执法单位' }]" required /> |
|||
|
|||
<!-- 上传时间 --> |
|||
<van-field v-model="form.uploadTime" label="上传时间" placeholder="" readonly |
|||
:rules="[{ required: true, message: '请选择上传时间' }]" required /> |
|||
|
|||
<!-- 执法区域 --> |
|||
<van-field v-model="form.enforcementArea" label="执法区域" placeholder="请输入" |
|||
:rules="[{ required: true, message: '请输入执法区域' }]" required /> |
|||
|
|||
<!-- 附件上传 --> |
|||
<div class="upload-section"> |
|||
<div class="upload-label"> |
|||
<span class="required">*</span> |
|||
附 件 |
|||
<span class="upload-hint">(仅限PDF文档,5MB以下)</span> |
|||
<a href="#" class="template-download" @click.prevent="downloadTemplate"> |
|||
<van-icon name="down" /> 模板下载 |
|||
</a> |
|||
</div> |
|||
|
|||
<van-uploader v-model="fileList" :max-count="1" :max-size="5 * 1024 * 1024" |
|||
@oversize="onOversize" accept=".pdf" :after-read="afterRead"> |
|||
<div class="upload-area"> |
|||
<van-icon name="photograph" size="40" color="#dcdee0" /> |
|||
<p>点击上传</p> |
|||
</div> |
|||
</van-uploader> |
|||
</div> |
|||
|
|||
<!-- 提示信息 --> |
|||
<div class="notice-bar upload-notice"> |
|||
<van-icon name="info-o" /> |
|||
<span>仅支持上传5MB以下的PDF格式文档</span> |
|||
</div> |
|||
<div class="submit-section"> |
|||
<van-button type="primary" block round class="submit-btn" native-type="submit"> |
|||
发布 |
|||
</van-button> |
|||
</div> |
|||
</van-form> |
|||
</div> |
|||
|
|||
</div> |
|||
<!-- 发布按钮 --> |
|||
|
|||
<!-- 查询列表 --> |
|||
<div class="query-container" v-if="activeTab === 'query'"> |
|||
<!-- 搜索栏 --> |
|||
<van-search v-model="querySearchText" show-action placeholder="请输入计划名称/执法单位/区域查询" clearable |
|||
clear-trigger="always" @clear="onQueryClear" @search="onQuerySearch"> |
|||
<template #action> |
|||
<div @click="onQuerySearch">搜索</div> |
|||
</template> |
|||
</van-search> |
|||
|
|||
<!-- 日期快捷筛选按钮 --> |
|||
<div class="search-button-list"> |
|||
<van-button plain hairline :class="queryDateType === null ? 'search-btn active' : 'search-btn'" |
|||
@click="querySearchDateType(null)" round type="primary" size="mini">全部</van-button> |
|||
<van-button plain hairline :class="queryDateType === 1 ? 'search-btn active' : 'search-btn'" |
|||
@click="querySearchDateType(1)" round type="primary" size="mini">本月</van-button> |
|||
<van-button plain hairline :class="queryDateType === 2 ? 'search-btn active' : 'search-btn'" |
|||
@click="querySearchDateType(2)" round type="primary" size="mini">本季</van-button> |
|||
<van-button plain hairline :class="queryDateType === 3 ? 'search-btn active' : 'search-btn'" |
|||
@click="querySearchDateType(3)" round type="primary" size="mini">本年</van-button> |
|||
<van-button plain hairline :class="queryDateType === 9 ? 'search-btn active' : 'search-btn'" |
|||
@click="openQueryDatePicker" round type="primary" size="mini">筛选</van-button> |
|||
</div> |
|||
|
|||
<!-- 列表区域 --> |
|||
<div class="plan-list"> |
|||
<van-pull-refresh v-model="queryRefreshing" @refresh="onQueryRefresh"> |
|||
<van-list v-model:loading="queryLoading" :finished="queryFinished" finished-text="" |
|||
@load="onQueryLoad" :offset="50"> |
|||
<van-empty v-if="queryFinished && queryList.length === 0" description="暂无数据" /> |
|||
|
|||
<div class="plan-card" v-for="item in queryList" :key="item.id"> |
|||
<div class="plan-time">发布时间:{{ item.publishTime }}</div> |
|||
<div class="plan-title">{{ item.title }}</div> |
|||
<div class="plan-info"> |
|||
<span class="info-label">执法单位:</span> |
|||
<span>{{ item.enforcementUnit }}</span> |
|||
</div> |
|||
<div class="plan-info"> |
|||
<span class="info-label">执法区域:</span> |
|||
<span>{{ item.area }}</span> |
|||
</div> |
|||
<div class="plan-info"> |
|||
<span class="info-label">附 件:</span> |
|||
<a href="#" class="file-link" @click.prevent="downloadFile(item)">{{ item.fileName |
|||
}}</a> |
|||
</div> |
|||
<div class="plan-actions"> |
|||
<a href="#" class="cancel-link" @click.prevent="cancelPublish(item)"> |
|||
<van-icon name="close" /> 取消公开 |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</van-list> |
|||
</van-pull-refresh> |
|||
</div> |
|||
|
|||
<!-- 日历选择器 --> |
|||
<van-calendar v-model:show="showQueryCalendar" type="range" @confirm="onQueryDateConfirm" |
|||
:min-date="queryMinDate" :max-date="queryMaxDate" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 底部导航栏 --> |
|||
<FooterBar active="program" /> |
|||
|
|||
<!-- 年份选择器 --> |
|||
<van-popup v-model:show="showYearPicker" position="bottom" round> |
|||
<van-picker :columns="yearColumns" title="选择年份" @confirm="onYearConfirm" @cancel="showYearPicker = false" /> |
|||
</van-popup> |
|||
|
|||
<!-- 周期选择器 --> |
|||
<van-popup v-model:show="showPeriodPicker" position="bottom" round> |
|||
<van-picker :columns="periodColumns" title="选择周期" @confirm="onPeriodConfirm" |
|||
@cancel="showPeriodPicker = false" /> |
|||
</van-popup> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="enforcementProgram"> |
|||
import { ref, reactive, onMounted } from 'vue'; |
|||
import { showToast } from 'vant'; |
|||
import FooterBar from '@/components/FooterBar.vue'; |
|||
|
|||
const activeTab = ref('create'); |
|||
|
|||
const form = reactive({ |
|||
planName: '', |
|||
planPeriod: '', |
|||
enforcementUnit: '深圳市司法局', |
|||
uploadTime: new Date().toLocaleString('zh-CN', { |
|||
year: 'numeric', |
|||
month: '2-digit', |
|||
day: '2-digit', |
|||
hour: '2-digit', |
|||
minute: '2-digit', |
|||
second: '2-digit', |
|||
hour12: false |
|||
}).replace(/\//g, '-'), |
|||
enforcementArea: '' |
|||
}); |
|||
|
|||
const fileList = ref([]); |
|||
const showYearPicker = ref(false); |
|||
const showPeriodPicker = ref(false); |
|||
|
|||
const yearColumns = [ |
|||
{ text: '2026年', value: '2026' }, |
|||
{ text: '2027年', value: '2027' }, |
|||
{ text: '2028年', value: '2028' } |
|||
]; |
|||
|
|||
const periodColumns = [ |
|||
{ text: '年度', value: 'year' }, |
|||
{ text: '第一季度', value: 'Q1' }, |
|||
{ text: '第二季度', value: 'Q2' }, |
|||
{ text: '第三季度', value: 'Q3' }, |
|||
{ text: '第四季度', value: 'Q4' } |
|||
]; |
|||
|
|||
const onYearConfirm = ({ selectedOptions }) => { |
|||
form.planName = selectedOptions[0]?.text || ''; |
|||
showYearPicker.value = false; |
|||
}; |
|||
|
|||
const onPeriodConfirm = ({ selectedOptions }) => { |
|||
form.planPeriod = selectedOptions[0]?.text || ''; |
|||
showPeriodPicker.value = false; |
|||
}; |
|||
|
|||
const afterRead = (file) => { |
|||
file.status = 'uploading'; |
|||
file.message = '上传中...'; |
|||
|
|||
setTimeout(() => { |
|||
file.status = 'done'; |
|||
file.message = ''; |
|||
}, 1000); |
|||
}; |
|||
|
|||
const onOversize = () => { |
|||
showToast('文件大小不能超过 5MB'); |
|||
}; |
|||
|
|||
const downloadTemplate = () => { |
|||
showToast('模板下载功能开发中'); |
|||
}; |
|||
|
|||
const submitForm = () => { |
|||
if (!form.planName) { |
|||
showToast('请选择计划年份'); |
|||
return; |
|||
} |
|||
if (!form.planPeriod) { |
|||
showToast('请选择计划周期'); |
|||
return; |
|||
} |
|||
if (!form.enforcementArea) { |
|||
showToast('请输入执法区域'); |
|||
return; |
|||
} |
|||
if (fileList.value.length === 0) { |
|||
showToast('请上传附件'); |
|||
return; |
|||
} |
|||
|
|||
showToast('发布成功'); |
|||
}; |
|||
|
|||
// ========== 查询列表相关 ========== |
|||
const querySearchText = ref(''); |
|||
const queryDateType = ref(null); |
|||
const showQueryCalendar = ref(false); |
|||
const queryMinDate = new Date(2024, 0, 1); |
|||
const queryMaxDate = new Date(2030, 11, 31); |
|||
|
|||
// 列表状态 |
|||
const queryList = ref([]); |
|||
const queryLoading = ref(false); |
|||
const queryFinished = ref(false); |
|||
const queryRefreshing = ref(false); |
|||
|
|||
// 分页参数 |
|||
const queryPageNum = ref(1); |
|||
const queryPageSize = ref(10); |
|||
|
|||
// 模拟数据 |
|||
const mockPlanData = [ |
|||
{ |
|||
id: 1, |
|||
publishTime: '2026-01-27 16:36:57', |
|||
title: '深圳市司法局2026年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '0127测试区域', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
publishTime: '2026-01-20 09:49:26', |
|||
title: '深圳市司法局2027年一季度行政执法计划', |
|||
enforcementUnit: '深圳市司法局', |
|||
area: '测试区域0120', |
|||
fileName: '深圳市城市管理和综合执法局2024年度联合检查计划_合并.pdf' |
|||
}, |
|||
]; |
|||
|
|||
// 加载数据 |
|||
const fetchQueryList = () => { |
|||
queryLoading.value = true; |
|||
|
|||
setTimeout(() => { |
|||
const startIndex = (queryPageNum.value - 1) * queryPageSize.value; |
|||
const endIndex = startIndex + queryPageSize.value; |
|||
|
|||
let filteredData = [...mockPlanData]; |
|||
|
|||
// 关键字搜索过滤 |
|||
if (querySearchText.value) { |
|||
filteredData = filteredData.filter(item => |
|||
item.title.includes(querySearchText.value) || |
|||
item.enforcementUnit.includes(querySearchText.value) || |
|||
item.area.includes(querySearchText.value) |
|||
); |
|||
} |
|||
|
|||
// 分页切片 |
|||
const pageData = filteredData.slice(startIndex, endIndex); |
|||
|
|||
if (queryRefreshing.value) { |
|||
queryList.value = pageData; |
|||
queryRefreshing.value = false; |
|||
} else { |
|||
queryList.value = [...queryList.value, ...pageData]; |
|||
} |
|||
|
|||
// 判断是否加载完毕 |
|||
if (pageData.length === 0 || queryList.value.length >= filteredData.length) { |
|||
queryFinished.value = true; |
|||
} else { |
|||
queryPageNum.value++; |
|||
} |
|||
|
|||
queryLoading.value = false; |
|||
}, 500); |
|||
}; |
|||
|
|||
// 触底加载更多 |
|||
const onQueryLoad = () => { |
|||
if (queryRefreshing.value) return; |
|||
fetchQueryList(); |
|||
}; |
|||
|
|||
// 下拉刷新 |
|||
const onQueryRefresh = () => { |
|||
queryPageNum.value = 1; |
|||
queryList.value = []; |
|||
queryFinished.value = false; |
|||
queryLoading.value = true; |
|||
fetchQueryList(); |
|||
}; |
|||
|
|||
// 搜索 |
|||
const onQuerySearch = () => { |
|||
queryPageNum.value = 1; |
|||
queryList.value = []; |
|||
queryFinished.value = false; |
|||
queryLoading.value = true; |
|||
fetchQueryList(); |
|||
}; |
|||
|
|||
// 清空搜索 |
|||
const onQueryClear = () => { |
|||
querySearchText.value = ''; |
|||
onQuerySearch(); |
|||
}; |
|||
|
|||
// 日期类型切换 |
|||
const querySearchDateType = (val) => { |
|||
queryDateType.value = val; |
|||
if (val !== null && val !== 9) { |
|||
onQuerySearch(); |
|||
} else if (val === null) { |
|||
onQuerySearch(); |
|||
} else { |
|||
showQueryCalendar.value = true; |
|||
} |
|||
}; |
|||
|
|||
// 打开日期选择器 |
|||
const openQueryDatePicker = () => { |
|||
queryDateType.value = 9; |
|||
showQueryCalendar.value = true; |
|||
}; |
|||
|
|||
// 日历确认 |
|||
const onQueryDateConfirm = (dates) => { |
|||
const [start, end] = dates; |
|||
const format = (date) => { |
|||
const y = date.getFullYear(); |
|||
const m = String(date.getMonth() + 1).padStart(2, '0'); |
|||
const d = String(date.getDate()).padStart(2, '0'); |
|||
return `${y}-${m}-${d}`; |
|||
}; |
|||
showToast(`已选择:${format(start)} 至 ${format(end)}`); |
|||
showQueryCalendar.value = false; |
|||
onQuerySearch(); |
|||
}; |
|||
|
|||
// 下载文件 |
|||
const downloadFile = (item) => { |
|||
showToast(`正在下载:${item.fileName}`); |
|||
}; |
|||
|
|||
// 取消公开 |
|||
const cancelPublish = (item) => { |
|||
showToast('取消公开功能开发中'); |
|||
}; |
|||
|
|||
// 初始化加载 |
|||
onMounted(() => { |
|||
fetchQueryList(); |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.program-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
min-height: 100vh; |
|||
background: #f5f7fa; |
|||
} |
|||
|
|||
.tab-header { |
|||
display: flex; |
|||
background: #fff; |
|||
border-bottom: 1px solid #ebedf0; |
|||
flex-shrink: 0; |
|||
|
|||
.tab-item { |
|||
flex: 1; |
|||
text-align: center; |
|||
padding: 14px 0; |
|||
font-size: 15px; |
|||
color: #666; |
|||
position: relative; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
|
|||
&.active { |
|||
color: #000; |
|||
font-weight: 500; |
|||
|
|||
&::after { |
|||
content: ''; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
width: 160px; |
|||
height: 1px; |
|||
background: #1989fa; |
|||
border-radius: 2px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.content-wrapper { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
overflow: hidden; |
|||
max-height: calc(100vh - 44px - 50px); |
|||
} |
|||
|
|||
.form-container { |
|||
background: #fff; |
|||
margin-top: 10px; |
|||
padding: 4px; |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.period-field { |
|||
:deep(.van-field__label) { |
|||
display: none; |
|||
} |
|||
|
|||
:deep(.van-cell__value) { |
|||
padding-left: 0; |
|||
} |
|||
} |
|||
|
|||
.notice-bar { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
padding: 10px 16px; |
|||
margin: 0 6px; |
|||
background: #fffbe8; |
|||
color: #ed6a0c; |
|||
font-size: 13px; |
|||
border-radius: 8px; |
|||
|
|||
.van-icon { |
|||
font-size: 16px; |
|||
} |
|||
|
|||
&.upload-notice { |
|||
margin: 6px; |
|||
} |
|||
} |
|||
|
|||
.upload-section { |
|||
padding: 16px 0; |
|||
|
|||
.upload-label { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 12px; |
|||
font-size: 14px; |
|||
color: #323233; |
|||
|
|||
.required { |
|||
color: #ee0a24; |
|||
margin-right: 4px; |
|||
} |
|||
|
|||
.upload-hint { |
|||
color: #969799; |
|||
font-size: 12px; |
|||
margin-left: 4px; |
|||
} |
|||
|
|||
.template-download { |
|||
margin-left: auto; |
|||
color: #ed6a0c; |
|||
font-size: 13px; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
|
|||
&:active { |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.upload-area { |
|||
width: 120px; |
|||
height: 120px; |
|||
border: 1px dashed #dcdee0; |
|||
border-radius: 8px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
|
|||
&:active { |
|||
background: #f7f8fa; |
|||
border-color: #1989fa; |
|||
} |
|||
|
|||
p { |
|||
margin-top: 8px; |
|||
font-size: 12px; |
|||
color: #969799; |
|||
} |
|||
} |
|||
|
|||
:deep(.van-uploader) { |
|||
width: auto; |
|||
} |
|||
|
|||
:deep(.van-uploader__preview) { |
|||
margin: 0; |
|||
} |
|||
|
|||
:deep(.van-uploader__preview-image) { |
|||
width: 120px; |
|||
height: 120px; |
|||
} |
|||
} |
|||
|
|||
.submit-section { |
|||
padding: 30px 8px 20px; |
|||
|
|||
.submit-btn { |
|||
height: 44px; |
|||
font-size: 16px; |
|||
letter-spacing: 4px; |
|||
} |
|||
} |
|||
|
|||
.query-container { |
|||
padding: 0; |
|||
background: #f5f7fa; |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
overflow: hidden; |
|||
|
|||
:deep(.van-search) { |
|||
flex-shrink: 0; |
|||
background: #fff; |
|||
} |
|||
|
|||
.search-button-list { |
|||
display: flex; |
|||
gap: 8px; |
|||
padding: 12px 16px 4px 16px; |
|||
flex-wrap: wrap; |
|||
flex-shrink: 0; |
|||
|
|||
.search-btn { |
|||
padding: 0 12px; |
|||
height: 28px; |
|||
font-size: 13px; |
|||
color: #666; |
|||
border-color: #ebebeb; |
|||
background: #ebebeb; |
|||
|
|||
&.active { |
|||
color: #1989fa; |
|||
border-color: #1989fa; |
|||
background: #e8f4ff; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.plan-list { |
|||
padding: 10px; |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.plan-card { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
margin-bottom: 10px; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); |
|||
|
|||
.plan-time { |
|||
font-size: 13px; |
|||
color: #969799; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
.plan-title { |
|||
font-size: 16px; |
|||
font-weight: 560; |
|||
color: #000; |
|||
line-height: 1.5; |
|||
padding: 20px 0px; |
|||
border-top: 1px solid #ebedf0; |
|||
} |
|||
|
|||
.plan-info { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 14px; |
|||
color: #646566; |
|||
margin-bottom: 8px; |
|||
line-height: 1.6; |
|||
|
|||
.info-label { |
|||
flex-shrink: 0; |
|||
color: #333; |
|||
padding: 4px 0px; |
|||
} |
|||
|
|||
.file-link { |
|||
color: #1989fa; |
|||
text-decoration: none; |
|||
word-break: break-all; |
|||
|
|||
&:active { |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.plan-actions { |
|||
text-align: right; |
|||
margin-top: 12px; |
|||
border-top: 1px solid #ebedf0; |
|||
|
|||
.cancel-link { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
font-size: 13px; |
|||
color: #969799; |
|||
text-decoration: none; |
|||
|
|||
&:active { |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* ===== Vant 搜索框样式覆盖 ===== */ |
|||
:deep(.van-search__content) { |
|||
height: 0.9rem; |
|||
border: 1px solid #ececec; |
|||
border-radius: 0.15rem 0 0 0.15rem; |
|||
} |
|||
|
|||
:deep(.van-search__action) { |
|||
margin: 0 3% 0 0; |
|||
width: 10%; |
|||
height: 0.9rem; |
|||
background: #1367fe; |
|||
border: 0.01rem solid #1367fe; |
|||
border-radius: 0 0.15rem 0.15rem 0; |
|||
color: #fff; |
|||
text-align: center; |
|||
line-height: 0.9rem; |
|||
} |
|||
</style> |
|||
@ -1,43 +1,561 @@ |
|||
<template> |
|||
<div class="query-page app_content"> |
|||
<div class="page-header"> |
|||
<div class="page-title">查询</div> |
|||
<div class="registration-list-page"> |
|||
<!-- 搜索栏 --> |
|||
<van-search v-model="searchText" show-action placeholder="企业名称" clearable clear-trigger="always" |
|||
@clear="onClear" @search="onSearch"> |
|||
<template #action> |
|||
<div @click="onSearch">搜索</div> |
|||
</template> |
|||
</van-search> |
|||
|
|||
<!-- 日期快捷筛选按钮 --> |
|||
<div class="search-button-list"> |
|||
<van-button plain hairline :class="dateType === 3 ? 'search-btn active' : 'search-btn'" |
|||
@click="searchDateType(3)" round type="primary" size="mini">自定义</van-button> |
|||
<van-button plain hairline :class="dateType === 9 ? 'search-btn active' : 'search-btn'" |
|||
@click="openDatePicker" round type="primary" size="mini">次数</van-button> |
|||
</div> |
|||
<div class="page-body"> |
|||
<van-empty description="查询功能开发中" /> |
|||
<div class="date-picker-container"> |
|||
<van-notice-bar left-icon="info-o"> |
|||
{{ noticeText }} |
|||
</van-notice-bar> |
|||
</div> |
|||
|
|||
<!-- 可滚动列表区域 --> |
|||
<div class="list-container"> |
|||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> |
|||
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad" :offset="50"> |
|||
<!-- 空状态 --> |
|||
<van-empty v-if="finished && list.length === 0" description="暂无数据" /> |
|||
|
|||
<!-- 列表项 --> |
|||
<div class="enterprise-card" v-for="item in list" :key="item.id" @click="toggleExpand(item.id)"> |
|||
<div class="enterprise-header"> |
|||
<span class="enterprise-date">{{ item.enforcementTime || item.createTime || '-' }}</span> |
|||
</div> |
|||
<div class="enterprise-body"> |
|||
<div class="enterprise-name">{{ item.enterpriseName || item.name || '-' }}</div> |
|||
<div class="enterprise-count">被执法次数:{{ item.enforceCount || item.count || 0 }}次</div> |
|||
</div> |
|||
<div class="enterprise-arrow"> |
|||
<van-icon :name="expandedIds.includes(item.id) ? 'arrow-up' : 'arrow-down'" /> |
|||
</div> |
|||
|
|||
<!-- 展开内容 |
|||
<div class="enterprise-detail" v-if="expandedIds.includes(item.id)"> |
|||
<div class="detail-item"> |
|||
<span class="detail-label">执法单位:</span> |
|||
<span>{{ item.lawEnforcement || '-' }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="detail-label">执法人员:</span> |
|||
<span>{{ getEnforcerDisplay(item) }}</span> |
|||
</div> |
|||
<div class="detail-item"> |
|||
<span class="detail-label">执法项目:</span> |
|||
<span>{{ item.lawEnforcementItemText || '-' }}</span> |
|||
</div> |
|||
</div> --> |
|||
</div> |
|||
</van-list> |
|||
</van-pull-refresh> |
|||
</div> |
|||
<van-calendar v-model:show="dateShow" type="range" @confirm="onConfirm" :min-date="minDate" |
|||
:max-date="maxDate" /> |
|||
<van-popup v-model:show="showDatePicker" position="bottom" :style="{ height: '30%' }"> |
|||
<div class="filter-popup-content"> |
|||
<div class="filter-row"> |
|||
<span class="filter-label">次数:</span> |
|||
<input type="text" v-model="countMin" class="filter-input" placeholder="最小值"> |
|||
<span class="filter-separator">-</span> |
|||
<input type="text" v-model="countMax" class="filter-input" placeholder="最大值"> |
|||
</div> |
|||
<div class="filter-btn-row"> |
|||
<van-button type="primary" size="small" @click="onFilterSearch">查询</van-button> |
|||
</div> |
|||
</div> |
|||
</van-popup> |
|||
<!-- 底部导航栏 --> |
|||
<FooterBar active="query" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="enforcementQuery"> |
|||
import { ref, computed, onMounted } from 'vue'; |
|||
import FooterBar from '@/components/FooterBar.vue'; |
|||
|
|||
// ========== 列表状态 ========== |
|||
const list = ref([]); |
|||
const loading = ref(false); |
|||
const finished = ref(false); |
|||
const refreshing = ref(false); |
|||
const searchText = ref(''); |
|||
|
|||
// ========== 分页参数 ========== |
|||
const pageNum = ref(1); |
|||
const pageSize = ref(10); |
|||
|
|||
// ========== 日期筛选 ========== |
|||
const dateType = ref(null); |
|||
const dateSearch = ref(null); |
|||
const dateSearchDisplay = ref(''); |
|||
const dateShow = ref(false); |
|||
const showDatePicker = ref(false); |
|||
const minDate = new Date(2020, 0, 1); |
|||
const maxDate = new Date(2030, 11, 31); |
|||
|
|||
// ========== 次数筛选 ========== |
|||
const countMin = ref(''); |
|||
const countMax = ref(''); |
|||
|
|||
// ========== 展开状态 ========== |
|||
const expandedIds = ref([]); |
|||
|
|||
// ========== 切换展开/收起 ========== |
|||
const toggleExpand = (id) => { |
|||
const index = expandedIds.value.indexOf(id); |
|||
if (index > -1) { |
|||
expandedIds.value.splice(index, 1); |
|||
} else { |
|||
expandedIds.value.push(id); |
|||
} |
|||
}; |
|||
|
|||
// ========== 提示信息文本 ========== |
|||
const noticeText = computed(() => { |
|||
if (dateSearchDisplay.value) { |
|||
return dateSearchDisplay.value; |
|||
} |
|||
if (countMin.value || countMax.value) { |
|||
const min = countMin.value || '0'; |
|||
const max = countMax.value || '不限'; |
|||
return `次数:${min} - ${max}`; |
|||
} |
|||
return '请输入关键字查询或选择筛选条件'; |
|||
}); |
|||
|
|||
// ========== 格式化日期 ========== |
|||
const formatDate = (dateStr) => { |
|||
if (!dateStr) return '-'; |
|||
const d = new Date(dateStr.replace ? dateStr.replace(/-/g, '/') : dateStr); |
|||
if (isNaN(d.getTime())) return dateStr; |
|||
const y = d.getFullYear(); |
|||
const m = String(d.getMonth() + 1).padStart(2, '0'); |
|||
const day = String(d.getDate()).padStart(2, '0'); |
|||
return `${y}-${m}-${day}`; |
|||
}; |
|||
|
|||
// ========== 执法人员展示 ========== |
|||
const getEnforcerDisplay = (item) => { |
|||
if (item.lawEnforcerDisplay) return item.lawEnforcerDisplay; |
|||
const names = []; |
|||
if (item.lawEnforcer) names.push(item.lawEnforcer); |
|||
if (item.lawEnforcerTwo) names.push(item.lawEnforcerTwo); |
|||
if (Array.isArray(item.lawEnforcer2List)) names.push(...item.lawEnforcer2List); |
|||
return names.length ? names.join('、') : '-'; |
|||
}; |
|||
|
|||
// ========== 模拟数据 ========== |
|||
const mockData = [ |
|||
{ |
|||
id: 1, |
|||
enforcementTime: '2026-04-28', |
|||
enterpriseName: '深圳市地铁集团有限公司', |
|||
enforceCount: 53, |
|||
lawEnforcement: '市交通运输局', |
|||
lawEnforcerDisplay: '张三、李四', |
|||
lawEnforcementItemText: '行政检查' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
enforcementTime: '2026-05-19', |
|||
enterpriseName: '中建科工集团有限公司', |
|||
enforceCount: 13, |
|||
lawEnforcement: '市住房和建设局', |
|||
lawEnforcerDisplay: '王五、赵六', |
|||
lawEnforcementItemText: '行政处罚' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
enforcementTime: '2026-05-08', |
|||
enterpriseName: '深圳市建工集团股份有限公司', |
|||
enforceCount: 9, |
|||
lawEnforcement: '市应急管理局', |
|||
lawEnforcerDisplay: '孙七、周八', |
|||
lawEnforcementItemText: '行政强制' |
|||
}, |
|||
{ |
|||
id: 4, |
|||
enforcementTime: '2026-03-15', |
|||
enterpriseName: '深圳市科技创新有限公司', |
|||
enforceCount: 25, |
|||
lawEnforcement: '市市场监督管理局', |
|||
lawEnforcerDisplay: '吴九、郑十', |
|||
lawEnforcementItemText: '行政许可' |
|||
}, |
|||
{ |
|||
id: 5, |
|||
enforcementTime: '2026-04-22', |
|||
enterpriseName: '深圳市商贸发展有限公司', |
|||
enforceCount: 7, |
|||
lawEnforcement: '市商务局', |
|||
lawEnforcerDisplay: '冯十一、陈十二', |
|||
lawEnforcementItemText: '行政检查' |
|||
}, |
|||
{ |
|||
id: 6, |
|||
enforcementTime: '2026-02-10', |
|||
enterpriseName: '深圳市制造业有限公司', |
|||
enforceCount: 31, |
|||
lawEnforcement: '市工业和信息化局', |
|||
lawEnforcerDisplay: '褚十三、卫十四', |
|||
lawEnforcementItemText: '行政处罚' |
|||
}, |
|||
{ |
|||
id: 7, |
|||
enforcementTime: '2026-01-20', |
|||
enterpriseName: '深圳市文化创意有限公司', |
|||
enforceCount: 5, |
|||
lawEnforcement: '市文化广电旅游体育局', |
|||
lawEnforcerDisplay: '蒋十五、沈十六', |
|||
lawEnforcementItemText: '行政确认' |
|||
}, |
|||
{ |
|||
id: 8, |
|||
enforcementTime: '2025-12-08', |
|||
enterpriseName: '深圳市教育培训有限公司', |
|||
enforceCount: 18, |
|||
lawEnforcement: '市教育局', |
|||
lawEnforcerDisplay: '韩十七、杨十八', |
|||
lawEnforcementItemText: '行政检查' |
|||
} |
|||
]; |
|||
|
|||
// ========== 加载数据(使用模拟数据)========== |
|||
const fetchList = () => { |
|||
loading.value = true; |
|||
|
|||
setTimeout(() => { |
|||
const startIndex = (pageNum.value - 1) * pageSize.value; |
|||
const endIndex = startIndex + pageSize.value; |
|||
|
|||
let filteredData = [...mockData]; |
|||
|
|||
// 关键字搜索过滤 |
|||
if (searchText.value) { |
|||
filteredData = filteredData.filter(item => |
|||
item.enterpriseName.includes(searchText.value) |
|||
); |
|||
} |
|||
|
|||
// 次数范围过滤 |
|||
if (countMin.value || countMax.value) { |
|||
const min = countMin.value ? parseInt(countMin.value) : 0; |
|||
const max = countMax.value ? parseInt(countMax.value) : Infinity; |
|||
filteredData = filteredData.filter(item => |
|||
item.enforceCount >= min && item.enforceCount <= max |
|||
); |
|||
} |
|||
|
|||
// 分页切片 |
|||
const pageData = filteredData.slice(startIndex, endIndex); |
|||
|
|||
if (refreshing.value) { |
|||
list.value = pageData; |
|||
refreshing.value = false; |
|||
} else { |
|||
list.value = [...list.value, ...pageData]; |
|||
} |
|||
|
|||
// 判断是否加载完毕 |
|||
if (pageData.length === 0 || list.value.length >= filteredData.length) { |
|||
finished.value = true; |
|||
} else { |
|||
pageNum.value++; |
|||
} |
|||
|
|||
loading.value = false; |
|||
}, 500); |
|||
}; |
|||
// ========== 日历确认(格式化 yyyy-MM-DD)========== |
|||
const onConfirm = (dates) => { |
|||
const [start, end] = dates; |
|||
const format = (date) => { |
|||
const y = date.getFullYear(); |
|||
const m = String(date.getMonth() + 1).padStart(2, '0'); |
|||
const d = String(date.getDate()).padStart(2, '0'); |
|||
return `${y}-${m}-${d}`; |
|||
}; |
|||
dateSearchDisplay.value = `${format(start)} 至 ${format(end)}`; |
|||
dateSearch.value = `${format(start)}_${format(end)}`; |
|||
dateType.value = 3; |
|||
dateShow.value = false; |
|||
onSearch(); |
|||
}; |
|||
// ========== 触底加载更多 ========== |
|||
const onLoad = () => { |
|||
if (refreshing.value) return; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// ========== 下拉刷新 ========== |
|||
const onRefresh = () => { |
|||
pageNum.value = 1; |
|||
list.value = []; |
|||
finished.value = false; |
|||
loading.value = true; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// ========== 搜索 ========== |
|||
const onSearch = () => { |
|||
pageNum.value = 1; |
|||
list.value = []; |
|||
finished.value = false; |
|||
loading.value = true; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// ========== 次数筛选查询 ========== |
|||
const onFilterSearch = () => { |
|||
showDatePicker.value = false; |
|||
pageNum.value = 1; |
|||
list.value = []; |
|||
finished.value = false; |
|||
loading.value = true; |
|||
fetchList(); |
|||
}; |
|||
|
|||
// ========== 清空搜索 ========== |
|||
const onClear = () => { |
|||
searchText.value = ''; |
|||
onSearch(); |
|||
}; |
|||
|
|||
// ========== 日期类型切换 ========== |
|||
const searchDateType = (val) => { |
|||
dateType.value = val; |
|||
dateShow.value = true; |
|||
onSearch(); |
|||
}; |
|||
|
|||
// ========== 打开月份选择器 ========== |
|||
const openDatePicker = () => { |
|||
dateType.value = 9; |
|||
showDatePicker.value = true; |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
fetchList(); |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.query-page { |
|||
.registration-list-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background: #f0f6ff; |
|||
background-color: #ebf3fb; |
|||
padding-bottom: calc(56px + env(safe-area-inset-bottom)); |
|||
|
|||
.list-container { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 0 0.27rem; |
|||
margin-top: 0.27rem; |
|||
} |
|||
} |
|||
|
|||
.date-picker-container { |
|||
margin: 0px 10px; |
|||
|
|||
:deep(.van-notice-bar) { |
|||
border-radius: 8px; |
|||
height: 28px; |
|||
font-size: 12px; |
|||
padding: 0px 10px; |
|||
} |
|||
} |
|||
|
|||
.page-header { |
|||
height: 44px; |
|||
line-height: 44px; |
|||
/* ===== 搜索按钮行 ===== */ |
|||
.search-button-list { |
|||
display: flex; |
|||
flex-direction: row; |
|||
padding: 0.2rem 0.27rem 0; |
|||
gap: 0.15rem; |
|||
margin-bottom: 8px; |
|||
|
|||
.search-btn { |
|||
width: 60px; |
|||
height: 20px; |
|||
background: #ebebeb; |
|||
border-color: #ebebeb; |
|||
color: #999; |
|||
font-size: 0.3rem; |
|||
margin: 4px 0px; |
|||
} |
|||
|
|||
.search-btn.active { |
|||
background-color: #d9e6fd; |
|||
border-color: #75a5fb; |
|||
color: #1367fe; |
|||
} |
|||
} |
|||
|
|||
/* ===== Vant 搜索框样式覆盖 ===== */ |
|||
:deep(.van-search__content) { |
|||
height: 0.9rem; |
|||
border: 1px solid #ececec; |
|||
border-radius: 0.15rem 0 0 0.15rem; |
|||
} |
|||
|
|||
:deep(.van-search__action) { |
|||
margin: 0 3% 0 0; |
|||
width: 10%; |
|||
height: 0.9rem; |
|||
background: #1367fe; |
|||
border: 0.01rem solid #1367fe; |
|||
border-radius: 0 0.15rem 0.15rem 0; |
|||
color: #fff; |
|||
text-align: center; |
|||
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 100%); |
|||
line-height: 0.9rem; |
|||
} |
|||
|
|||
/* ===== 企业卡片样式 ===== */ |
|||
.enterprise-card { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
margin-bottom: 12px; |
|||
padding: 16px; |
|||
position: relative; |
|||
cursor: pointer; |
|||
transition: all 0.2s; |
|||
|
|||
&:active { |
|||
background: #f8f9fa; |
|||
} |
|||
} |
|||
|
|||
.enterprise-header { |
|||
margin-bottom: 8px; |
|||
.enterprise-date { |
|||
color: #999; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.enterprise-body { |
|||
margin-left: 10px; |
|||
padding: 10px; |
|||
.enterprise-name { |
|||
font-size: 16px; |
|||
font-weight: 560; |
|||
color: #333; |
|||
margin-bottom: 6px; |
|||
line-height: 1.4; |
|||
padding: 10px 0px; |
|||
} |
|||
|
|||
.enterprise-count { |
|||
font-size: 13px; |
|||
color: #666; |
|||
margin-bottom: 20px; |
|||
} |
|||
} |
|||
|
|||
.enterprise-arrow { |
|||
position: absolute; |
|||
left: 45%; |
|||
bottom: 16px; |
|||
color: #999; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.enterprise-detail { |
|||
margin-top: 12px; |
|||
padding-top: 12px; |
|||
border-top: 1px solid #f0f0f0; |
|||
|
|||
.detail-item { |
|||
display: flex; |
|||
font-size: 13px; |
|||
color: #666; |
|||
line-height: 1.8; |
|||
|
|||
.detail-label { |
|||
color: #999; |
|||
white-space: nowrap; |
|||
margin-right: 8px; |
|||
min-width: 70px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* ===== Vant 搜索框样式覆盖 ===== */ |
|||
:deep(.van-search__content) { |
|||
height: 0.9rem; |
|||
border: 1px solid #ececec; |
|||
border-radius: 0.15rem 0 0 0.15rem; |
|||
} |
|||
|
|||
:deep(.van-search__action) { |
|||
margin: 0 3% 0 0; |
|||
width: 10%; |
|||
height: 0.9rem; |
|||
background: #1367fe; |
|||
border: 0.01rem solid #1367fe; |
|||
border-radius: 0 0.15rem 0.15rem 0; |
|||
color: #fff; |
|||
font-size: 17px; |
|||
font-weight: 500; |
|||
text-align: center; |
|||
line-height: 0.9rem; |
|||
} |
|||
|
|||
/* ===== 筛选弹窗样式 ===== */ |
|||
.filter-popup-content { |
|||
padding: 20px 16px; |
|||
} |
|||
|
|||
.page-body { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
.filter-row { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 16px; |
|||
|
|||
.filter-label { |
|||
font-size: 14px; |
|||
color: #333; |
|||
white-space: nowrap; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.filter-input { |
|||
width: 80px; |
|||
height: 36px; |
|||
border: 1px solid #e5e5e5; |
|||
border-radius: 6px; |
|||
padding: 0 12px; |
|||
font-size: 14px; |
|||
outline: none; |
|||
|
|||
&:focus { |
|||
border-color: #1989fa; |
|||
} |
|||
} |
|||
|
|||
.filter-separator { |
|||
margin: 0 10px; |
|||
color: #999; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.filter-btn-row { |
|||
display: flex; |
|||
justify-content: center; |
|||
padding-top: 8px; |
|||
|
|||
:deep(.van-button) { |
|||
width: 120px; |
|||
height: 40px; |
|||
} |
|||
} |
|||
</style> |
|||
</style> |
|||
Loading…
Reference in new issue