Browse Source

fix:优化组件传值

dev
上官小雨 3 months ago
parent
commit
68ea6c5ab3
  1. 21
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/AttachmentTaskSource.vue
  2. 57
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/EnforcementInfoSection.vue
  3. 42
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/EnterpriseInfoSection.vue
  4. 431
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/register/EnforcementInfoSection.vue
  5. 76
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/register/EnterpriseInfoSection.vue
  6. 461
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/register/RegisterPopups.vue
  7. 95
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailMaterialsTab.vue
  8. 55
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/components/NosDetailTaskTab.vue
  9. 14
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosAdd.vue
  10. 9
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/views/enforcement/nos/nosDetail.vue
  11. 2
      zdxt-web-client/zdxt-efcode-nos-enforcement-app/vite-env/.env.enforcement-dev
  12. 2
      zdxt-web-client/zdxt-efcode-nos-enterprise-app/vite-env/.env.enterprise-dev

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

@ -7,7 +7,7 @@
<div class="file-upload-label">   <span class="form-text">仅限于PDF文档50M以下最多五个文件</span></div>
<div class="uploader-wrapper">
<van-uploader
v-model="formData.proclamation"
v-model="proclamation"
capture="false"
accept=".pdf"
multiple
@ -22,7 +22,7 @@
<!-- 任务来源 -->
<van-field
:class="formData.taskTypeId == 5 ? 'usci-code-field' : ''"
:class="taskTypeId == 5 ? 'usci-code-field' : ''"
:model-value="taskSourceName"
is-link
readonly
@ -31,8 +31,8 @@
placeholder="请选择"
@click="$emit('showTaskSourcePicker')"
clearable
:rules="[{ required: formData.taskTypeId != 5, message: '请选择任务来源' }]"
:required="formData.taskTypeId != 5"
:rules="[{ required: taskTypeId != 5, message: '请选择任务来源' }]"
:required="taskTypeId != 5"
/>
</div>
</template>
@ -43,11 +43,12 @@ import { uploadFile } from '@/util/fileUpload';
import { toFullFileUrl } from '@/util/fileUrl';
import { showToast } from 'vant';
import 'vant/es/toast/style';
const proclamation = defineModel('proclamation', { default: () => [] });
const taskSourceId = defineModel('taskSourceId', { default: '' });
const taskTypeId = defineModel('taskTypeId', { default: null });
const props = defineProps({
formData: {
type: Object,
required: true
},
taskSourceOptions: {
type: Array,
default: () => []
@ -55,11 +56,11 @@ const props = defineProps({
});
const taskSourceName = computed(() => {
const option = props.taskSourceOptions.find((item) => item.value === props.formData.taskSourceId);
const option = props.taskSourceOptions.find((item) => item.value === taskSourceId.value);
return option?.text || '';
});
const emit = defineEmits(['showTaskSourcePicker', 'open-picker']);
defineEmits(['showTaskSourcePicker']);
const showUploadToast = (message) => {
showToast({

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

@ -151,73 +151,68 @@
</template>
<script setup name="EnforcementInfoSection">
const formData = defineModel('formData', { required: true });
const additionalPersons = defineModel('additionalPersons', { default: () => [] });
const props = defineProps({
formData: {
type: Object,
required: true
},
defaultPerson: {
type: Object,
default: null
},
additionalPersons: {
type: Array,
default: () => []
},
categoryDisabled: {
type: Boolean,
default: false
},
category: {
type: String,
default: ""
default: ''
}
})
});
const emit = defineEmits(["openPersonPicker", "removePerson", "addPerson", "showCategoryPicker", "showTaskSourcePicker", "showTimePicker", "openBasisPicker", "addBasis", "removeBasis"])
const emit = defineEmits(['openPersonPicker', 'removePerson', 'addPerson', 'showCategoryPicker', 'showTaskSourcePicker', 'showTimePicker', 'openBasisPicker', 'addBasis', 'removeBasis']);
const onCategoryClick = () => {
if (!props.categoryDisabled) {
emit("showCategoryPicker")
emit('showCategoryPicker');
}
}
};
const CATEGORY_REQUIREMENT_LABEL_MAP = {
行政检查: "检查要求",
材料备案: "备案文件",
材料验证: "验证文件",
巡查: "巡查要求"
}
行政检查: '检查要求',
材料备案: '备案文件',
材料验证: '验证文件',
巡查: '巡查要求'
};
// label category prop keep-alive
const requirementLabel = computed(() => {
return CATEGORY_REQUIREMENT_LABEL_MAP[props.category] || ""
})
return CATEGORY_REQUIREMENT_LABEL_MAP[props.category] || '';
});
//
const hasPerson = computed(() => {
return (props.defaultPerson?.lawEnforcerName || props.additionalPersons.some((p) => p.lawEnforcerName)) ? "已填写" : ""
})
return (props.defaultPerson?.lawEnforcerName || additionalPersons.value.some((p) => p.lawEnforcerName)) ? '已填写' : '';
});
//
const hasMaterialRequirement = computed(() => {
return props.formData.checkItemList?.some((item) => item?.trim()) ? "已填写" : ""
})
return formData.value.checkItemList?.some((item) => item?.trim()) ? '已填写' : '';
});
//
const hasLegalBasis = computed(() => {
return props.formData.legalBasis?.some((item) => item?.trim()) ? "已填写" : ""
})
return formData.value.legalBasis?.some((item) => item?.trim()) ? '已填写' : '';
});
const addMaterialRow = () => {
props.formData.checkItemList.push("")
}
formData.value.checkItemList.push('');
};
const removeMaterialRow = (index) => {
if (props.formData.checkItemList.length > 1) {
props.formData.checkItemList.splice(index, 1)
if (formData.value.checkItemList.length > 1) {
formData.value.checkItemList.splice(index, 1);
}
}
};
</script>
<style lang="less" scoped>

42
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/nos/EnterpriseInfoSection.vue

