Browse Source

feat: 增加巡查

dev
上官小雨 5 months ago
parent
commit
2b6928a04b
  1. 1
      zdxt-web-client/zdxt-admin-nos-plus-ui/.prettierrc
  2. 20
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/.prettierrc
  3. 5
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/api/nos/enfDemo.js
  4. 2
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/FooterBar.vue
  5. 50
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/AttachmentTaskSource.vue
  6. 54
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/EnforcementInfoSection.vue
  7. 9
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/router/nos/nos-base-router.js
  8. 26
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailMaterialsTab.vue
  9. 21
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailTaskTab.vue
  10. 534
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosAdd.vue
  11. 15
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosDetail.vue
  12. 44
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosEvaluateList.vue
  13. 14
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosList.vue
  14. 2
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/vite-env/.env.enforcement-dev
  15. 11
      zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/components/NosDetailTaskTab.vue
  16. 10
      zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/nosDetail.vue
  17. 1
      zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/nosList.vue
  18. 2
      zdxt-web-client/zdxt-efcode-nos-enterprise-app/vite-env/.env.enterprise-dev

1
zdxt-web-client/zdxt-admin-nos-plus-ui/.prettierrc

@ -16,5 +16,6 @@
"proseWrap": "preserve", "proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css", "htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"singleAttributePerLine": false,
"endOfLine": "auto" "endOfLine": "auto"
} }

20
zdxt-web-client/zdxt-efcode-nos-enforcement-app/.prettierrc

@ -0,0 +1,20 @@
{
"printWidth": 150,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"jsxSingleQuote": false,
"bracketSameLine": false,
"trailingComma": "none",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto"
}

5
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/api/nos/enfDemo.js

