Browse Source

计划模块,后台地址代理

dev-V1.0.1
wangmingyong 4 months ago
parent
commit
91002abb3b
  1. 3
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js
  2. 532
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/components/ProgramCreate.vue
  3. 373
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/components/ProgramQuery.vue
  4. 697
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/index.vue
  5. 6
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-config/index.js
  6. 2
      zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-dev

3
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/index.js

@ -5,7 +5,8 @@ import { _get, _post, _form, _delete } from "../../http/index";
let enforcementAppBaseUrl = "/app-enf";
// 执法端用户登录
export const enforcementLogin = (params, success, error) => _post(enforcementAppBaseUrl + "/login/do", params, success, error);
// export const enforcementLogin = (params, success, error) => _post(enforcementAppBaseUrl + "/login/do", params, success, error);
export const enforcementLogin = (params, success, error) => _post(enforcementAppBaseUrl + "/login/szyptLogin", params, success, error);
// 扫描二维码后获取企业信息(qrContent 为前端解析二维码获取的原始文本)
export const getEntInfoByQRCode = (qrContent, success, error) => _get(enforcementAppBaseUrl + "/enterpriseInfo/getEntInfoByQRCode", { qrContent }, success, error);

532
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/components/ProgramCreate.vue

@ -0,0 +1,532 @@
<template>
<div class="warp_body">
<van-form @failed="onFailed" @submit="submit">
<van-cell-group class="warning-rule-show">
<van-popup v-model:show="data.showDatePicker" position="bottom">
<van-date-picker
title="选择日期"
v-model="data.currentDate"
:min-date="data.minDate"
:max-date="data.maxDate"
:columns-type="data.columnsType"
@confirm="onDateConfirm"
@cancel="cancelSelectDate"
/>
</van-popup>
<van-popup v-model:show="data.showMouthPicker" position="bottom">
<van-date-picker
title="选择月份"
v-model="data.currentMouth"
:min-date="data.minDate"
:max-date="data.maxDate"
:columns-type="data.mouthColumnsType"
@confirm="onMouthConfirm"
@cancel="cancelSelectDate"
/>
</van-popup>
<div class="one-label">
<van-field
label="计划名称"
v-model="submitData.planYear"
is-link
placeholder="请选择年份"
required
readonly
@click="data.showDatePicker = true"
/>
<div class="flex-div">
<van-field
label=" "
v-model="data.fieldValue"
placeholder="请选择年/季/月度"
is-link
@click="data.dateShowPicker = true"
required
readonly
/>
<div class="right-txt" v-if="data.showMouth">
<van-field
v-model="data.mouthText"
placeholder="请选择月度"
is-link
@click="data.showMouthPicker = true"
required
readonly
/>
</div>
<div class="right-txt" v-if="data.showQuarter">
<van-field
v-model="data.quarterText"
placeholder="请选择季度"
is-link
@click="data.dataQuarterShowPicker = true"
readonly
required
/>
</div>
</div>
<van-field
label=" "
v-model="submitData.planName"
placeholder="行政执法计划"
/>
</div>
<div class="warning-rule warning-rule-show">
<span><van-icon name="warning-o" /></span><span>{{ nameObj.bureauDept + nameObj.planYear + nameObj.quarter + nameObj.mouth + nameObj.planName }}</span>
</div>
<van-popup v-model:show="data.dateShowPicker" round position="bottom">
<van-picker
:columns="data.dateColumns"
@cancel="data.dateShowPicker = false"
@confirm="dateOnConfirm"
/>
</van-popup>
<!-- 季度选择 -->
<van-popup v-model:show="data.dataQuarterShowPicker" round position="bottom">
<van-picker
:columns="data.quarterColumns"
@cancel="data.dataQuarterShowPicker = false"
@confirm="dateQuarterOnConfirm"
/>
</van-popup>
<van-field
v-model="submitData.bureauDeptName"
label="执法单位"
placeholder="请选择"
@click=""
required
readonly
:rules="[{ required: true, message: '请选择执法部门'}]"
/>
<van-field
v-model="submitData.enforcementTime"
label="上传时间"
placeholder="请选择"
clickable
readonly
required
:rules="[{ required: true, message: '请选择上传时间'}]"
/>
<van-popup v-model:show="data.showPicker" position="bottom">
<van-date-picker
title="选择日期"
v-model="data.currentDate1"
:min-date="data.minDate"
:max-date="data.maxDate"
:columns-type="data.columnsTypes"
@confirm="onEnforcementTimeConfirm"
@cancel="cancelEnforcementTime"
/>
</van-popup>
<van-field
v-model="submitData.enforcementArea"
label="执法区域"
placeholder="请输入"
required
:rules="[{ required: true, message: '请输入执法区域'}]"
/>
<div class="file-upload">
<div class="file-upload-center">
<label>&nbsp; &nbsp; &nbsp; &nbsp;</label> <span>(仅限PDF文档5MB以下)</span>
<span class="down-mod"><van-icon name="upgrade" /><a @click="downloadFile">模板下载</a></span>
</div>
<van-uploader v-model="data.fileListObj" max-count="1" :max-size="5 * 1024 * 1024" class="file" :after-read="uploadFiles"
multiple @oversize="onOversize" :before-delete="deleteFile" accept=".pdf" :before-read="beforeRead">
<template #preview-cover="{ file }">
<div class="preview-cover van-ellipsis">{{ file.name }}</div>
</template>
</van-uploader>
</div>
<div class="warning-rule">
<span><van-icon name="warning-o" /></span>仅支持上传5MB以下的PDF格式文档
</div>
</van-cell-group>
<van-button round block type="primary" native-type="submit">发布</van-button>
</van-form>
<div class="van-overlay" v-show="data.showIsPublish">
<div class="popcont">
<div class="poptip">是否公开该行政执法计划</div>
<van-cell center>
<van-radio-group v-model="submitData.isPublish" shape="square" direction="horizontal">
<van-radio name="1">公开</van-radio>
<van-radio name="0">不公开</van-radio>
</van-radio-group>
</van-cell>
<div class="popup-btn">
<a @click="cancelPlan()">取消</a><a @click="addPlan()">确定</a>
</div>
</div>
</div>
</div>
</template>
<script setup name="ProgramCreate">
import {showToast, showSuccessToast} from 'vant';
import {reactive, ref, onMounted} from "vue";
const nameObj = reactive({
planName:"",
mouth:"",
quarter:"",
bureauDept:"",
planYear:""
})
const data = reactive({
searchResult: [],
showPicker: false,
columns: [],
selectTypeValue: ref[0],
showDatePicker: ref(false),
columnsType: ['year'],
columnsTypes: ['year', 'month', 'day'],
currentDate: ref(['2026']),
minDate: new Date(2020, 0, 1),
maxDate: new Date(2125, 5, 1),
currentDate1: [],
fileListObj: [],
dateShowPicker: false,
fieldValue: "",
dateColumns: [
{ text: '年度', value: 'year' },
{ text: '季度', value: 'quarter' },
{ text: '月度', value: 'month' },
],
quarterColumns: [
{ text: '第一季度', value: '1' },
{ text: '第二季度', value: '2' },
{ text: '第三季度', value: '3' },
{ text: '第四季度', value: '4' },
],
showIsPublish:false,
showMouth:false,
showQuarter: false,
showMouthPicker:false,
mouthColumnsType: ['month'],
currentMouth: ref([]),
dataQuarterShowPicker: false,
quarterText: "",
mouthText:"",
planNameText:"",
showPeriodPicker: false,
});
const submitData = reactive({
userId: "",
relId: "",
planYear: "",
planName: "",
enforcementDepartment: "深圳市司法局",
enforcementDepartmentId: "123",
enforcementTime: 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: "",
isPublish: '1',
fileList: [],
bureauDeptId:"",
bureauDeptName:"深圳市司法局",
timeType: 0,
mouth: 0,
quarter:0,
})
const submit = () => {
data.showIsPublish = true;
}
const addPlan = (val) =>{
data.showIsPublish = false;
showSuccessToast('发布成功');
}
const cancelPlan = (val) =>{
data.showIsPublish = false;
}
const currentDate =() => {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
data.currentDate1 = [year, month, day]
}
const onFailed = (errorInfo) => {
console.log('failed', errorInfo);
};
const downloadFile = () => {
showToast('模板下载功能开发中');
}
const uploadFiles = (file) => {
file.status = 'uploading';
file.message = '上传中...';
setTimeout(() => {
file.status = 'done';
file.message = '';
}, 1000);
}
const deleteFile = (file, index) => {
submitData.fileList.filter((item, indexs) => {
if (index.index == indexs) {
submitData.fileList.splice(indexs, 1);
}
});
return true;
}
const beforeRead = (file) => {
if (file.type !== 'application/pdf') {
showToast('仅支持上传PDF格式文档');
return false;
}
return true;
}
const onOversize = () => {
showToast('文件大小不能超过 5MB');
};
onMounted(() => {
submitData.planName = "行政执法计划";
nameObj.planName = '行政执法计划';
nameObj.bureauDept = '深圳市司法局';
currentDate();
});
const selectDate = () => {
data.showDatePicker = true;
}
const onDateConfirm = (value) => {
value.selectedValues.forEach((item, index) => {
submitData.planYear = item;
nameObj.planYear = item + "年"
});
data.showDatePicker = false;
}
const onMouthConfirm = (value) => {
value.selectedValues.forEach((item, index) => {
data.mouthText = item;
nameObj.mouth = item + "月";
submitData.mouth = item;
});
data.showMouthPicker = false;
data.quarterText = "";
}
const cancelSelectDate = () => {
data.showDatePicker = false;
data.showMouthPicker = false;
}
const cancelEnforcementTime = () => {
data.showPicker = false;
}
const onEnforcementTimeConfirm = (value) => {
submitData.enforcementTime = '';
value.selectedValues.forEach((item, index) => {
submitData.enforcementTime += item + (index < value.selectedValues.length - 1 ? '-' : '');
});
data.showPicker = false;
}
const dateOnConfirm = ({ selectedOptions }) => {
const value = selectedOptions[0];
data.fieldValue = value.text;
data.dateShowPicker = false;
data.showMouth = false;
data.showQuarter = false;
data.mouthText = "";
data.quarterText = "";
nameObj.quarter = "";
nameObj.mouth = "";
submitData.mouth = 0;
submitData.quarter = 0;
submitData.timeType = data.dateColumns.indexOf(value) + 1;
if (value.value == 'quarter') {
data.showQuarter = true;
} else if (value.value == 'month') {
data.showMouth = true;
}
}
const dateQuarterOnConfirm = ({ selectedOptions }) => {
const value = selectedOptions[0];
data.quarterText = value.text;
nameObj.quarter = value.text;
submitData.quarter = value.value;
data.dataQuarterShowPicker = false;
}
</script>
<style lang="less" scoped>
.warp_body {
flex: 1;
overflow-y: auto;
margin: 10px;
}
.file-upload {
padding: 16px;
background: #fff;
.file-upload-center {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: #323233;
position: relative;
label {
font-weight: 500;
margin-right: 8px;
}
span {
color: #969799;
font-size: 12px;
}
.down-mod {
position: absolute;
right: 0;
color: #faad14;
font-size: 12px;
}
}
.file {
.van-uploader__upload {
width: 100%;
height: 180px;
border: 1px dashed #dcdee0;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #fafafa;
}
}
.preview-cover {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 8px;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.warning-rule {
margin: 12px 16px;
background: #fff7e6;
color: #faad14;
font-size: 12px;
margin-bottom: 16px;
display: flex;
align-items: center;
padding: 6px;
border-radius: 4px;
span {
margin-right: 4px;
}
}
.warning-rule-show{
padding-bottom: 6px;
margin-bottom: 16px;
}
.one-label {
margin-bottom: 8px;
}
.flex-div {
display: flex;
align-items: center;
.right-txt {
flex: 1;
}
:deep(.van-field) {
flex: 1;
}
}
.van-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
.popcont {
width: 85%;
background: #fff;
border-radius: 12px;
padding: 24px;
.poptip {
text-align: center;
font-size: 16px;
color: #323233;
margin-bottom: 20px;
}
.popup-btn {
display: flex;
margin-top: 24px;
a {
flex: 1;
text-align: center;
padding: 12px;
border-radius: 8px;
font-size: 14px;
&:first-child {
background: #f5f5f5;
color: #666;
margin-right: 12px;
}
&:last-child {
background: #faad14;
color: #fff;
}
}
}
}
}
</style>

373
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/components/ProgramQuery.vue

@ -0,0 +1,373 @@
<template>
<div class="query-container">
<!-- 搜索栏 -->
<van-search
v-model="querySearchText"
show-action
placeholder="请输入计划名称/执法单位/区域查询"
clearable="true"
clear-icon="clear"
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="search-button-date">
<span class="date-select" v-show="queryDateShow" @click="openQueryDatePicker">{{ queryDateSearch }}</span>
<div class="van-hairline--bottom"></div>
</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">
<van-empty v-if="queryFinished && queryList.length === 0" description="暂无数据" />
<div class="plan-card" v-for="item in queryList" :key="item.id">
<van-swipe-cell class="swipe_cell">
<div class="card_box">
<div class="card_box_span">
<p>发布时间{{ item.publishTime }}</p>
</div>
<div class="van-hairline--bottom"></div>
<div class="card_box_span card-title">
<p>{{ item.title }}</p>
</div>
<div class="card_box_span">
<p><label class="card_box_label">执法单位</label> <span>{{ item.enforcementUnit }}</span></p>
</div>
<div class="card_box_span">
<p><label class="card_box_label">执法区域</label> <span>{{ item.area }}</span></p>
</div>
<div class="card_box_span">
<p><label class="card_box_label">&nbsp; &nbsp; &nbsp; &nbsp;</label> <a style="color: #2b75fd;">{{ item.fileName }}</a></p>
</div>
<div class="btn-list">
<a v-if="item.isPublish == 0" @click.stop="updatePublish(item.id, 1)"><van-icon name="passed" />公开</a>
<a v-if="item.isPublish == 1" @click.stop="updatePublish(item.id, 0)"><van-icon name="close" />取消公开</a>
</div>
</div>
</van-swipe-cell>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 日期筛选弹窗 -->
<van-popup v-model:show="queryShowDatePicker" position="bottom">
<van-date-picker title="选择月度" v-model="queryCurrentDate" :columns-type="['year', 'month']"
@confirm="onQueryDateConfirm" @cancel="queryShowDatePicker = false" />
</van-popup>
</div>
</template>
<script setup name="ProgramQuery">
import {showToast, showSuccessToast, showFailToast} from 'vant';
import {ref, onMounted} from "vue";
const querySearchText = ref('');
const queryDateType = ref(null);
const queryRefreshing = ref(false);
const queryLoading = ref(false);
const queryFinished = ref(false);
const queryList = ref([]);
const queryShowDatePicker = ref(false);
const queryCurrentDate = ref(['2026', '05']);
const queryDateShow = ref(false);
const queryDateSearch = ref('');
const fetchQueryList = () => {
queryLoading.value = true;
setTimeout(() => {
const mockData = [
{
id: 1,
title: '2026年行政执法计划',
publishTime: '2026-05-20 10:30:00',
enforcementUnit: '深圳市司法局',
area: '深圳市',
fileName: '2026年度执法计划.pdf',
isPublish: 1
},
{
id: 2,
title: '2026年第一季度执法计划',
publishTime: '2026-03-01 09:00:00',
enforcementUnit: '深圳市司法局',
area: '南山区',
fileName: '2026Q1执法计划.pdf',
isPublish: 0
},
{
id: 3,
title: '2026年5月执法计划',
publishTime: '2026-05-01 14:00:00',
enforcementUnit: '深圳市司法局',
area: '福田区',
fileName: '202605执法计划.pdf',
isPublish: 1
}
];
queryList.value = [...queryList.value, ...mockData];
queryLoading.value = false;
queryFinished.value = true;
}, 1000);
}
const onQueryRefresh = () => {
queryRefreshing.value = true;
queryList.value = [];
queryFinished.value = false;
fetchQueryList();
setTimeout(() => {
queryRefreshing.value = false;
}, 1000);
}
const onQueryLoad = () => {
if (!queryFinished.value) {
fetchQueryList();
}
}
const onQuerySearch = () => {
queryList.value = [];
queryFinished.value = false;
fetchQueryList();
}
const onQueryClear = () => {
queryDateType.value = null;
queryDateShow.value = false;
queryDateSearch.value = '';
queryList.value = [];
queryFinished.value = false;
fetchQueryList();
}
const querySearchDateType = (type) => {
queryDateType.value = type;
queryDateShow.value = false;
queryDateSearch.value = '';
queryList.value = [];
queryFinished.value = false;
fetchQueryList();
}
const openQueryDatePicker = () => {
queryDateType.value = 9;
queryShowDatePicker.value = true;
}
const onQueryDateConfirm = (value) => {
queryDateSearch.value = value.selectedValues[0] + "/" + value.selectedValues[1];
queryDateShow.value = true;
queryShowDatePicker.value = false;
queryDateType.value = 9;
queryList.value = [];
queryFinished.value = false;
fetchQueryList();
}
const updatePublish = (id, isPublish) => {
if (isPublish === 1) {
showSuccessToast('已公开');
} else {
showSuccessToast('已取消公开');
}
const item = queryList.value.find(item => item.id === id);
if (item) {
item.isPublish = isPublish;
}
}
onMounted(() => {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
queryDateSearch.value = `${year}/${month}`;
fetchQueryList();
});
</script>
<style lang="less" scoped>
.query-container {
height: 100%;
display: flex;
flex-direction: column;
background-color: #ebf3fb;
.search-button-list {
height: 0.7rem;
display: flex;
flex-direction: row;
padding-left: 0.27rem;
margin-top: 0.27rem;
align-items: end;
.search-btn {
width: 15%;
height: 90%;
background: #ebebeb;
border-color: #ebebeb;
color: #999;
font-size: 0.3rem;
line-height: 1.5;
}
.search-btn.active {
background-color: #d9e6fd;
border-color: #75a5fb;
color: #1367fe;
}
}
.search-button-date {
display: flex;
flex-direction: row;
align-items: end;
position: relative;
.date-select {
margin: 3% 0 0 4%;
color: #c1bfbf;
font-size: 0.52rem;
}
}
.plan-list {
flex: 1;
overflow-y: auto;
position: relative;
padding-bottom: 60px;
.plan-card {
display: flex;
background-color: #fff;
margin: 0.27rem;
border-radius: 0.27rem;
width: calc(100% - 0.54rem);
.swipe_cell {
width: 100%;
}
.card_box {
display: flex;
width: 100%;
padding: 0.27rem 0;
flex-direction: column;
.card_box_span {
display: flex;
align-items: center;
margin: 2% 0;
padding: 0 3%;
color: #666;
font-size: 0.35rem;
line-height: 1.5;
p {
display: flex;
margin: 0;
}
}
.card_box_action {
position: absolute;
right: 4%;
}
}
}
}
}
/deep/ .van-search {
background: #fff;
padding: 12px 16px;
margin-bottom: -1px;
}
/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: 1px solid #1367fe;
border-radius: 0 0.15rem 0.15rem 0;
color: #fff;
text-align: center;
line-height: 0.9rem;
}
/deep/ .van-hairline--bottom {
margin: -1% 0 3% 0;
height: 0;
background-color: transparent;
border: 1px;
}
/deep/ .card_box_span:first-child p {
color: #c1bfbf !important;
}
.card-title p {
display: block !important;
color: #000;
font-size: 0.40rem;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card_box_label {
color: #323233;
white-space: nowrap;
}
.btn-list {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 0.3rem 3% 0;
width: 94%;
border-top: 1px solid #f4f5f6;
}
.btn-list a {
margin: 3% 3% 1%;
font-size: 0.35rem;
color: #1367fe;
white-space: nowrap;
}
.btn-list a i {
margin: 0 0.12rem;
font-size: 0.37rem;
}
</style>

697
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/program/index.vue

@ -13,441 +13,73 @@
<!-- 内容区域 -->
<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>
&nbsp;&nbsp;&nbsp;
<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>
<!-- 发布按钮 -->
<ProgramCreate v-if="activeTab === 'create'" />
<!-- 查询列表 -->
<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">&nbsp;&nbsp;&nbsp;</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>
<ProgramQuery v-else />
</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 v-model:show="data.showDatePicker" position="bottom" round>
<van-picker :columns="yearColumns" title="选择年份" @confirm="onYearConfirm" @cancel="data.showDatePicker = false" />
</van-popup>
<!-- 周期选择器 -->
<van-popup v-model:show="showPeriodPicker" position="bottom" round>
<van-popup v-model:show="data.showPeriodPicker" position="bottom" round>
<van-picker :columns="periodColumns" title="选择周期" @confirm="onPeriodConfirm"
@cancel="showPeriodPicker = false" />
@cancel="data.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);
import {useRouter, useRoute} from 'vue-router';
import {showToast} from 'vant';
import FooterBar from '@/components/FooterBar.vue';
import ProgramCreate from './components/ProgramCreate.vue';
import ProgramQuery from './components/ProgramQuery.vue';
import {reactive, ref} from "vue";
const yearColumns = [
{ text: '2026年', value: '2026' },
{ text: '2027年', value: '2027' },
{ text: '2028年', value: '2028' }
];
const router = useRouter();
const route = useRoute();
const periodColumns = [
{ text: '年度', value: 'year' },
{ text: '第一季度', value: 'Q1' },
{ text: '第二季度', value: 'Q2' },
{ text: '第三季度', value: 'Q3' },
{ text: '第四季度', value: 'Q4' }
];
const activeTab = ref('create');
const onYearConfirm = ({ selectedOptions }) => {
form.planName = selectedOptions[0]?.text || '';
showYearPicker.value = false;
};
const data = reactive({
showDatePicker: false,
showPeriodPicker: false
});
const onPeriodConfirm = ({ selectedOptions }) => {
form.planPeriod = selectedOptions[0]?.text || '';
showPeriodPicker.value = false;
};
const yearColumns = [
{ text: '2024年', value: '2024' },
{ text: '2025年', value: '2025' },
{ text: '2026年', value: '2026' },
{ text: '2027年', value: '2027' },
];
const afterRead = (file) => {
file.status = 'uploading';
file.message = '上传中...';
const periodColumns = [
{ text: '年度', value: 'year' },
{ text: '季度', value: 'quarter' },
{ text: '月度', value: 'month' },
];
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;
const onYearConfirm = (value) => {
data.showDatePicker = false;
}
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 onPeriodConfirm = (value) => {
data.showPeriodPicker = false;
}
};
//
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;
height: 100vh;
background: #f5f7fa;
}
@ -477,8 +109,8 @@ onMounted(() => {
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 160px;
height: 1px;
width: 40px;
height: 3px;
background: #1989fa;
border-radius: 2px;
}
@ -487,260 +119,7 @@ onMounted(() => {
}
.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>
</style>

6
zdxt-web-client/zdxt-efcode-enforcement-app/vite-config/index.js

@ -43,9 +43,13 @@ const baseCfg = (viteEnv = {}) => {
cors: true,
proxy: {
'/api': {
target: 'http://localhost:17000',
target: 'http://139.186.148.21:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/api-enf': {
target: 'http://139.186.148.21:8080',
changeOrigin: true
}
}
},

2
zdxt-web-client/zdxt-efcode-enforcement-app/vite-env/.env.enforcement-dev

@ -1,5 +1,5 @@
# 执法端开发环境
VITE_PORT = 12002
VITE_BASE_URL = 'http://localhost:17000/enf-ep'
VITE_BASE_URL = '/'
VITE_DEFAULT_LOGIN = '/enforcement/login'
VITE_APP_ENV = 'dev'

Loading…
Cancel
Save