@ -4,7 +4,7 @@
<!-- 企业名称 -->
<van-field
v-model="enterpriseName"
v-model="enterpriseData.enterpriseName"
:is-link="isSignMode"
:readonly="true"
name="enterpriseName"
@ -18,7 +18,7 @@
<!-- 统一社会信用代码 -->
<van-field
class="usci-code-field"
v-model="usciCode"
v-model="enterpriseData.usciCode"
:is-link="isSignMode"
:readonly="true"
name="usciCode"
@ -31,40 +31,26 @@
</template>
<script setup name="EnterpriseInfoSection">
import { computed, onMounted } from "vue"
const enterpriseData = defineModel({
default: () => ({
enterpriseName: '',
usciCode: '',
enterpriseId: ''
})
});
const props = defineProps({
enterpriseData: {
type: Object,
default: null
},
defineProps({
isSignMode: {
type: Boolean,
default: false
}
})
});
const emit = defineEmits(["update:enterpriseData", "open-picker"])
//
const enterpriseName = computed({
get: () => props.enterpriseData?.enterpriseName || "",
set: (val) => {
emit("update:enterpriseData", { ...props.enterpriseData, enterpriseName: val })
}
})
//
const usciCode = computed({
get: () => props.enterpriseData?.usciCode || "",
set: (val) => {
emit("update:enterpriseData", { ...props.enterpriseData, usciCode: val })
}
})
const emit = defineEmits(['open-picker']);
const handleClick = () => {
emit("open-picker")
}
emit('open-picker');
};
</script>
<style lang="less" scoped>

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