@ -6,7 +6,7 @@ const baseUrl = "/app-enf";
export const listEnfDemo = (params) => export const listEnfDemo = (params) =>
_get("/enforcementcode/enfDemo/list", params); _get("/enforcementcode/enfDemo/list", params);
// 非现场api // 非现场api
// 新增非现场执法任务 // 新增非现场执法任务
export const nosAddApi = (params, success, error) => _post("/app-enf/nos-law-enforcement-task/add", params, success, error); export const nosAddApi = (params, success, error) => _post("/app-enf/nos-law-enforcement-task/add", params, success, error);
// 分页查询非现场执法任务 // 分页查询非现场执法任务
@ -19,3 +19,6 @@ export const nosApproveApi = (params, success, error) => _post("/app-enf/nos-law
export const nosTypeApi = (params, success, error) => _post("/app-enf/nos-law-enforcement-task/type", params, success, error); export const nosTypeApi = (params, success, error) => _post("/app-enf/nos-law-enforcement-task/type", params, success, error);
//查询非现场执法项目任务来源列表 //查询非现场执法项目任务来源列表
export const nosTaskSourceListApi = (params, success, error) => _post("/app-enf/task-source/list", params, success, error); export const nosTaskSourceListApi = (params, success, error) => _post("/app-enf/task-source/list", params, success, error);
//查询已评价的任务
export const nosTaskHasCommentPageApi = (params, success, error) => _post("/app-enf/nos-law-enforcement-task/has-comment/page", params, success, error);

2
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/FooterBar.vue

@ -79,7 +79,7 @@ const tabs = [
{ {
key: "message", key: "message",
label: "消息", label: "消息",
route: "/enforcement/message", route: "/enforcement/nos/evaluate",
path: '<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>' path: '<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>'
}, },
{ {

50
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/AttachmentTaskSource.vue

@ -12,6 +12,7 @@
<!-- 任务来源 --> <!-- 任务来源 -->
<van-field <van-field
:class="formData.taskTypeId == 5 ? 'usci-code-field' : ''"
:model-value="taskSourceName" :model-value="taskSourceName"
is-link is-link
readonly readonly
@ -20,15 +21,16 @@
placeholder="请选择" placeholder="请选择"
@click="$emit('showTaskSourcePicker')" @click="$emit('showTaskSourcePicker')"
clearable clearable
:rules="[{ required: true, message: '请选择任务来源' }]" :rules="[{ required: formData.taskTypeId != 5, message: '请选择任务来源' }]"
required /> :required="formData.taskTypeId != 5"
/>
</div> </div>
</template> </template>
<script setup name="AttachmentTaskSource"> <script setup name="AttachmentTaskSource">
import { computed } from "vue" import { computed } from 'vue';
import { uploadFile } from "@/util/fileUpload" import { uploadFile } from '@/util/fileUpload';
import { toFullFileUrl } from "@/util/fileUrl" import { toFullFileUrl } from '@/util/fileUrl';
const props = defineProps({ const props = defineProps({
formData: { formData: {
@ -39,29 +41,29 @@ const props = defineProps({
type: Array, type: Array,
default: () => [] default: () => []
} }
}) });
const taskSourceName = computed(() => { const taskSourceName = computed(() => {
const option = props.taskSourceOptions.find((item) => item.value === props.formData.taskSourceId) const option = props.taskSourceOptions.find((item) => item.value === props.formData.taskSourceId);
return option?.text || "" return option?.text || '';
}) });
const emit = defineEmits(["showTaskSourcePicker", "open-picker"]) const emit = defineEmits(['showTaskSourcePicker', 'open-picker']);
// 附件上传处理 // 附件上传处理
const onAfterReadImage = (file) => { const onAfterReadImage = (file) => {
const files = Array.isArray(file) ? file : [file] const files = Array.isArray(file) ? file : [file];
files.forEach((f) => { files.forEach((f) => {
uploadFile(f, (res) => { uploadFile(f, (res) => {
const data = res && res.data ? res.data : res const data = res && res.data ? res.data : res;
const filePath = typeof data === "string" ? data : (data && (data.url || data.fileUrl || data.path)) || "" const filePath = typeof data === 'string' ? data : (data && (data.url || data.fileUrl || data.path)) || '';
if (filePath) { if (filePath) {
f.url = toFullFileUrl(filePath) f.url = toFullFileUrl(filePath);
f.status = "done" f.status = 'done';
} }
}) });
}) });
} };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -107,20 +109,24 @@ const onAfterReadImage = (file) => {
border-radius: 0.1rem; border-radius: 0.1rem;
} }
} }
:deep(.van-cell){ :deep(.van-cell) {
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
} }
.label-required-placeholder .form-label { .label-required-placeholder .form-label {
position: relative; position: relative;
padding-left: 0.1rem; padding-left: 0.1rem;
&::before { &::before {
content: "*"; content: '*';
color: transparent; color: transparent;
font-size: 0.32rem; font-size: 0.32rem;
margin-right: 0.08rem; margin-right: 0.08rem;
font-weight: bold; font-weight: bold;
} }
} }
:deep(.usci-code-field .van-field__label) {
padding-left: 8px; // 按需调整
box-sizing: border-box;
}
</style> </style>

54
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/EnforcementInfoSection.vue

@ -1,19 +1,20 @@
<template> <template>
<div class="section"> <div class="section">
<div class="section-title">执法信息</div> <div class="section-title">{{ formData.taskTypeId == 1 ? '执法信息' : '巡查信息' }}</div>
<!-- 执法单位 --> <!-- 执法单位 -->
<van-field <van-field
v-model="formData.lawEnforcementAgency" v-model="formData.lawEnforcementAgency"
name="lawEnforcementAgency" name="lawEnforcementAgency"
label="执法单位" :label="formData.taskTypeId == 1 ?'执法单位' : '巡查单位'"
placeholder="请输入执法单位" :placeholder="formData.taskTypeId == 1 ? '请输入执法单位' : '请输入巡查单位'"
:rules="[{ required: true, message: '请填写执法单位' }]" :class="formData.taskTypeId == 5 ? 'usci-code-field' : ''"
required /> :required="formData.taskTypeId != 5"
disabled />
<!-- 执法人员 --> <!-- 执法人员 -->
<div class="form-row required"> <div class="form-row required">
<div class="form-label"><span class="required-mark">*</span>执法人员</div> <div class="form-label"><span class="required-mark" :class="{ 'required-mark--placeholder': formData.taskTypeId == 5 }">*</span>{{ formData.taskTypeId == 1 ? '执法人员' : '参与人员' }}</div>
<div class="form-value person-select"> <div class="form-value person-select">
<div class="person-item default-person"> <div class="person-item default-person">
<span>{{ defaultPerson?.lawEnforcerName }} {{ defaultPerson?.lawEnforcerId }}</span> <span>{{ defaultPerson?.lawEnforcerName }} {{ defaultPerson?.lawEnforcerId }}</span>
@ -31,7 +32,12 @@
</div> </div>
</div> </div>
<!-- 隐藏字段用于验证执法人员 --> <!-- 隐藏字段用于验证执法人员 -->
<van-field v-model="hasPerson" name="enforcementPerson" :rules="[{ required: true, message: '请选择执法人员' }]" style="display: none" /> <van-field
v-if="formData.taskTypeId != 5"
v-model="hasPerson"
name="enforcementPerson"
:rules="[{ required: true, message: '请选择执法人员' }]"
style="display: none" />
<!-- 执法类别 --> <!-- 执法类别 -->
<van-field <van-field
@ -40,22 +46,22 @@
readonly readonly
:disabled="categoryDisabled" :disabled="categoryDisabled"
name="taskTypeId" name="taskTypeId"
class="usci-code-field"
label="类  别" label="类  别"
placeholder="请选择" placeholder="请选择"
@click="onCategoryClick" @click="onCategoryClick"
:clearable="!categoryDisabled" :clearable="!categoryDisabled"
:rules="[{ required: true, message: '请选择类别' }]" />
required />
<!-- 执法事项 --> <!-- 执法事项 -->
<van-field <van-field
v-model="formData.lawEnforcementMatters" v-model="formData.lawEnforcementMatters"
name="lawEnforcementMatters" name="lawEnforcementMatters"
label="执法事项" v-if="formData.taskTypeId == 1 || formData.taskTypeId == 5"
placeholder="请输入执法事项" :label="formData.taskTypeId == 1 ? '执法事项' : '巡查事项'"
:placeholder="formData.taskTypeId == 1 ? '请输入执法事项' : '请输入巡查事项'"
type="textarea" type="textarea"
:rules="[{ required: true, message: '请填写执法事项' }]" class="usci-code-field"
required
rows="2" rows="2"
autosize autosize
maxlength="1000" maxlength="1000"
@ -64,7 +70,7 @@
<!-- 检查要求 / 备案文件 / 验证文件 --> <!-- 检查要求 / 备案文件 / 验证文件 -->
<template v-if="requirementLabel"> <template v-if="requirementLabel">
<div class="form-row required material-header-row"> <div class="form-row required material-header-row">
<div class="form-label"><span class="required-mark">*</span>{{ requirementLabel }}</div> <div class="form-label"><span class="required-mark" :class="{ 'required-mark--placeholder': formData.taskTypeId == 5 }">*</span>{{ requirementLabel }}</div>
<div class="form-value"> <div class="form-value">
<span class="material-hint">指定企业要上传的材料名称(例如:营业执照)</span> <span class="material-hint">指定企业要上传的材料名称(例如:营业执照)</span>
</div> </div>
@ -81,7 +87,7 @@
</div> </div>
</template> </template>
<van-field <van-field
v-if="requirementLabel" v-if="requirementLabel && formData.taskTypeId != 5"
v-model="hasMaterialRequirement" v-model="hasMaterialRequirement"
name="checkItemList" name="checkItemList"
:rules="[{ required: true, message: `请填写${requirementLabel}` }]" :rules="[{ required: true, message: `请填写${requirementLabel}` }]"
@ -89,7 +95,7 @@
<!-- 执法依据 --> <!-- 执法依据 -->
<div class="form-row required"> <div class="form-row required">
<div class="form-label"><span class="required-mark">*</span>执法依据</div> <div class="form-label"><span class="required-mark" :class="{ 'required-mark--placeholder': formData.taskTypeId == 5 }">*</span>执法依据</div>
<div class="form-value basis-select"> <div class="form-value basis-select">
<div v-for="(basis, index) in formData.legalBasis" :key="index" class="basis-item-add"> <div v-for="(basis, index) in formData.legalBasis" :key="index" class="basis-item-add">
<span @click="$emit('openBasisPicker', index)" :class="{ placeholder: !basis }"> <span @click="$emit('openBasisPicker', index)" :class="{ placeholder: !basis }">
@ -109,13 +115,14 @@
<van-field <van-field
v-model="formData.deadline" v-model="formData.deadline"
name="deadline" name="deadline"
:class="formData.taskTypeId == 5 ? 'usci-code-field' : ''"
label="截止时间" label="截止时间"
placeholder="请选择截止时间" placeholder="请选择截止时间"
is-link is-link
readonly readonly
:rules="[{ required: true, message: '请选择截止时间' }]" :rules="[{ required: formData.taskTypeId != 5, message: '请选择截止时间' }]"
@click="$emit('showTimePicker')" @click="$emit('showTimePicker')"
required /> :required="formData.taskTypeId != 5" />
<!-- 联合执法 --> <!-- 联合执法 -->
</div> </div>
@ -156,7 +163,8 @@ const onCategoryClick = () => {
const CATEGORY_REQUIREMENT_LABEL_MAP = { const CATEGORY_REQUIREMENT_LABEL_MAP = {
行政检查: "检查要求", 行政检查: "检查要求",
材料备案: "备案文件", 材料备案: "备案文件",
材料验证: "验证文件" 材料验证: "验证文件",
巡查: "巡查要求"
} }
// 根据类别动态显示 label(独立 category prop,避免 keep-alive 下嵌套对象追踪失效) // 根据类别动态显示 label(独立 category prop,避免 keep-alive 下嵌套对象追踪失效)
@ -240,6 +248,10 @@ const removeMaterialRow = (index) => {
font-size: 0.32rem; font-size: 0.32rem;
margin-right: 0.08rem; margin-right: 0.08rem;
font-weight: bold; font-weight: bold;
&--placeholder {
visibility: hidden;
}
} }
@ -473,4 +485,8 @@ const removeMaterialRow = (index) => {
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
} }
:deep(.usci-code-field .van-field__label) {
padding-left: 8px; // 按需调整
box-sizing: border-box;
}
</style> </style>

9
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/router/nos/nos-base-router.js

@ -27,6 +27,15 @@ const routes = [
isKeepAlive: false, isKeepAlive: false,
}, },
}, },
// 非现场消息
{
path: "/enforcement/nos/evaluate",
name: "nosEvaluate",
component: () => import("@/views/enforcement/nos/nosEvaluateList.vue"),
meta: {
isKeepAlive: false,
},
},
]; ];
export { routes }; export { routes };

26
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailMaterialsTab.vue