@ -1,431 +0,0 @@
<template>
<div class="section">
<div class="section-title">执法信息</div>
<!-- 是否关联预告 -->
<van-field v-model="formData.relatedNotice" is-link readonly name="relatedNotice" label="是否关联预告"
placeholder="请选择" @click="$emit('showNoticePicker')" clearable />
<!-- 执法单位 -->
<van-field v-model="formData.enforcementUnit" name="enforcementUnit" label="执法单位" placeholder="请输入执法单位"
:rules="[{ required: true, message: '请填写执法单位' }]" required />
<!-- 执法人员 -->
<div class="form-row required">
<div class="form-label"><span class="required-mark">*</span>执法人员</div>
<div class="form-value person-select">
<div class="person-item default-person">
<span>{{ defaultPerson?.name }} {{ defaultPerson?.id }}</span>
</div>
<div v-for="(person, index) in additionalPersons" :key="index" class="person-item-add">
<span @click="$emit('openPersonPicker', index)" :class="{ placeholder: !person.name }">
{{ person.name || '请选择' }}
</span>
<van-icon name="close" class="remove-icon" @click="$emit('removePerson', index)" />
</div>
<div class="person-add">
<span @click="$emit('openPersonPicker', -1)">请选择</span>
<van-icon name="add-o" class="add-circle-icon" @click="$emit('addPerson')" />
</div>
</div>
</div>
<!-- 隐藏字段用于验证执法人员 -->
<van-field v-model="hasPerson" name="enforcementPerson" :rules="[{ required: true, message: '请选择执法人员' }]"
style="display: none;" />
<!-- 执法类别 -->
<van-field v-model="formData.category" is-link readonly name="category" label="执法类别" placeholder="请选择"
@click="$emit('showCategoryPicker')" clearable :rules="[{ required: true, message: '请选择执法类别' }]" required />
<!-- 任务来源 -->
<van-field v-model="formData.taskSource" is-link readonly name="taskSource" label="任务来源" placeholder="请选择"
@click="$emit('showTaskSourcePicker')" clearable :rules="[{ required: true, message: '请选择任务来源' }]"
required />
<!-- 执法事项 -->
<van-field v-model="formData.enforcementMatter" name="enforcementMatter" label="执法事项" placeholder="请输入执法事项"
type="textarea" :rules="[{ required: true, message: '请填写执法事项' }]" required rows="3" maxlength="1000"
show-word-limit />
<!-- 执法依据 -->
<div class="basis-container">
<div v-for="(basis, index) in formData.enforcementBases" :key="index" class="form-row basis-row">
<div class="form-label">执法依据</div>
<div class="form-value basis-wrapper">
<span @click="$emit('openBasisPicker', index)" :class="{ placeholder: !basis }">
{{ basis || '请选择' }}
</span>
<van-icon v-if="index === formData.enforcementBases.length - 1" name="add-o" class="add-circle-icon"
@click.stop="$emit('addBasis')" />
<van-icon v-else name="close" class="close-btn" @click.stop="$emit('removeBasis', index)" />
</div>
</div>
</div>
<!-- 原因 -->
<van-field v-model="formData.reason" name="reason" label="原因" placeholder="请输入原因" readonly />
<!-- 执法时间 -->
<van-field v-model="formData.enforcementTime" name="enforcementTime" label="执法时间" placeholder="请选择执法时间"
readonly />
<!-- <van-field v-model="formData.enforcementTime" name="enforcementTime" label="执法时间" placeholder="请选择执法时间" is-link
readonly :rules="[{ required: true, message: '请选择执法时间' }]" required /> -->
<!-- 联合执法 -->
</div>
<div class="form-section">
<div class="">
<van-field name="radio" label="联合执法" required>
<template #input>
<van-radio-group v-model="formData.jointEnforcement" direction="horizontal">
<van-radio name="0" shape="square"></van-radio>
<van-radio name="1" shape="square"></van-radio>
</van-radio-group>
</template>
</van-field>
<!-- <div class="form-label"><span class="required-mark">*</span>联合执法</div>
<div class="form-value radio-group">
<label :class="['radio-option', { active: formData.jointEnforcement === false }]"
@click="formData.jointEnforcement = false">
</label>
<label :class="['radio-option', { active: formData.jointEnforcement === true }]"
@click="formData.jointEnforcement = true">
</label>
</div> -->
</div>
<!-- 隐藏字段用于验证联合执法 -->
<van-field v-model="jointEnforcementValue" name="jointEnforcement"
:rules="[{ required: true, message: '请选择联合执法' }]" style="display: none;" />
</div>
<div class="form-entrusted">
<!-- 受委托执法 -->
<div class="entrusted-enforcement">
<van-field name="radio" label="受委托执法" required>
<template #input>
<van-radio-group v-model="formData.entrustedEnforcement" direction="horizontal">
<van-radio name="0" shape="square"></van-radio>
<van-radio name="1" shape="square"></van-radio>
</van-radio-group>
</template>
</van-field>
<!-- <div class="form-label"><span class="required-mark">*</span>受委托执法</div>
<div class="form-value radio-group">
<label :class="['radio-option', { active: formData.entrustedEnforcement === false }]"
@click="formData.entrustedEnforcement = false">
</label>
<label :class="['radio-option', { active: formData.entrustedEnforcement === true }]"
@click="formData.entrustedEnforcement = true">
</label>
</div> -->
</div>
<!-- 隐藏字段用于验证受委托执法 -->
<van-field v-model="entrustedEnforcementValue" name="entrustedEnforcement"
:rules="[{ required: true, message: '请选择受委托执法' }]" style="display: none;" />
<div class="form-hint">本次执法是否为受委托事项</div>
<!-- 附件 -->
<div class="file-upload-row">
<div class="file-upload-label">附件 <span class="form-text">不多于8张</span></div>
<div class="uploader-wrapper">
<van-uploader v-model="formData.fileList" multiple :max-count="8" :after-read="onAfterReadImage" />
</div>
</div>
</div>
</template>
<script setup name="EnforcementInfoSection">
import { computed } from 'vue';
import { uploadFile } from '@/util/fileUpload';
const props = defineProps({
formData: {
type: Object,
required: true
},
defaultPerson: {
type: Object,
default: null
},
additionalPersons: {
type: Array,
default: () => []
}
});
defineEmits([
'showNoticePicker',
'openPersonPicker',
'removePerson',
'showCategoryPicker',
'showTaskSourcePicker',
'openBasisPicker',
'addBasis',
'removeBasis'
]);
//
const hasPerson = computed(() => {
return props.defaultPerson?.name || props.additionalPersons.some(p => p.name);
});
//
const jointEnforcementValue = computed(() => {
return props.formData.jointEnforcement !== undefined ? '已选择' : '';
});
//
const entrustedEnforcementValue = computed(() => {
return props.formData.entrustedEnforcement !== undefined ? '已选择' : '';
});
//
const onAfterReadImage = (file) => {
const files = Array.isArray(file) ? file : [file];
files.forEach(f => {
uploadFile(f, (res) => {
const data = res && res.data ? res.data : res;
const filePath = typeof data === 'string' ? data : (data && (data.url || data.fileUrl || data.path)) || '';
if (filePath) {
f.url = filePath;
f.status = 'done';
}
});
});
};
</script>
<style lang="less" scoped>
.section {
background: #fff;
border-radius: 0.16rem;
margin-bottom: 0.24rem;
overflow: hidden;
:deep(.van-field__label) {
margin-right: 6px;
color: #000;
}
}
.section-title {
padding: 0.28rem 0.32rem;
font-size: 16px;
font-weight: 600;
color: #000;
}
.form-row {
display: flex;
justify-content: space-between;
// align-items: center;
padding: 0.32rem;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&.clickable {
&:active {
background: #f9f9f9;
}
}
&.required .form-label {
position: relative;
padding-left: 0.1rem;
}
&.radio-row {
padding-top: 0.24rem;
padding-bottom: 0.24rem;
}
}
.attachment-row {}
.required-mark {
color: #ee0a24;
font-size: 0.32rem;
margin-right: 0.08rem;
font-weight: bold;
}
.form-label {
font-size: 14px;
color: #333;
flex: none;
width: 2.4rem;
font-weight: 500;
}
.form-value {
flex: 1;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
.placeholder {
color: #c8c9cc;
}
}
.person-select {
// margin-left: 30px;
display: block;
// flex-direction: column;
// align-items: flex-end;
// gap: 0.16rem;
}
.person-item {
display: flex;
align-items: center;
font-size: 14px;
&.default-person span {
color: #333;
font-size: 14px;
}
}
.person-item-add {
padding: 10px 10px 0px 0px;
display: flex;
justify-content: space-between;
align-items: center;
.remove-icon {
font-size: 16px;
}
}
.person-add {
display: flex;
align-items: center;
justify-content: space-between;
align-items: center;
color: #c8c9cc;
font-size: 14px;
padding: 10px 10px 0px 0px;
.add-circle-icon {
font-size: 16px;
color: #333;
}
}
.basis-container {
margin-bottom: 0.12rem;
margin-left: 6px;
}
.basis-row {
padding: 0.24rem 0.32rem;
}
.basis-wrapper {
margin-left: 8px;
display: flex;
align-items: end;
justify-content: space-between;
padding-right: 10px;
.close-btn {
font-size: 16px;
}
.add-circle-icon {
font-size: 16px;
}
}
.form-section {
margin-bottom: 10px;
:deep(.van-field__value) {
display: flex;
justify-content: flex-end;
color: #000;
}
}
.form-entrusted {
height: 200px;
background-color: #fff;
.entrusted-enforcement {
:deep(.van-field__value) {
display: flex;
justify-content: flex-end;
color: #000;
}
}
}
.radio-option {
padding: 0.12rem 0.48rem;
border: 1px solid #dcdee0;
border-radius: 0.06rem;
font-size: 0.3rem;
color: #666;
transition: all 0.2s;
&:active {
transform: scale(0.95);
}
&.active {
border-color: #1367fe;
color: #1367fe;
background: rgba(19, 103, 254, 0.05);
}
}
.form-hint {
font-size: 12px;
color: #999;
padding: 0.12rem 0.32rem 0.24rem;
text-align: right;
}
.attachment-wrapper {
flex-direction: column;
align-items: flex-end;
gap: 0.16rem;
}
.attachment-hint {
font-size: 0.26rem;
color: #999;
}
.upload-icon {
font-size: 0.72rem;
color: #c8c9cc;
}
.file-upload-row{
padding:0px 20px 20px 20px;
.file-upload-label{
font-size: 14px;
color: #333;
margin-bottom: 0.16rem;
}
}
.uploader-wrapper {
:deep(.van-uploader__upload) {
background: #f5f7fa;
border-radius: 0.1rem;
}
}
</style>

76
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/register/EnterpriseInfoSection.vue

@ -1,76 +0,0 @@
<template>
<div class="section">
<div class="section-title">企业信息</div>
<!-- 企业名称 -->
<van-field v-model="enterpriseName" :is-link="isSignMode" :readonly="true" name="enterpriseName" label="企业名称"
placeholder="请选择" @click="handleClick" clearable
:rules="isSignMode ? [{ required: true, message: '请选择企业名称' }] : []" :required="isSignMode" />
<!-- 统一社会信用代码 -->
<van-field v-model="enterpriseNumber" :is-link="isSignMode" :readonly="true" name="enterpriseNumber"
label="统一社会信用代码" placeholder="请选择" @click="handleClick" clearable
:rules="isSignMode ? [{ required: true, message: '请选择统一社会信用代码' }] : []" :required="isSignMode" />
</div>
</template>
<script setup name="EnterpriseInfoSection">
import { computed } from 'vue';
const props = defineProps({
enterpriseData: {
type: Object,
default: null
},
isSignMode: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:enterpriseData', 'open-picker']);
//
const enterpriseName = computed({
get: () => props.enterpriseData?.enterpriseName || '',
set: (val) => {
emit('update:enterpriseData', { ...props.enterpriseData, enterpriseName: val });
}
});
//
const enterpriseNumber = computed({
get: () => props.enterpriseData?.enterpriseNumber || '',
set: (val) => {
emit('update:enterpriseData', { ...props.enterpriseData, enterpriseNumber: val });
}
});
const handleClick = () => {
emit('open-picker');
};
</script>
<style lang="less" scoped>
.section {
background: #fff;
border-radius: 0.16rem;
margin-bottom: 0.24rem;
overflow: hidden;
:deep(.van-field__label) {
width: 64px;
margin-right: 22px;
color: #000;
font-size: 14px;
}
}
.section-title {
padding: 0.28rem 0.32rem;
font-size: 16px;
font-weight: 600;
color: #000;
}
</style>

461
zdxt-web-client/zdxt-efcode-nos-enforcement-app/src/components/enforcement/register/RegisterPopups.vue

@ -1,461 +0,0 @@
<template>
<div class="register-popups">
<!-- 企业选择弹窗 -->
<van-popup v-model:show="localEnterprisePopup" position="bottom" :style="{ height: '80%' }" round>
<div class="enterprise-search-popup">
<div class="popup-header">
<span class="popup-title">选择企业</span>
<van-icon name="cross" class="close-icon" @click="localEnterprisePopup = false" />
</div>
<div class="search-bar">
<van-search v-model="enterpriseSearchKeyword" placeholder="请输入企业名称/统一信用代码搜索"
@search="handleEnterpriseSearch" @clear="clearEnterpriseSearch" />
<van-button class="search-btn" type="primary" @click="handleEnterpriseSearch">搜索</van-button>
</div>
<div class="enterprise-list">
<div v-for="(item, index) in enterpriseList" :key="index" class="enterprise-list-item"
@click="$emit('selectEnterprise', item)">
<div class="enterprise-name">{{ item.enterpriseName }}</div>
<div class="enterprise-number">统一社会信用代码{{ item.enterpriseNumber }}</div>
</div>
<van-empty v-if="enterpriseList.length === 0" description="暂无数据" />
</div>
</div>
</van-popup>
<!-- 预告查询弹窗 -->
<van-popup v-model:show="localNoticePopup" position="bottom" :style="{ height: '70%' }">
<div class="notice-popup">
<div class="popup-header">
<span class="popup-title">预告查询</span>
<van-icon name="cross" class="close-icon" @click="localNoticePopup = false" />
</div>
<div class="popup-content" @scroll="onNoticeScroll">
<div v-for="(notice, index) in noticeList" :key="index" class="notice-item">
<div class="notice-info">
<div class="notice-matter">执法事项{{ notice.matter }}</div>
<div class="notice-time">执法时间{{ notice.time }}</div>
</div>
<van-button class="link-btn" type="primary" @click="$emit('linkNotice', notice)">
一键关联
</van-button>
</div>
<div v-if="loading" class="loading-more">
<van-loading size="small" />
<span>加载中...</span>
</div>
<div v-else-if="finished" class="no-more">没有更多了</div>
</div>
</div>
</van-popup>
<!-- 类别选择弹窗 -->
<van-popup v-model:show="localCategoryPopup" round position="bottom">
<van-picker :columns="options.categories" @cancel="localCategoryPopup = false"
@confirm="$emit('confirmCategory', $event)" />
</van-popup>
<!-- 任务来源弹窗 -->
<van-popup v-model:show="localTaskSourcePopup" round position="bottom">
<van-picker :columns="options.taskSources" @cancel="localTaskSourcePopup = false"
@confirm="$emit('confirmTaskSource', $event)" />
</van-popup>
<!-- 人员查询弹窗 -->
<van-popup v-model:show="localPersonPopup" position="bottom" :style="{ height: '80%' }">
<div class="person-search-popup">
<div class="popup-header">
<span class="popup-title">人员查询</span>
<van-icon name="cross" class="close-icon" @click="localPersonPopup = false" />
</div>
<div class="search-bar">
<van-search v-model="personSearchKeyword" placeholder="请输入姓名/执法证件号搜索" @search="handlePersonSearch"
@clear="clearPersonSearch" />
<van-button class="search-btn" type="primary" @click="handlePersonSearch">搜索</van-button>
</div>
<div class="person-list">
<div v-for="(person, index) in personList" :key="index" class="person-list-item"
@click="$emit('selectPerson', person)">
<div class="person-name">{{ person.name }}</div>
<div class="person-id">执法证件号{{ person.id }}</div>
</div>
<van-empty v-if="personList.length === 0" description="暂无数据" />
</div>
</div>
</van-popup>
<!-- 执法依据弹窗 -->
<van-popup v-model:show="localBasisPopup" position="bottom" :style="{ height: '80%' }">
<div class="enforcement-basis-popup">
<div class="popup-header">
<span class="popup-title">执法依据</span>
<van-icon name="cross" class="close-icon" @click="localBasisPopup = false" />
</div>
<div class="search-bar">
<van-search v-model="basisSearchKeyword" placeholder="请输入关键字搜索" @search="handleBasisSearch"
@clear="clearBasisSearch" />
<van-button class="search-btn" type="primary" @click="handleBasisSearch">搜索</van-button>
</div>
<div class="enforcement-basis-list">
<div v-for="(item, index) in enforcementBasisList" :key="index" class="basis-list-item"
@click="$emit('selectBasis', item)">
<div class="basis-name">{{ item.name }}</div>
</div>
<van-empty v-if="enforcementBasisList.length === 0" description="没有更多了" />
</div>
</div>
</van-popup>
</div>
</template>
<script setup name="RegisterPopups">
import { ref, reactive, watch, computed } from 'vue';
const props = defineProps({
visible: {
type: Object,
required: true
},
options: {
type: Object,
default: () => ({
categories: [],
taskSources: []
})
}
});
const emit = defineEmits([
'close-popup',
'selectEnterprise',
'linkNotice',
'confirmCategory',
'confirmTaskSource',
'selectPerson',
'selectBasis'
]);
// 使 computed props
const localEnterprisePopup = computed({
get: () => props.visible.enterprise,
set: () => emit('close-popup', 'enterprise')
});
const localNoticePopup = computed({
get: () => props.visible.notice,
set: () => emit('close-popup', 'notice')
});
const localCategoryPopup = computed({
get: () => props.visible.category,
set: () => emit('close-popup', 'category')
});
const localTaskSourcePopup = computed({
get: () => props.visible.taskSource,
set: () => emit('close-popup', 'taskSource')
});
const localPersonPopup = computed({
get: () => props.visible.person,
set: () => emit('close-popup', 'person')
});
const localBasisPopup = computed({
get: () => props.visible.basis,
set: () => emit('close-popup', 'basis')
});
// ========== ==========
const enterpriseSearchKeyword = ref('');
const enterpriseList = ref([]);
const initEnterpriseList = () => {
enterpriseList.value = [
{ enterpriseName: '深圳市南山区椿立嘉源麻辣烫店', enterpriseNumber: '92440300MA5FXXXX1' },
{ enterpriseName: '深圳伊腾迪新能源有限公司', enterpriseNumber: '91440300MA5FXXXX2' },
{ enterpriseName: '深圳市永顺新绣花有限公司', enterpriseNumber: '91440300MA5FXXXX3' },
{ enterpriseName: '深圳市中棋金业有限公司', enterpriseNumber: '91440300MA5FXXXX4' },
{ enterpriseName: '深圳市百合隆科技有限公司', enterpriseNumber: '91440300MA5FXXXX5' },
];
};
const handleEnterpriseSearch = () => {
initEnterpriseList();
if (enterpriseSearchKeyword.value.trim()) {
const keyword = enterpriseSearchKeyword.value.toLowerCase();
enterpriseList.value = enterpriseList.value.filter(item =>
item.enterpriseName.toLowerCase().includes(keyword) ||
item.enterpriseNumber.toLowerCase().includes(keyword)
);
}
};
const clearEnterpriseSearch = () => {
enterpriseSearchKeyword.value = '';
initEnterpriseList();
};
watch(localEnterprisePopup, (val) => {
if (val) {
enterpriseSearchKeyword.value = '';
initEnterpriseList();
}
});
// ========== ==========
const personSearchKeyword = ref('');
const personList = ref([]);
const initPersonList = () => {
personList.value = [
{ id: '19020011041', name: '刘倩琳' },
{ id: '19020011042', name: '张明华' },
{ id: '19020011043', name: '李晓东' },
{ id: '19020011044', name: '王芳' },
];
};
const handlePersonSearch = () => {
initPersonList();
if (personSearchKeyword.value.trim()) {
const keyword = personSearchKeyword.value.toLowerCase();
personList.value = personList.value.filter(item =>
item.name.toLowerCase().includes(keyword) ||
item.id.toLowerCase().includes(keyword)
);
}
};
const clearPersonSearch = () => {
personSearchKeyword.value = '';
initPersonList();
};
watch(localPersonPopup, (val) => {
if (val) {
personSearchKeyword.value = '';
initPersonList();
}
});
// ========== ==========
const basisSearchKeyword = ref('');
const enforcementBasisList = ref([]);
const initBasisList = () => {
enforcementBasisList.value = [
{ id: 1, name: '《中华人民共和国行政处罚法》' },
{ id: 2, name: '《中华人民共和国行政许可法》' },
{ id: 3, name: '《中华人民共和国行政强制法》' },
{ id: 4, name: '《深圳市经济特区商事登记若干规定》' },
{ id: 5, name: '《深圳经济特区优化营商环境条例》' },
{ id: 6, name: '《中华人民共和国市场主体登记管理条例》' },
];
};
const handleBasisSearch = () => {
initBasisList();
if (basisSearchKeyword.value.trim()) {
const keyword = basisSearchKeyword.value.toLowerCase();
enforcementBasisList.value = enforcementBasisList.value.filter(item =>
item.name.toLowerCase().includes(keyword)
);
}
};
const clearBasisSearch = () => {
basisSearchKeyword.value = '';
initBasisList();
};
watch(localBasisPopup, (val) => {
if (val) {
basisSearchKeyword.value = '';
initBasisList();
}
});
// ========== ==========
const noticeList = ref([]);
const noticePage = ref(1);
const loading = ref(false);
const finished = ref(false);
const loadNotices = () => {
if (loading.value || finished.value) return;
loading.value = true;
setTimeout(() => {
const mockData = [
{ id: 1, matter: '例行检查', time: '2026-05-20 10:00' },
{ id: 2, matter: '专项检查', time: '2026-05-19 14:30' },
];
if (noticePage.value === 1) {
noticeList.value = mockData;
} else {
noticeList.value = [...noticeList.value, ...mockData];
}
loading.value = false;
if (noticePage.value >= 1) {
finished.value = true;
}
noticePage.value++;
}, 500);
};
const onNoticeScroll = () => { };
watch(localNoticePopup, (newVal) => {
if (newVal) {
noticePage.value = 1;
noticeList.value = [];
loading.value = false;
finished.value = false;
loadNotices();
}
});
</script>
<style lang="less" scoped>
.register-popups {
//
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.3rem;
border-bottom: 1px solid #f5f5f5;
.popup-title {
font-size: 0.4rem;
font-weight: 600;
color: #333;
}
.close-icon {
font-size: 0.45rem;
color: #999;
}
}
.search-bar {
display: flex;
align-items: center;
margin: 0.3rem;
// padding: 0.3rem;
:deep(.van-search) {
padding-right: 0px;
flex: 1;
height: 34px;
}
.search-btn {
flex: none;
height: 34px;
}
}
.enterprise-search-popup,
.person-search-popup,
.enforcement-basis-popup {
height: 100%;
display: flex;
flex-direction: column;
}
.enterprise-list,
.person-list,
.enforcement-basis-list {
flex: 1;
overflow-y: auto;
padding: 0 0.3rem;
}
.enterprise-list-item,
.person-list-item,
.basis-list-item {
padding:20px 20px 14px 14px;
border-bottom: 1px solid #f5f5f5;
&:active {
background: #f9f9f9;
}
}
.enterprise-name,
.person-name,
.basis-name {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.enterprise-number,
.person-id {
font-size: 14px;
color: #999;
}
.notice-popup {
height: 100%;
display: flex;
flex-direction: column;
}
.popup-content {
flex: 1;
overflow-y: auto;
padding: 0.3rem;
}
.notice-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.3rem;
// background: #f9f9f9;
margin-bottom: 0.2rem;
border-bottom: 0.1px solid #ededed;
.notice-info {
flex: 1;
}
.notice-matter {
font-size: 16px;
color: #000;
margin-bottom: 10px;
}
.notice-time {
font-size: 14px;
color: #999;
}
.link-btn {
flex: none;
font-size: 12px;
height: 26px;
}
}
.loading-more,
.no-more {
text-align: center;
padding: 0.3rem;
font-size: 0.28rem;
color: #999;
display: flex;
align-items: center;
justify-content: center;
gap: 0.1rem;
}
</style>

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

@ -1,13 +1,13 @@
<template>
<div class="detail-materials-tab">
<template v-if="Number(taskStatus) === 1">
<template v-if="Number(taskDetail.taskStatus) === 1">
<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>
</template>
<template v-else-if="Number(taskStatus) === 5 && !taskItemList.some((item) => (item.itemValue || []).length > 0)">
<template v-else-if="Number(taskDetail.taskStatus) === 5 && !taskItemList.some((item) => (item.itemValue || []).length > 0)">
<div class="empty-state">
<van-image :src="noDataImg" width="151" height="120" />
<div class="empty-text-main">暂无资料上传</div>
@ -39,9 +39,9 @@
</template>
</div>
<van-field v-if="Number(taskStatus) >= 2" class="audit-field" label="审核" :required="Number(taskStatus) === 3">
<van-field v-if="Number(taskDetail.taskStatus) >= 2" class="audit-field" label="审核" :required="Number(taskDetail.taskStatus) === 3">
<template #input>
<van-radio-group v-if="Number(taskStatus) === 3" v-model="item.pass" direction="horizontal" @change="onAuditChange(item)">
<van-radio-group v-if="Number(taskDetail.taskStatus) === 3" v-model="item.pass" direction="horizontal" @change="onAuditChange(item)">
<van-radio :name="1" icon-size="16px">通过</van-radio>
<van-radio :name="0" icon-size="16px">不通过</van-radio>
</van-radio-group>
@ -56,24 +56,24 @@
type="textarea"
rows="3"
maxlength="1000"
:show-word-limit="Number(taskStatus) === 3"
:show-word-limit="Number(taskDetail.taskStatus) === 3"
placeholder="请输入"
label="原因"
:readonly="Number(taskStatus) !== 3"
:required="Number(taskStatus) === 3"
:rules="Number(taskStatus) === 3 ? [{ required: true, message: '请输入原因', trigger: 'onBlur' }] : []"
:readonly="Number(taskDetail.taskStatus) !== 3"
:required="Number(taskDetail.taskStatus) === 3"
:rules="Number(taskDetail.taskStatus) === 3 ? [{ required: true, message: '请输入原因', trigger: 'onBlur' }] : []"
/>
</div>
<div class="result-card" v-if="Number(taskStatus) >= 2">
<div class="result-card" v-if="Number(taskDetail.taskStatus) >= 2">
<div class="result-title">检查结果</div>
<template v-if="Number(taskStatus) === 3">
<template v-if="Number(taskDetail.taskStatus) === 3">
<template v-if="taskItemList.length > 0 && taskItemList.every((item) => item.pass === 1)">
<div class="readonly-result-text" style="padding-top: 4px">检查结果通过</div>
</template>
<template v-else>
<van-radio-group v-model="inspectResult.checkResult" class="result-options" @change="onResultTypeChange">
<van-radio :name="1" icon-size="16px" v-if="Number(taskTypeId) != 5">转为待核实处置案件</van-radio>
<van-radio :name="1" icon-size="16px" v-if="Number(taskDetail.taskTypeId) != 5">转为待核实处置案件</van-radio>
<van-radio :name="2" icon-size="16px">退回企业补正材料</van-radio>
</van-radio-group>
@ -92,24 +92,24 @@
</template>
</template>
<template v-else>
<div class="readonly-result-text" style="padding-top: 4px">检查结果{{ TASK_STATUS_TEXT[Number(taskStatus)] }}</div>
<div class="readonly-result-text" v-if="Number(taskStatus) !== 3 && inspectResult.checkResult === 2 && inspectResult.deadline">
<div class="readonly-result-text" style="padding-top: 4px">检查结果{{ TASK_STATUS_TEXT[Number(taskDetail.taskStatus)] }}</div>
<div class="readonly-result-text" v-if="Number(taskDetail.taskStatus) !== 3 && inspectResult.checkResult === 2 && inspectResult.deadline">
补正时限{{ inspectResult.deadline }}
</div>
<div class="readonly-result-text" v-else>检查时间{{ Number(taskStatus) === 4 ? inspectResult.checkTime : overdueCheckTime }}</div>
<div class="readonly-result-text" v-else>检查时间{{ Number(taskDetail.taskStatus) === 4 ? inspectResult.checkTime : overdueCheckTime }}</div>
</template>
</div>
<div class="cloud-record-card" v-if="Number(taskStatus) === 4 && Number(digitalVoiceLoggingStatus) !== 0">
<div class="cloud-record-card" v-if="Number(taskDetail.taskStatus) === 4 && Number(taskDetail.digitalVoiceLoggingStatus) !== 0">
<div class="cloud-record-row">
<span class="cloud-record-label">云笔录</span>
<van-button class="btn-view" size="small" plain type="primary" @click="onViewCloudRecord"> 查看 </van-button>
</div>
</div>
<div class="evaluate-card" v-if="Number(taskStatus) === 4 && evaluateData">
<div class="evaluate-card" v-if="Number(taskDetail.taskStatus) === 4 && evaluateData">
<div class="card-title">评价</div>
<!-- 表情选择 -->
@ -162,7 +162,6 @@
<script setup name="NosDetailMaterialsTab">
import { showImagePreview, showToast } from 'vant'
import { useRoute } from 'vue-router'
import noDataImg from '@/assets/images/nos/no-data.png'
import { toFullFileUrl } from '@/util/fileUrl'
@ -173,32 +172,24 @@ import goodDisImg from '@/assets/images/eval-good-dis.png'
import badImg from '@/assets/images/eval-bad.png'
import badDisImg from '@/assets/images/eval-bad-dis.png'
const props = defineProps({
taskStatus: {
type: [Number, String],
default: ''
},
taskDeadline: {
type: String,
default: ''
},
taskTypeId: {
type: [Number, String],
default: ''
},
evaluateData: {
type: Object,
default: null
}
})
const taskItemList = defineModel('taskItemList', { type: Array, default: () => [] });
const inspectResult = defineModel('inspectResult', {
type: Object,
default: () => ({
checkResult: '',
deadline: ''
})
});
const taskDetail = defineModel('taskDetail', { default: () => ({}) });
const evaluateData = defineModel('evaluateData', { default: null });
const emit = defineEmits(['view-cloud-record'])
const emit = defineEmits(['view-cloud-record']);
const onViewCloudRecord = () => {
emit('view-cloud-record')
}
emit('view-cloud-record');
};
const overdueCheckTime = computed(() => props.taskDeadline || '')
const overdueCheckTime = computed(() => taskDetail.value?.deadline || '');
const TASK_STATUS_TEXT = {
1: '企业处理中',
2: '退回企业补正',
@ -206,39 +197,29 @@ const TASK_STATUS_TEXT = {
4: '已办结',
5: '已办结(超期)',
6: '转为待核实处置案件'
}
};
const evaluateForm = ref({
satisfactionLevel: 3, // 3: , 2: , 1:
commentReason: ''
})
});
watch(
() => props.evaluateData,
evaluateData,
(val) => {
if (val && val.satisfaction !== undefined) {
const satMap = { 0: 3, 1: 2, 2: 1 }
evaluateForm.value.satisfactionLevel = satMap[val.satisfaction] !== undefined ? satMap[val.satisfaction] : 3
const satMap = { 0: 3, 1: 2, 2: 1 };
evaluateForm.value.satisfactionLevel = satMap[val.satisfaction] !== undefined ? satMap[val.satisfaction] : 3;
if (val.commentReason) {
evaluateForm.value.commentReason = val.commentReason
evaluateForm.value.commentReason = val.commentReason;
}
}
},
{ immediate: true, deep: true }
)
const taskItemList = defineModel('taskItemList', { type: Array, default: () => [] })
const digitalVoiceLoggingStatus = defineModel('digitalVoiceLoggingStatus', { type: [Number, String], default: '' })
const inspectResult = defineModel('inspectResult', {
type: Object,
default: () => ({
checkResult: '',
deadline: ''
})
})
);
const showDatePicker = ref(false)
const showDatePicker = ref(false);
const showVideoPreview = ref(false)
const previewVideoUrl = ref('')
const minDate = new Date()

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