@ -7,6 +7,18 @@
<div class="empty-text-sub">等待企业上传材料</div> <div class="empty-text-sub">等待企业上传材料</div>
</div> </div>
</template> </template>
<template v-else-if="Number(taskStatus) === 5">
<div class="empty-state">
<van-image :src="noDataImg" width="151" height="120" />
<div class="empty-text-main">暂无资料上传</div>
<div class="empty-text-sub"></div>
</div>
<div class="result-card">
<div class="result-title">检查结果</div>
<div class="readonly-result-text">检查结果:已办结(<span class="overdue-text">超期</span>)</div>
<div class="readonly-result-text" v-if="overdueCheckTime">检查时间:{{ overdueCheckTime }}</div>
</div>
</template>
<template v-else> <template v-else>
<div <div
v-for="(item, index) in taskItemList" v-for="(item, index) in taskItemList"
@ -218,12 +230,18 @@ const props = defineProps({
type: [Number, String], type: [Number, String],
default: '' default: ''
}, },
taskDeadline: {
type: String,
default: '',
},
evaluateData: { evaluateData: {
type: Object, type: Object,
default: null, default: null,
} }
}); });
const overdueCheckTime = computed(() => props.taskDeadline || '');
const evaluateForm = ref({ const evaluateForm = ref({
satisfactionLevel: 3, // 3: 满意, 2: 一般, 1: 不满意 satisfactionLevel: 3, // 3: 满意, 2: 一般, 1: 不满意
commentReason: "", commentReason: "",
@ -333,6 +351,14 @@ const onPreview = (url, itemValue) => {
font-size: 14px; font-size: 14px;
color: #969799; color: #969799;
} }
.empty-text-overdue {
color: #ee0a24;
}
}
.overdue-text {
color: #ee0a24;
} }
.detail-materials-tab { .detail-materials-tab {

21
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailTaskTab.vue

@ -35,11 +35,11 @@
<div class="section"> <div class="section">
<div class="section-title">检查对象</div> <div class="section-title">检查对象</div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">执法单位</span> <span class="detail-label">{{ detail.taskTypeId != 5 ? '执法单位' : '巡查单位' }}</span>
<span class="detail-value">{{ detail.enforcementUnit }}</span> <span class="detail-value">{{ detail.enforcementUnit }}</span>
</div> </div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">执法人员</span> <span class="detail-label">{{ detail.taskTypeId != 5 ? '执法人员' : '参与人员' }}</span>
<div class="detail-value"> <div class="detail-value">
<p v-for="(person, index) in detail.enforcementPerson" :key="index" class="detail-text"> <p v-for="(person, index) in detail.enforcementPerson" :key="index" class="detail-text">
{{ person }} {{ person }}
@ -49,11 +49,13 @@
</div> </div>
<div class="section"> <div class="section">
<div class="section-title">执法信息</div> <div class="section-title">{{ detail.taskTypeId != 5 ? '执法信息' : '巡查信息' }}</div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">企业名称</span> <span class="detail-label">企业名称</span>
<div class="detail-value"> <div class="detail-value">
<p v-if="detail.enterpriseName" class="detail-text">{{ detail.enterpriseName }}</p> <p v-if="detail.enterpriseName" class="detail-text">
{{ detail.enterpriseName }}
</p>
<p v-if="detail.enterpriseNumber" class="detail-text">({{ detail.enterpriseNumber }})</p> <p v-if="detail.enterpriseNumber" class="detail-text">({{ detail.enterpriseNumber }})</p>
</div> </div>
</div> </div>
@ -61,8 +63,8 @@
<span class="detail-label">类  别</span> <span class="detail-label">类  别</span>
<span class="detail-value">{{ detail.category }}</span> <span class="detail-value">{{ detail.category }}</span>
</div> </div>
<div class="detail-row"> <div class="detail-row" v-if="detail.taskTypeId == 1 || detail.taskTypeId == 5">
<span class="detail-label">执法事项</span> <span class="detail-label">{{ detail.taskTypeId == 1 ? '执法事项' : '巡查事项' }}</span>
<span class="detail-value">{{ detail.enforcementMatter }}</span> <span class="detail-value">{{ detail.enforcementMatter }}</span>
</div> </div>
<div v-if="requirementLabel" class="detail-row"> <div v-if="requirementLabel" class="detail-row">
@ -100,14 +102,14 @@
const CATEGORY_REQUIREMENT_LABEL_MAP = { const CATEGORY_REQUIREMENT_LABEL_MAP = {
行政检查: '检查要求', 行政检查: '检查要求',
材料备案: '备案文件', 材料备案: '备案文件',
材料验证: '验证文件', 材料验证: '验证文件'
}; };
const props = defineProps({ const props = defineProps({
detail: { detail: {
type: Object, type: Object,
required: true, required: true
}, }
}); });
const emit = defineEmits(['download-notice']); const emit = defineEmits(['download-notice']);
@ -204,6 +206,7 @@ const onDownloadNotice = (url) => {
justify-content: space-between; justify-content: space-between;
gap: 8px; gap: 8px;
margin-bottom: 8px; margin-bottom: 8px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }

534
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosAdd.vue

@ -32,7 +32,8 @@
@open-basis-picker="handleOpenBasisPicker" @open-basis-picker="handleOpenBasisPicker"
@add-basis="handleAddBasis" @add-basis="handleAddBasis"
@remove-basis="handleRemoveBasis" @remove-basis="handleRemoveBasis"
@add-person="handleAddPerson" /> @add-person="handleAddPerson"
/>
<!-- 底部按钮 --> <!-- 底部按钮 -->
<div class="bottom-buttons"> <div class="bottom-buttons">
@ -53,49 +54,51 @@
@confirm-task-source="handleTaskSourceConfirm" @confirm-task-source="handleTaskSourceConfirm"
@select-person="handleSelectPerson" @select-person="handleSelectPerson"
@select-basis="handleSelectBasis" @select-basis="handleSelectBasis"
@select-time="handleSelectTime" /> @select-time="handleSelectTime"
/>
</div> </div>
</template> </template>
<script setup name="EnforcementRegister"> <script setup name="EnforcementRegister">
import { ref, reactive, computed, onMounted, onActivated, onDeactivated } from "vue" import { ref, reactive, computed, onMounted, onActivated, onDeactivated } from 'vue';
import { useRouter, useRoute } from "vue-router" import { useRouter, useRoute } from 'vue-router';
import { showConfirmDialog, showSuccessToast, showToast } from "vant" import { showConfirmDialog, showSuccessToast, showToast } from 'vant';
import EnterpriseInfoSection from "@/components/enforcement/nos/EnterpriseInfoSection.vue" import EnterpriseInfoSection from '@/components/enforcement/nos/EnterpriseInfoSection.vue';
import EnforcementInfoSection from "@/components/enforcement/nos/EnforcementInfoSection.vue" import EnforcementInfoSection from '@/components/enforcement/nos/EnforcementInfoSection.vue';
import RegisterPopups from "@/components/enforcement/nos/RegisterPopups.vue" import RegisterPopups from '@/components/enforcement/nos/RegisterPopups.vue';
import AttachmentTaskSource from "@/components/enforcement/nos/AttachmentTaskSource.vue" import AttachmentTaskSource from '@/components/enforcement/nos/AttachmentTaskSource.vue';
import { nosAddApi, nosTaskSourceListApi } from "@/api/nos/enfDemo" import { nosAddApi, nosTaskSourceListApi } from '@/api/nos/enfDemo';
import { toFullFileUrl, toRelativeFilePath } from "@/util/fileUrl" import { toFullFileUrl, toRelativeFilePath } from '@/util/fileUrl';
const router = useRouter() const router = useRouter();
const route = useRoute() const route = useRoute();
// ========== 状态定义 ========== // ========== 状态定义 ==========
// 补录模式(必填为true) // 补录模式(必填为true)
const isSignMode = ref(true) const isSignMode = ref(true);
/** 创建空的企业信息对象 */
const createDefaultEnterpriseData = () => ({ const createDefaultEnterpriseData = () => ({
enterpriseName: "", enterpriseName: '',
usciCode: "", usciCode: '',
enterpriseId: "" enterpriseId: ''
}) });
// 企业数据 // 企业数据
const enterpriseData = ref(createDefaultEnterpriseData()) const enterpriseData = ref(createDefaultEnterpriseData());
// 表单数据(字段名与 NosLawEnforcementTaskSaveBO 一致) // 表单数据(字段名与 NosLawEnforcementTaskSaveBO 一致)
const formData = reactive({ const formData = reactive({
lawEnforcementAgency: '', lawEnforcementAgency: $globalStore.useCommon.bureauUnitName,
taskTypeId: null, taskTypeId: null,
taskSourceId: "", taskSourceId: '',
lawEnforcementMatters: "", lawEnforcementMatters: '',
checkItemList: [""], checkItemList: [''],
legalBasis: [""], legalBasis: [''],
deadline: "", deadline: '',
proclamation: [] proclamation: []
}) });
// 执法人员 // 执法人员
const lawEnforcer = ref([ const lawEnforcer = ref([
@ -105,9 +108,9 @@ const lawEnforcer = ref([
lawEnforcerId: $globalStore.useCommon.userId, lawEnforcerId: $globalStore.useCommon.userId,
lawEnforcerIdCard: null lawEnforcerIdCard: null
} }
]) ]);
const additionalLawEnforcers = ref([]) const additionalLawEnforcers = ref([]);
// 弹窗显示状态 // 弹窗显示状态
const showPopups = reactive({ const showPopups = reactive({
@ -118,124 +121,135 @@ const showPopups = reactive({
person: false, person: false,
basis: false, basis: false,
time: false time: false
}) });
// 当前编辑索引(用于人员和执法依据) // 当前编辑索引(用于人员和执法依据)
const currentEditIndex = ref(-1) const currentEditIndex = ref(-1);
// 选项数据(仅三种类别) // 选项数据(仅三种类别)
const categoryOptions = [ const categoryOptions = [
{ text: "行政检查", value: 1 }, { text: '行政检查', value: 1 },
{ text: "材料备案", value: 2 }, { text: '材料备案', value: 2 },
{ text: "材料验证", value: 3 } { text: '材料验证', value: 3 },
] { text: '巡查', value: 5 }
];
// 从列表页带入 tab 时锁定类别 // 从列表页带入 tab 时锁定类别
const categoryDisabled = ref(false) const categoryDisabled = ref(false);
// 切换 tab / 类别时强制刷新执法信息区块(keep-alive 场景) // 切换 tab / 类别时强制刷新执法信息区块(keep-alive 场景)
const enforcementSectionKey = computed(() => `${route.query.tab || ""}_${formData.taskTypeId}`) const enforcementSectionKey = computed(() => `${route.query.tab || ''}_${formData.taskTypeId}`);
const taskTypeName = computed(() => categoryOptions.find((item) => item.value === formData.taskTypeId)?.text || "") const taskTypeName = computed(() => categoryOptions.find((item) => item.value === formData.taskTypeId)?.text || '');
const DRAFT_STORAGE_PREFIX = "nos_add_draft_" const DRAFT_STORAGE_PREFIX = 'nos_add_draft_';
const lastActiveTab = ref("") const lastActiveTab = ref('');
/** 从路由 query 中读取 tab 参数(兼容数组形式) */
const getRouteTabValue = () => { const getRouteTabValue = () => {
const rawTab = route.query.tab const rawTab = route.query.tab;
return Array.isArray(rawTab) ? rawTab[0] : rawTab return Array.isArray(rawTab) ? rawTab[0] : rawTab;
} };
/** 将路由 tab 解析为任务类别 ID(taskTypeId) */
const getTabTaskTypeId = () => { const getTabTaskTypeId = () => {
const tabValue = getRouteTabValue() const tabValue = getRouteTabValue();
if (tabValue === undefined || tabValue === null || tabValue === "") return null if (tabValue === undefined || tabValue === null || tabValue === '') return null;
const parsedValue = Number(tabValue) const parsedValue = Number(tabValue);
return Number.isFinite(parsedValue) ? parsedValue : null return Number.isFinite(parsedValue) ? parsedValue : null;
} };
const getPageTabKey = () => getRouteTabValue() || "default" /** 获取当前页签标识,用于草稿隔离存储 */
const getPageTabKey = () => getRouteTabValue() || 'default';
const getDraftStorageKey = (tab = getPageTabKey()) => `${DRAFT_STORAGE_PREFIX}${tab}` /** 生成当前 Tab 对应的 sessionStorage 草稿键名 */
const getDraftStorageKey = (tab = getPageTabKey()) => `${DRAFT_STORAGE_PREFIX}${tab}`;
/** 创建表单默认值(含当前用户所属执法单位) */
const createDefaultFormData = () => ({ const createDefaultFormData = () => ({
lawEnforcementAgency: $globalStore.useCommon.bureauUnitName, lawEnforcementAgency: $globalStore.useCommon.bureauUnitName,
taskTypeId: null, taskTypeId: null,
taskSourceId: "", taskSourceId: '',
lawEnforcementMatters: "", lawEnforcementMatters: '',
checkItemList: [""], checkItemList: [''],
legalBasis: [""], legalBasis: [''],
deadline: "", deadline: '',
proclamation: [] proclamation: []
}) });
/** 默认任务截止时间:次日,格式 YYYY-MM-DD */
const getDefaultEnforcementTime = () => { const getDefaultEnforcementTime = () => {
const tomorrow = new Date() const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1) tomorrow.setDate(tomorrow.getDate() + 1);
return `${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, "0")}-${String(tomorrow.getDate()).padStart(2, "0")}` return `${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, '0')}-${String(tomorrow.getDate()).padStart(2, '0')}`;
} };
/** uploader 展示用(幂等,可多次调用) */ /** uploader 展示用(幂等,可多次调用) */
const serializeProclamation = (proclamation = []) => const serializeProclamation = (proclamation = []) =>
proclamation.map((file) => ({ proclamation.map((file) => ({
url: toFullFileUrl(file?.url || file?.filePath || ""), url: toFullFileUrl(file?.url || file?.filePath || ''),
status: file?.status || "done", status: file?.status || 'done',
name: file?.name || file?.fileName || "", name: file?.name || file?.fileName || '',
isImage: file?.isImage isImage: file?.isImage
})) }));
/** 草稿持久化用相对路径,避免 sessionStorage 中重复叠加 /api */ /** 草稿持久化用相对路径,避免 sessionStorage 中重复叠加 /api */
const persistProclamationForDraft = (proclamation = []) => const persistProclamationForDraft = (proclamation = []) =>
proclamation.map((file) => ({ proclamation.map((file) => ({
url: toRelativeFilePath(file?.url || file?.filePath || ""), url: toRelativeFilePath(file?.url || file?.filePath || ''),
status: file?.status || "done", status: file?.status || 'done',
name: file?.name || file?.fileName || "", name: file?.name || file?.fileName || '',
isImage: file?.isImage isImage: file?.isImage
})) }));
/** 将草稿/历史字段名统一映射为 formData 结构 */
const normalizeFormData = (data = {}) => { const normalizeFormData = (data = {}) => {
const categoryNameToId = Object.fromEntries(categoryOptions.map((item) => [item.text, item.value])) const categoryNameToId = Object.fromEntries(categoryOptions.map((item) => [item.text, item.value]));
const checkItemList = data.checkItemList ?? (data.materialRequirements || []).map((item) => (typeof item === "string" ? item : item?.name || "")) const checkItemList = data.checkItemList ?? (data.materialRequirements || []).map((item) => (typeof item === 'string' ? item : item?.name || ''));
return { return {
lawEnforcementAgency: data.lawEnforcementAgency ?? data.enforcementUnit ?? "", lawEnforcementAgency: data.lawEnforcementAgency ?? '',
taskTypeId: data.taskTypeId ?? categoryNameToId[data.category] ?? null, taskTypeId: data.taskTypeId ?? categoryNameToId[data.category] ?? null,
taskSourceId: data.taskSourceId ?? data.taskSource ?? "", taskSourceId: data.taskSourceId ?? data.taskSource ?? '',
lawEnforcementMatters: data.lawEnforcementMatters ?? data.enforcementMatter ?? "", lawEnforcementMatters: data.lawEnforcementMatters ?? data.enforcementMatter ?? '',
checkItemList: checkItemList.length ? checkItemList : [""], checkItemList: checkItemList.length ? checkItemList : [''],
legalBasis: [...(data.legalBasis || data.enforcementBases || [""])], legalBasis: [...(data.legalBasis || data.enforcementBases || [''])],
deadline: data.deadline ?? data.enforcementTime ?? "", deadline: data.deadline ?? data.enforcementTime ?? '',
proclamation: data.proclamation ?? data.fileList ?? [] proclamation: data.proclamation ?? data.fileList ?? []
} };
} };
/** 将标准化后的数据写入响应式 formData */
const applyFormData = (data) => { const applyFormData = (data) => {
const defaults = createDefaultFormData() const defaults = createDefaultFormData();
const normalized = normalizeFormData(data) const normalized = normalizeFormData(data);
Object.assign(formData, defaults, normalized, { Object.assign(formData, defaults, normalized, {
checkItemList: [...normalized.checkItemList], checkItemList: [...normalized.checkItemList],
legalBasis: [...normalized.legalBasis], legalBasis: [...normalized.legalBasis],
proclamation: serializeProclamation(normalized.proclamation) proclamation: serializeProclamation(normalized.proclamation)
}) });
} };
/** 重置表单、企业及执法人员等全部页面状态 */
const resetFormState = () => { const resetFormState = () => {
applyFormData(createDefaultFormData()) applyFormData(createDefaultFormData());
formData.deadline = getDefaultEnforcementTime() formData.deadline = getDefaultEnforcementTime();
enterpriseData.value = createDefaultEnterpriseData() enterpriseData.value = createDefaultEnterpriseData();
additionalLawEnforcers.value = [] additionalLawEnforcers.value = [];
categoryDisabled.value = false categoryDisabled.value = false;
} };
/** 根据列表页带入的 tab 锁定任务类别并禁止修改 */
const applyTabCategoryFromRoute = () => { const applyTabCategoryFromRoute = () => {
const taskTypeId = getTabTaskTypeId() const taskTypeId = getTabTaskTypeId();
if (taskTypeId !== null) { if (taskTypeId !== null) {
formData.taskTypeId = taskTypeId formData.taskTypeId = taskTypeId;
categoryDisabled.value = true categoryDisabled.value = true;
} }
} };
/** 将当前填写内容持久化到 sessionStorage 草稿 */
const saveDraftToStorage = (tab = getPageTabKey()) => { const saveDraftToStorage = (tab = getPageTabKey()) => {
const draft = { const draft = {
formData: { formData: {
@ -249,109 +263,119 @@ const saveDraftToStorage = (tab = getPageTabKey()) => {
additionalLawEnforcers: JSON.parse(JSON.stringify(additionalLawEnforcers.value)), additionalLawEnforcers: JSON.parse(JSON.stringify(additionalLawEnforcers.value)),
categoryDisabled: categoryDisabled.value, categoryDisabled: categoryDisabled.value,
isSignMode: isSignMode.value isSignMode: isSignMode.value
} };
sessionStorage.setItem(getDraftStorageKey(tab), JSON.stringify(draft)) sessionStorage.setItem(getDraftStorageKey(tab), JSON.stringify(draft));
} };
/** 从 sessionStorage 读取指定 Tab 的草稿 */
const loadDraftFromStorage = (tab = getPageTabKey()) => { const loadDraftFromStorage = (tab = getPageTabKey()) => {
try { try {
const raw = sessionStorage.getItem(getDraftStorageKey(tab)) const raw = sessionStorage.getItem(getDraftStorageKey(tab));
return raw ? JSON.parse(raw) : null return raw ? JSON.parse(raw) : null;
} catch (e) { } catch (e) {
console.error("读取新增页草稿失败", e) console.error('读取新增页草稿失败', e);
return null return null;
} }
} };
/** 清除指定 Tab 的本地草稿 */
const clearDraftFromStorage = (tab = getPageTabKey()) => { const clearDraftFromStorage = (tab = getPageTabKey()) => {
sessionStorage.removeItem(getDraftStorageKey(tab)) sessionStorage.removeItem(getDraftStorageKey(tab));
} };
/** 将草稿对象还原到页面各响应式状态 */
const restoreDraft = (draft) => { const restoreDraft = (draft) => {
applyFormData(draft.formData) applyFormData(draft.formData);
const draftEnterprise = draft.enterpriseData || {} const draftEnterprise = draft.enterpriseData || {};
enterpriseData.value = { enterpriseData.value = {
enterpriseName: draftEnterprise.enterpriseName || "", enterpriseName: draftEnterprise.enterpriseName || '',
usciCode: draftEnterprise.usciCode || draftEnterprise.enterpriseNumber || "", usciCode: draftEnterprise.usciCode || draftEnterprise.enterpriseNumber || '',
enterpriseId: draftEnterprise.enterpriseId || "" enterpriseId: draftEnterprise.enterpriseId || ''
} };
lawEnforcer.value = draft.lawEnforcer || lawEnforcer.value lawEnforcer.value = draft.lawEnforcer || lawEnforcer.value;
additionalLawEnforcers.value = draft.additionalLawEnforcers || draft.additionalPersons || [] additionalLawEnforcers.value = draft.additionalLawEnforcers || draft.additionalPersons || [];
categoryDisabled.value = !!draft.categoryDisabled categoryDisabled.value = !!draft.categoryDisabled;
isSignMode.value = !!draft.isSignMode isSignMode.value = !!draft.isSignMode;
} };
/** 非补录模式下,从扫码缓存恢复企业信息 */
const initEnterpriseFromCache = () => { const initEnterpriseFromCache = () => {
if (isSignMode.value) return if (isSignMode.value) return;
const cached = sessionStorage.getItem("enterpriseScanData") const cached = sessionStorage.getItem('enterpriseScanData');
if (!cached) return if (!cached) return;
try { try {
const parsed = JSON.parse(cached) const parsed = JSON.parse(cached);
enterpriseData.value = { enterpriseData.value = {
enterpriseName: parsed.enterpriseName || "", enterpriseName: parsed.enterpriseName || '',
usciCode: parsed.usciCode || parsed.enterpriseNumber || "", usciCode: parsed.usciCode || parsed.enterpriseNumber || '',
enterpriseId: parsed.enterpriseId || "" enterpriseId: parsed.enterpriseId || ''
} };
} catch (e) { } catch (e) {
console.error("解析企业缓存数据失败", e) console.error('解析企业缓存数据失败', e);
} }
} };
/**
* 页面进入时的初始化逻辑(keep-alive 场景)
* - Tab 切换时先保存旧 Tab 草稿再重置
* - 优先恢复当前 Tab 草稿,否则应用路由类别并设置默认截止时间
*/
const handlePageEnter = () => { const handlePageEnter = () => {
const tab = getPageTabKey() const tab = getPageTabKey();
if (lastActiveTab.value && lastActiveTab.value !== tab) { if (lastActiveTab.value && lastActiveTab.value !== tab) {
saveDraftToStorage(lastActiveTab.value) saveDraftToStorage(lastActiveTab.value);
resetFormState() resetFormState();
} }
lastActiveTab.value = tab lastActiveTab.value = tab;
const draft = loadDraftFromStorage(tab) const draft = loadDraftFromStorage(tab);
if (draft) { if (draft) {
restoreDraft(draft) restoreDraft(draft);
return return;
} }
applyTabCategoryFromRoute() applyTabCategoryFromRoute();
if (!formData.deadline) { if (!formData.deadline) {
formData.deadline = getDefaultEnforcementTime() formData.deadline = getDefaultEnforcementTime();
} }
} };
const taskSourceOptions = ref([]) const taskSourceOptions = ref([]);
/** 拉取任务来源下拉选项 */
const fetchTaskSourceList = () => { const fetchTaskSourceList = () => {
nosTaskSourceListApi( nosTaskSourceListApi(
{}, {},
(res) => { (res) => {
if (res.code === 200) { if (res.code === 200) {
const list = res.rows ?? res.data ?? [] const list = res.rows ?? res.data ?? [];
taskSourceOptions.value = list.map((item) => ({ taskSourceOptions.value = list.map((item) => ({
text: item.sourceName, text: item.sourceName,
value: item.id value: item.id
})) }));
} }
}, },
(err) => { (err) => {
console.error("获取任务来源列表失败", err) console.error('获取任务来源列表失败', err);
} }
) );
} };
// ========== 事件处理函数 ========== // ========== 事件处理函数 ==========
// 关闭弹窗(保持 reactive 对象的响应式) /** 关闭指定类型的弹窗 */
const handleClosePopup = (type) => { const handleClosePopup = (type) => {
showPopups[type] = false showPopups[type] = false;
} };
// 打开人员选择器 /** 打开执法人员选择弹窗,index 为额外人员列表中的编辑下标 */
const handleOpenPersonPicker = (index) => { const handleOpenPersonPicker = (index) => {
currentEditIndex.value = index currentEditIndex.value = index;
showPopups.person = true showPopups.person = true;
} };
// 选择人员 /** 确认选择执法人员:新增或替换 additionalLawEnforcers 中对应项 */
const handleSelectPerson = (person) => { const handleSelectPerson = (person) => {
if (currentEditIndex.value === -1) { if (currentEditIndex.value === -1) {
additionalLawEnforcers.value.push({ additionalLawEnforcers.value.push({
@ -359,105 +383,105 @@ const handleSelectPerson = (person) => {
lawEnforcerId: person.lawEnforcerId, lawEnforcerId: person.lawEnforcerId,
lawEnforcerPhone: person.lawEnforcerPhone, lawEnforcerPhone: person.lawEnforcerPhone,
lawEnforcerIdCard: person.lawEnforcerIdCard lawEnforcerIdCard: person.lawEnforcerIdCard
}) });
} else { } else {
additionalLawEnforcers.value[currentEditIndex.value] = { additionalLawEnforcers.value[currentEditIndex.value] = {
lawEnforcerName: person.lawEnforcerName, lawEnforcerName: person.lawEnforcerName,
lawEnforcerId: person.lawEnforcerId, lawEnforcerId: person.lawEnforcerId,
lawEnforcerPhone: person.lawEnforcerPhone, lawEnforcerPhone: person.lawEnforcerPhone,
lawEnforcerIdCard: person.lawEnforcerIdCard lawEnforcerIdCard: person.lawEnforcerIdCard
} };
} }
showPopups.person = false showPopups.person = false;
} };
// 删除额外人员 /** 删除指定下标的额外执法人员 */
const handleRemovePerson = (index) => { const handleRemovePerson = (index) => {
additionalLawEnforcers.value.splice(index, 1) additionalLawEnforcers.value.splice(index, 1);
} };
// 添加额外人员 /** 在额外执法人员列表末尾追加空行 */
const handleAddPerson = () => { const handleAddPerson = () => {
additionalLawEnforcers.value.push({}) additionalLawEnforcers.value.push({});
} };
// 打开执法依据选择器 /** 打开执法依据选择弹窗,index 为 legalBasis 中的编辑下标 */
const handleOpenBasisPicker = (index) => { const handleOpenBasisPicker = (index) => {
currentEditIndex.value = index currentEditIndex.value = index;
showPopups.basis = true showPopups.basis = true;
} };
// 添加执法依据 /** 在执法依据列表末尾追加空项 */
const handleAddBasis = () => { const handleAddBasis = () => {
formData.legalBasis.push("") formData.legalBasis.push('');
} };
// 删除执法依据 /** 删除指定下标的执法依据(至少保留一项) */
const handleRemoveBasis = (index) => { const handleRemoveBasis = (index) => {
if (formData.legalBasis.length > 1) { if (formData.legalBasis.length > 1) {
formData.legalBasis.splice(index, 1) formData.legalBasis.splice(index, 1);
} }
} };
// 选择执法依据 /** 将选中的执法依据写入 legalBasis 对应下标 */
const handleSelectBasis = (basis) => { const handleSelectBasis = (basis) => {
const index = currentEditIndex.value const index = currentEditIndex.value;
if (index >= 0 && index < formData.legalBasis.length) { if (index >= 0 && index < formData.legalBasis.length) {
formData.legalBasis[index] = basis.name formData.legalBasis[index] = basis.name;
} }
showPopups.basis = false showPopups.basis = false;
} };
// 选择截止时间(格式:YYYY-MM-DD) /** 确认选择任务截止时间(格式:YYYY-MM-DD) */
const handleSelectTime = (time) => { const handleSelectTime = (time) => {
formData.deadline = time formData.deadline = time;
showPopups.time = false showPopups.time = false;
} };
// 选择企业 /** 确认选择检查对象企业并回填 enterpriseData */
const handleSelectEnterprise = (enterprise) => { const handleSelectEnterprise = (enterprise) => {
enterpriseData.value = { enterpriseData.value = {
enterpriseName: enterprise.enterpriseName, enterpriseName: enterprise.enterpriseName,
usciCode: enterprise.usciCode, usciCode: enterprise.usciCode,
enterpriseId: enterprise.enterpriseId enterpriseId: enterprise.enterpriseId
} };
showPopups.enterprise = false showPopups.enterprise = false;
} };
// 类别选择确认 /** 确认选择任务类别(taskTypeId) */
const handleCategoryConfirm = ({ selectedOptions }) => { const handleCategoryConfirm = ({ selectedOptions }) => {
formData.taskTypeId = selectedOptions[0]?.value ?? null formData.taskTypeId = selectedOptions[0]?.value ?? null;
showPopups.category = false showPopups.category = false;
} };
// 任务来源选择确认 /** 确认选择任务来源(taskSourceId) */
const handleTaskSourceConfirm = ({ selectedOptions }) => { const handleTaskSourceConfirm = ({ selectedOptions }) => {
formData.taskSourceId = selectedOptions[0]?.value || "" formData.taskSourceId = selectedOptions[0]?.value || '';
showPopups.taskSource = false showPopups.taskSource = false;
} };
// 返回上一页(保留草稿,在 onDeactivated 中持久化) /** 返回上一页(草稿在 onDeactivated 中自动保存) */
const goBack = () => { const goBack = () => {
router.back() router.back();
} };
// 重置表单并清除当前 Tab 草稿 /** 二次确认后重置表单并清除当前 Tab 草稿 */
const handleReset = () => { const handleReset = () => {
showConfirmDialog({ showConfirmDialog({
title: "确认重置", title: '确认重置',
message: "重置后将清空已填写内容和附件,是否继续?", message: '重置后将清空已填写内容和附件,是否继续?',
width: "80%" width: '80%'
}) })
.then(() => { .then(() => {
clearDraftFromStorage() clearDraftFromStorage();
resetFormState() resetFormState();
applyTabCategoryFromRoute() applyTabCategoryFromRoute();
showSuccessToast("已重置") showSuccessToast('已重置');
}) })
.catch(() => {}) .catch(() => {});
} };
// 组装执法人员列表 /** 合并主执法人员与额外人员,过滤空项后返回提交结构 */
const buildLawEnforcerList = () => { const buildLawEnforcerList = () => {
const list = [...lawEnforcer.value, ...additionalLawEnforcers.value] const list = [...lawEnforcer.value, ...additionalLawEnforcers.value]
.filter((item) => item?.lawEnforcerName) .filter((item) => item?.lawEnforcerName)
@ -466,20 +490,20 @@ const buildLawEnforcerList = () => {
lawEnforcerName: item.lawEnforcerName, lawEnforcerName: item.lawEnforcerName,
lawEnforcerPhone: item.lawEnforcerPhone, lawEnforcerPhone: item.lawEnforcerPhone,
lawEnforcerIdCard: item.lawEnforcerIdCard lawEnforcerIdCard: item.lawEnforcerIdCard
})) }));
return list return list;
} };
// 将上传附件转换为 proclamation /** 将 uploader 附件列表转换为接口所需的 proclamation 结构 */
const buildProclamation = () => const buildProclamation = () =>
(formData.proclamation || []) (formData.proclamation || [])
.filter((file) => file && file.url) .filter((file) => file && file.url)
.map((file) => ({ .map((file) => ({
fileName: file.name || file.file?.name || "", fileName: file.name || file.file?.name || '',
filePath: toRelativeFilePath(file.url) filePath: toRelativeFilePath(file.url)
})) }));
// 构建提交参数(NosLawEnforcementTaskSaveBO) /** 组装新增任务接口入参(NosLawEnforcementTaskSaveBO) */
const buildSubmitPayload = () => ({ const buildSubmitPayload = () => ({
checkItemList: formData.checkItemList.filter((item) => item?.trim()), checkItemList: formData.checkItemList.filter((item) => item?.trim()),
deadline: formData.deadline, deadline: formData.deadline,
@ -493,81 +517,85 @@ const buildSubmitPayload = () => ({
taskSourceId: formData.taskSourceId, taskSourceId: formData.taskSourceId,
taskTypeId: formData.taskTypeId, taskTypeId: formData.taskTypeId,
usciCode: enterpriseData.value.usciCode usciCode: enterpriseData.value.usciCode
}) });
// 取消 /** 二次确认后清空草稿、重置表单并返回列表 */
const handleCancel = () => { const handleCancel = () => {
showConfirmDialog({ showConfirmDialog({
title: "确认取消", title: '确认取消',
message: "取消后将清空已填写内容,是否继续?", message: '取消后将清空已填写内容,是否继续?',
width: "80%" width: '80%'
}) })
.then(() => { .then(() => {
clearDraftFromStorage() clearDraftFromStorage();
resetFormState() resetFormState();
applyTabCategoryFromRoute() applyTabCategoryFromRoute();
router.back() router.back();
}) })
.catch(() => {}) .catch(() => {});
} };
// 提交表单 /** 校验附件上传状态后,二次确认并调用新增任务接口 */
const onSubmit = () => { const onSubmit = () => {
// 有未上传完的附件则拦截 // 有未上传完的附件则拦截
const uploadingFile = (formData.proclamation || []).find((img) => img && img.status === "uploading") const uploadingFile = (formData.proclamation || []).find((img) => img && img.status === 'uploading');
if (uploadingFile) { if (uploadingFile) {
showToast("附件上传中,请稍后") showToast('附件上传中,请稍后');
return return;
} }
showConfirmDialog({ showConfirmDialog({
title: "确认提交", title: '确认提交',
message: "确认提交吗?提交后将无法修改。", message: '确认提交吗?提交后将无法修改。',
width: "80%" width: '80%'
}) })
.then(() => { .then(() => {
const payload = buildSubmitPayload() const payload = buildSubmitPayload();
console.log("提交数据:", payload) console.log('提交数据:', payload);
nosAddApi( nosAddApi(
payload, payload,
(res) => { (res) => {
console.log(res) clearDraftFromStorage();
clearDraftFromStorage() resetFormState();
resetFormState() applyTabCategoryFromRoute();
applyTabCategoryFromRoute() showSuccessToast('提交成功');
showSuccessToast("提交成功")
setTimeout(() => { setTimeout(() => {
router.back() router.back();
}, 500) }, 500);
}, },
(err) => { (err) => {
console.log(err) console.log(err);
showToast(err?.message || '提交失败');
} }
) );
}) })
.catch(() => { .catch(() => {
// 用户取消 // 用户取消
}) });
} };
// ========== 生命周期(keep-alive:onMounted 仅一次,onActivated 每次进入) ========== // ========== 生命周期(keep-alive:onMounted 仅一次,onActivated 每次进入) ==========
/** 首次挂载:恢复扫码企业缓存并加载任务来源 */
onMounted(() => { onMounted(() => {
initEnterpriseFromCache() initEnterpriseFromCache();
fetchTaskSourceList() fetchTaskSourceList();
}) });
/** 每次从缓存激活:恢复或初始化当前 Tab 表单 */
onActivated(() => { onActivated(() => {
handlePageEnter() handlePageEnter();
}) });
/** 离开页面时自动保存当前 Tab 草稿 */
onDeactivated(() => { onDeactivated(() => {
saveDraftToStorage(lastActiveTab.value || getPageTabKey()) saveDraftToStorage(lastActiveTab.value || getPageTabKey());
}) });
// 路由 push 到本页时触发(与项目 keep-alive 约定一致) /** 路由 push 到本页时触发(与项目 keep-alive 约定一致) */
$globalConfigure(() => { $globalConfigure(() => {
handlePageEnter() handlePageEnter();
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

15
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosDetail.vue

@ -1,6 +1,6 @@
<template> <template>
<div class="nos-detail-page app_content"> <div class="nos-detail-page app_content">
<van-nav-bar fixed placeholder title="执法监督码"> <!-- <van-nav-bar fixed placeholder title="执法监督码">
<template #left> <template #left>
<van-icon name="arrow-left" class="nav-icon" @click.stop="onBack" /> <van-icon name="arrow-left" class="nav-icon" @click.stop="onBack" />
<van-icon name="cross" class="nav-icon nav-icon--close" @click.stop="onClose" /> <van-icon name="cross" class="nav-icon nav-icon--close" @click.stop="onClose" />
@ -8,7 +8,7 @@
<template #right> <template #right>
<van-icon name="ellipsis" class="nav-icon" /> <van-icon name="ellipsis" class="nav-icon" />
</template> </template>
</van-nav-bar> </van-nav-bar> -->
<van-form @submit="onSubmit" scroll-to-error class="detail-form"> <van-form @submit="onSubmit" scroll-to-error class="detail-form">
<van-tabs <van-tabs
@ -32,6 +32,7 @@
v-model:task-item-list="taskItemList" v-model:task-item-list="taskItemList"
v-model:inspect-result="inspectResult" v-model:inspect-result="inspectResult"
:task-status="taskDetail.taskStatus" :task-status="taskDetail.taskStatus"
:task-deadline="taskDetail.deadline"
:evaluate-data="evaluateData" :evaluate-data="evaluateData"
/> />
</div> </div>
@ -59,10 +60,11 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const TAB_TASK_TITLE_MAP = { const TAB_TASK_TITLE_MAP = {
inspection: '行政检查任务', 1: '行政检查任务',
filing: '材料备案任务', 2: '材料备案任务',
verify: '材料验证任务', 3: '材料验证任务',
pending: '待核实案件', 4: '待核实案件',
5: '巡查任务',
}; };
const activeTab = ref('task'); const activeTab = ref('task');
@ -97,6 +99,7 @@ const applyDetailData = (data) => {
enforcementBases: data.legalBasis || [], enforcementBases: data.legalBasis || [],
startTime: data.createTime || '', startTime: data.createTime || '',
deadline: data.deadline || '', deadline: data.deadline || '',
taskTypeId: data.taskTypeId || '',
}; };
taskItemList.value = (data.taskItemList || []).map(item => ({ taskItemList.value = (data.taskItemList || []).map(item => ({
...item, ...item,

44
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosEvaluateList.vue

@ -14,24 +14,24 @@
<div class="evaluate-card" v-for="item in list" :key="item.id" @click="toDetail(item)"> <div class="evaluate-card" v-for="item in list" :key="item.id" @click="toDetail(item)">
<!-- 执法时间 --> <!-- 执法时间 -->
<div class="card-time"> <div class="card-time">
执法时间:{{ item.enforcementTime }} 办结时间:{{ item.endTime }}
</div> </div>
<!-- 标题 --> <!-- 标题 -->
<div class="card-title">{{ item.title || item.checkItemText || '执法检查' }}</div> <div class="card-title">{{ item.title || '执法检查' }}</div>
<!-- 内容 --> <!-- 内容 -->
<div class="card-content"> <div class="card-content">
<div class="info-row"> <div class="info-row">
<span class="label">执法单位</span> <span class="label">执法单位</span>
<span class="value">{{ item.lawEnforcement || item.bureauDeptName }}</span> <span class="value">{{ item.lawEnforcementAgency || '--' }}</span>
</div> </div>
<div class="info-row"> <div class="info-row">
<span class="label">执法人员:</span> <span class="label">执法人员</span>
<div> <div>
<div v-for="(enforcer, index) in item" :key="index" <div v-for="(enforcer, index) in item.lawEnforcer" :key="index"
class="enforcer-item"> class="enforcer-item">
{{ enforcer }} {{ enforcer.lawEnforcerName }} {{ enforcer.lawEnforcerIdCard || enforcer.lawEnforcerPhone }}
</div> </div>
</div> </div>
</div> </div>
@ -43,9 +43,9 @@
<!-- 底部状态 --> <!-- 底部状态 -->
<div class="card-footer"> <div class="card-footer">
<span class="status">状态:<span :class="getStatusClass(item.hasEvaluate)">{{ <span class="status">状态:<span :class="getStatusClass(item.commentStatus)">{{
getStatusText(item.hasEvaluate) }}</span></span> getStatusText(item.commentStatus) }}</span></span>
<span class="evaluate-time">评价时间:{{ item.evaluateTime }}</span> <span class="evaluate-time">评价时间:{{ item.commentTime }}</span>
</div> </div>
</div> </div>
</van-list> </van-list>
@ -55,11 +55,11 @@
</template> </template>
<script setup name="enforcementEvaluateList"> <script setup name="enforcementEvaluateList">
import { ref, onMounted } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { showToast } from 'vant'; import { showToast } from 'vant';
import 'vant/es/toast/style'; import 'vant/es/toast/style';
import { queryEvaluateList } from '@/api/enforcement/index'; import { nosTaskHasCommentPageApi } from '@/api/nos/enfDemo';
const router = useRouter(); const router = useRouter();
@ -96,12 +96,11 @@ const fetchList = () => {
const params = { const params = {
pageNum: pageNum.value, pageNum: pageNum.value,
pageSize: pageSize.value, pageSize: pageSize.value,
hasEvaluate: 1,
}; };
queryEvaluateList(params, (res) => { nosTaskHasCommentPageApi(params, (res) => {
if (res.code === 200) { if (res.code === 200) {
const rows = res.rows || res.data?.rows || res.data?.list || res.data || []; const rows = res.rows || [];
const total = res.total ?? res.data?.total ?? 0; const total = res.total ?? 0;
if (refreshing.value) { if (refreshing.value) {
list.value = Array.isArray(rows) ? rows : []; list.value = Array.isArray(rows) ? rows : [];
@ -148,8 +147,8 @@ const onRefresh = () => {
// ========== 跳转详情页面 ========== // ========== 跳转详情页面 ==========
const toDetail = (item) => { const toDetail = (item) => {
router.push({ router.push({
path: '/enforcement/enforcementEvaluateDetail', path: '/enforcement/nos/detail',
query: { id: item.id, hasEvaluate: 1, from: 'evaluateList' } query: { id: item.id, tab: item.taskStatus }
}); });
}; };
@ -158,10 +157,6 @@ const onClickLeft = () => {
router.back(); router.back();
}; };
// ========== 组件挂载时加载数据 ==========
onMounted(() => {
fetchList();
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -218,12 +213,13 @@ onMounted(() => {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-bottom: 15px;
line-height: 1.4; line-height: 1.4;
border-top: 1px dashed #e5e5e5;
padding-top: 15px;
} }
.card-content { .card-content {
border-top: 1px dashed #e5e5e5; // border-top: 1px dashed #e5e5e5;
padding-top: 15px; padding-top: 15px;
} }
@ -238,7 +234,7 @@ onMounted(() => {
} }
.enforcer-item { .enforcer-item {
padding: 4px 0px; padding: 0px 0px 6px 0px;
} }
.value { .value {

14
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosList.vue

@ -1,10 +1,5 @@
<template> <template>
<div class="nos-list-page app_content"> <div class="nos-list-page app_content">
<van-nav-bar fixed placeholder title="执法监督码">
<!-- <template #left>
<van-icon name="arrow-left" class="nav-icon" @click.stop="onBack" />
</template> -->
</van-nav-bar>
<div class="nos-list-body"> <div class="nos-list-body">
<van-tabs <van-tabs
@ -31,12 +26,17 @@ import FooterBar from '@/components/FooterBar.vue';
import NosListPanel from './components/NosListPanel.vue'; import NosListPanel from './components/NosListPanel.vue';
const router = useRouter(); const router = useRouter();
// 判断是否是执法人员
const isLaw = $globalStore.useCommon.hasLaw
const TAB_LIST = [ const TAB_LIST = isLaw ? [
{ key: 1, title: '行政检查' }, { key: 1, title: '行政检查' },
{ key: 2, title: '材料备案' }, { key: 2, title: '材料备案' },
{ key: 3, title: '材料验证' }, { key: 3, title: '材料验证' },
{ key: 4, title: '待核实案件' }, { key: 5, title: '巡查' },
{ key: 4, title: '待核实' },
] : [
{ key: 5, title: '巡查' },
]; ];
const activeTab = ref(1); const activeTab = ref(1);

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

@ -9,7 +9,7 @@ VITE_NOS_BASE_URL = '/nosapi'
# 仅 dev server 代理目标(生产由 nginx 转发,不读这两项) # 仅 dev server 代理目标(生产由 nginx 转发,不读这两项)
VITE_PROXY_TARGET = 'http://139.186.148.21:8080' VITE_PROXY_TARGET = 'http://139.186.148.21:8080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.13:28080' VITE_NOS_PROXY_TARGET = 'http://192.168.1.12:28080'
# 附件/图片等静态资源前缀(与现场执法同源) # 附件/图片等静态资源前缀(与现场执法同源)
VITE_FILE_URL = '/api' VITE_FILE_URL = '/api'

11
zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/components/NosDetailTaskTab.vue

@ -35,11 +35,11 @@
<div class="section"> <div class="section">
<div class="section-title">检查对象</div> <div class="section-title">检查对象</div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">执法单位</span> <span class="detail-label">{{ detail.taskTypeId != 5 ? '执法单位' : '巡查单位' }}</span>
<span class="detail-value">{{ detail.enforcementUnit }}</span> <span class="detail-value">{{ detail.enforcementUnit }}</span>
</div> </div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">执法人员</span> <span class="detail-label">{{ detail.taskTypeId != 5 ? '执法人员' : '参与人员' }}</span>
<div class="detail-value"> <div class="detail-value">
<p v-for="(person, index) in detail.enforcementPerson" :key="index" class="detail-text"> <p v-for="(person, index) in detail.enforcementPerson" :key="index" class="detail-text">
{{ person }} {{ person }}
@ -49,7 +49,7 @@
</div> </div>
<div class="section"> <div class="section">
<div class="section-title">执法信息</div> <div class="section-title">{{ detail.taskTypeId != 5 ? '执法信息' : '巡查信息' }}</div>
<div class="detail-row"> <div class="detail-row">
<span class="detail-label">企业名称</span> <span class="detail-label">企业名称</span>
<div class="detail-value"> <div class="detail-value">
@ -61,8 +61,8 @@
<span class="detail-label">类  别</span> <span class="detail-label">类  别</span>
<span class="detail-value">{{ detail.category }}</span> <span class="detail-value">{{ detail.category }}</span>
</div> </div>
<div class="detail-row"> <div class="detail-row" v-if="detail.taskTypeId == 1 || detail.taskTypeId == 5">
<span class="detail-label">执法事项</span> <span class="detail-label">{{ detail.taskTypeId == 1 ? '执法事项' : '巡查事项' }}</span>
<span class="detail-value">{{ detail.enforcementMatter }}</span> <span class="detail-value">{{ detail.enforcementMatter }}</span>
</div> </div>
<div v-if="requirementLabel" class="detail-row"> <div v-if="requirementLabel" class="detail-row">
@ -101,6 +101,7 @@ const CATEGORY_REQUIREMENT_LABEL_MAP = {
行政检查: '检查要求', 行政检查: '检查要求',
材料备案: '备案文件', 材料备案: '备案文件',
材料验证: '验证文件', 材料验证: '验证文件',
巡查: '巡查要求',
}; };
const props = defineProps({ const props = defineProps({

10
zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/nosDetail.vue

@ -61,10 +61,11 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const TAB_TASK_TITLE_MAP = { const TAB_TASK_TITLE_MAP = {
inspection: '行政检查任务', 1: '行政检查任务',
filing: '材料备案任务', 2: '材料备案任务',
verify: '材料验证任务', 3: '材料验证任务',
pending: '待核实案件', 4: '待核实案件',
5: '巡查任务',
}; };
const activeTab = ref('task'); const activeTab = ref('task');
@ -95,6 +96,7 @@ const applyDetailData = (data) => {
enforcementBases: data.legalBasis || [], enforcementBases: data.legalBasis || [],
startTime: data.createTime || '', startTime: data.createTime || '',
deadline: data.deadline || '', deadline: data.deadline || '',
taskTypeId: data.taskTypeId || '',
}; };
taskItemList.value = (data.taskItemList || []).map((item) => { taskItemList.value = (data.taskItemList || []).map((item) => {
const { fileList, ...rest } = item const { fileList, ...rest } = item

1
zdxt-web-client/zdxt-efcode-nos-enterprise-app/src/views/enterprise/nos/nosList.vue

@ -36,6 +36,7 @@ const TAB_LIST = [
{ key: 1, title: '行政检查' }, { key: 1, title: '行政检查' },
{ key: 2, title: '材料备案' }, { key: 2, title: '材料备案' },
{ key: 3, title: '材料验证' }, { key: 3, title: '材料验证' },
{ key: 5, title: '巡查' },
]; ];
const activeTab = ref(1); const activeTab = ref(1);

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

@ -10,4 +10,4 @@ VITE_APP_ENV = 'dev'
VITE_NOS_BASE_URL = '/nosapi' VITE_NOS_BASE_URL = '/nosapi'
# 仅 dev server 代理目标(生产由 nginx 转发,不读这两项) # 仅 dev server 代理目标(生产由 nginx 转发,不读这两项)
VITE_PROXY_TARGET = 'http://139.186.148.21:8080' VITE_PROXY_TARGET = 'http://139.186.148.21:8080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.13:28080' VITE_NOS_PROXY_TARGET = 'http://192.168.1.12:28080'

Loading…
Cancel
Save