@ -129,30 +129,25 @@ const CATEGORY_REQUIREMENT_LABEL_MAP = {
巡查: '巡查要求'
}
const props = defineProps({
detail: {
type: Object,
required: true
}
})
const detail = defineModel({ default: () => ({}) });
const emit = defineEmits(['download-notice'])
const emit = defineEmits(['download-notice']);
const requirementLabel = computed(() => {
return CATEGORY_REQUIREMENT_LABEL_MAP[props.detail.category] || ''
})
return CATEGORY_REQUIREMENT_LABEL_MAP[detail.value.category] || '';
});
const onDownloadNotice = (url) => {
emit('download-notice', url)
}
const enforcementPersons = computed(() => {
return props.detail.enforcementPersonNames?.join('、') || ''
})
return detail.value.enforcementPersonNames?.join('、') || '';
});
const requirementItems = computed(() => {
return props.detail.materialRequirements?.map((item) => item.name).join('、') || ''
})
return detail.value.materialRequirements?.map((item) => item.name).join('、') || '';
});
const formatDateTimeCn = (dateTime) => {
if (!dateTime) return ''
@ -167,42 +162,42 @@ const formatDateTimeCn = (dateTime) => {
return `${year}${month}${day}${hour}${minute}${second}`
}
const formattedCreateTime = computed(() => formatDateTimeCn(props.detail.createTime))
const formattedCheckTime = computed(() => formatDateTimeCn(props.detail.checkTime))
const noticeFileName = computed(() => props.detail.proclamations?.[0]?.fileName || '')
const formattedCreateTime = computed(() => formatDateTimeCn(detail.value.createTime));
const formattedCheckTime = computed(() => formatDateTimeCn(detail.value.checkTime));
const noticeFileName = computed(() => detail.value.proclamations?.[0]?.fileName || '');
const noticeText = computed(() => {
return noticeFileName.value ? `依据“${noticeFileName.value}”通知/公告,` : ''
})
const investigationParagraph1Body = computed(() => {
const { detail } = props
const personType = detail.taskTypeId == 5 ? '巡查人员' : '执法人员'
const currentDetail = detail.value;
const personType = currentDetail.taskTypeId == 5 ? '巡查人员' : '执法人员';
return `${formattedCreateTime.value}深圳市执法局,${personType}${enforcementPersons.value},对${detail.enterpriseName || ''}进行了${detail.category || ''}`
})
return `${formattedCreateTime.value}深圳市执法局,${personType}${enforcementPersons.value},对${currentDetail.enterpriseName || ''}进行了${currentDetail.category || ''}`;
});
const investigationParagraph1 = computed(() => {
return `${noticeText.value}${investigationParagraph1Body.value}`
})
const investigationParagraph2 = computed(() => {
const { detail } = props
let text = ''
const currentDetail = detail.value;
let text = '';
if (detail.taskTypeId == 1 || detail.taskTypeId == 5) {
const matterType = detail.taskTypeId == 5 ? '巡查事项' : '执法事项'
text += `${matterType}${detail.enforcementMatter || ''}`
if (currentDetail.taskTypeId == 1 || currentDetail.taskTypeId == 5) {
const matterType = currentDetail.taskTypeId == 5 ? '巡查事项' : '执法事项';
text += `${matterType}${currentDetail.enforcementMatter || ''}`;
}
text += `${requirementLabel.value}有:${requirementItems.value}`
text += `${requirementLabel.value}有:${requirementItems.value}`;
return text
})
return text;
});
const investigationParagraph3 = computed(() => {
return `企业已提交材料,经核验,企业通过本次${props.detail.category || ''}`
})
return `企业已提交材料,经核验,企业通过本次${detail.value.category || ''}`;
});
const getInvestigationText = () => {
return `${investigationParagraph1.value}\n\n${investigationParagraph2.value}\n\n${investigationParagraph3.value}\n检查时间${formattedCheckTime.value}`

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

@ -11,17 +11,23 @@
<!-- 表单主体 -->
<van-form @submit="onSubmit" scroll-to-error class="page-content">
<!-- 附件组件 -->
<AttachmentTaskSource :form-data="formData" :task-source-options="taskSourceOptions" @show-task-source-picker="showPopups.taskSource = true" />
<AttachmentTaskSource
v-model:proclamation="formData.proclamation"
v-model:task-source-id="formData.taskSourceId"
v-model:task-type-id="formData.taskTypeId"
:task-source-options="taskSourceOptions"
@show-task-source-picker="showPopups.taskSource = true"
/>
<!-- 企业信息组件 -->
<EnterpriseInfoSection :enterprise-data="enterpriseData" :is-sign-mode="isSignMode" @open-picker="showPopups.enterprise = true" />
<EnterpriseInfoSection v-model="enterpriseData" :is-sign-mode="isSignMode" @open-picker="showPopups.enterprise = true" />
<!-- 执法信息组件 -->
<EnforcementInfoSection
:key="enforcementSectionKey"
:form-data="formData"
v-model:form-data="formData"
v-model:additional-persons="additionalLawEnforcers"
:category="taskTypeName"
:default-person="lawEnforcer[0]"
:additional-persons="additionalLawEnforcers"
:category-disabled="categoryDisabled"
@show-notice-picker="showPopups.notice = true"
@open-person-picker="handleOpenPersonPicker"

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

@ -23,7 +23,7 @@
>
<van-tab :title="taskTabTitle" name="task">
<div class="detail-scroll">
<NosDetailTaskTab :detail="taskDetail" @download-notice="onDownloadNotice" />
<NosDetailTaskTab v-model="taskDetail" @download-notice="onDownloadNotice" />
</div>
</van-tab>
<van-tab title="企业材料" name="materials">
@ -31,11 +31,8 @@
<NosDetailMaterialsTab
v-model:task-item-list="taskItemList"
v-model:inspect-result="inspectResult"
:task-status="taskDetail.taskStatus"
:task-deadline="taskDetail.deadline"
:task-type-id="taskDetail.taskTypeId"
:digital-voice-logging-status="taskDetail.digitalVoiceLoggingStatus"
:evaluate-data="evaluateData"
v-model:task-detail="taskDetail"
v-model:evaluate-data="evaluateData"
@view-cloud-record="onViewCloudRecord"
/>
</div>

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 转发,不读这两项)
VITE_PROXY_TARGET = 'http://139.186.148.21:8080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.9:28080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.12:28080'
# 附件/图片等静态资源前缀(与现场执法同源)
VITE_FILE_URL = '/api'

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'
# 仅 dev server 代理目标(生产由 nginx 转发,不读这两项)
VITE_PROXY_TARGET = 'http://139.186.148.21:8080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.9:28080'
VITE_NOS_PROXY_TARGET = 'http://192.168.1.12:28080'

Loading…
Cancel
Save