Browse Source

feat(enterprise): 评价详情提交接口接入、已评价数据回填、只读态控制、satisfaction数值映射

dev-V1.0.1
wangmingyong 6 months ago
parent
commit
e420aeee17
  1. 21
      zdxt-web-client/zdxt-efcode-enterprise-app/src/api/enterprise/index.js
  2. 67
      zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/BureauPicker.vue
  3. 33
      zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/ComplaintItemPicker.vue
  4. 9
      zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/ComplaintTypePicker.vue
  5. 2
      zdxt-web-client/zdxt-efcode-enterprise-app/src/config/env.js
  6. 97
      zdxt-web-client/zdxt-efcode-enterprise-app/src/util/dictUtil.js
  7. 2
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/demo/TestEnfDemoList.vue
  8. 13
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementCaseInfo.vue
  9. 227
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluate.vue
  10. 291
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluateDetail.vue
  11. 2
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementNoticeList.vue
  12. 14
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementRegistrationList.vue
  13. 194
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/registrationDetail.vue
  14. 53
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/home/enterpriseHomeV2.vue
  15. 2
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/law/lawServiceList.vue
  16. 13
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintList.vue
  17. 261
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintSuggestion.vue
  18. 4
      zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/myPage.vue
  19. 2
      zdxt-web-client/zdxt-efcode-enterprise-app/vite-env/.env.enterprise-dev

21
zdxt-web-client/zdxt-efcode-enterprise-app/src/api/enterprise/index.js

@ -30,6 +30,15 @@ export const getLawDetail = (params, success, error) => _get(enterpriseAppBaseUr
//执法案件信息详情
export const getLawCaseDetail = (params, success, error) => _get(enterpriseAppBaseUrl+"/lawEnforcementCase/lawCasedetail",params,success,error)
//评价详情
export const getEvaluateDetail = (params, success, error) => _get("/app/enterprise/evaluate", params, success, error)
//提交评价
export const submitEvaluate = (params, success, error) => _post("/app/enterprise/evaluate", params, success, error)
//企业消息 / 执法消息详情
export const getEnforcementRegistrationDetailById = (params, success, error) => _get(enterpriseAppBaseUrl + "/enforcement_registration/queryDetailById", params, success, error)
//普法服务列表
export const getEducationServicesList = (params, success, error) => _get("/app/enterprise/todoitem/list",params,success,error)
@ -42,6 +51,18 @@ export const getComplaintList = (params, success, error) => _get(enterpriseAppBa
//投诉建议-详情
export const getComplaintDetail = (params, success, error) => _get(enterpriseAppBaseUrl+"/complaint/detail",params,success,error)
//投诉建议-提交
export const addComplaint = (params, success, error) => _post(enterpriseAppBaseUrl+"/complaint/add",params,success,error)
//查询字典数据(公共)
export const queryDict = (params, success, error) => _get("/common/dict/queryDict",params,success,error)
//执法单位(部门)列表
export const getDeptList = (params, success, error) => _get("/enforcementcode/virtualUnit/list",params,success,error)
//投诉事项列表
export const getComplaintItemList = (params, success, error) => _get(enterpriseAppBaseUrl +"/complaint/listNotComplaint",params,success,error)
//深i企登录-暂时没有环境调通
export const getSiqLoginAuth = (params, success, error) => _post(enterpriseAppBaseUrl +"/thirdLogin/siqLogin",params,success,error);

67
zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/BureauPicker.vue

@ -17,17 +17,19 @@
</div>
<div class="bureau-list">
<van-radio-group v-model="selected">
<van-cell-group>
<van-cell v-for="item in filteredList" :key="item" :title="item" clickable
@click="selected = item">
<template #right-icon>
<van-radio :name="item" />
</template>
</van-cell>
</van-cell-group>
</van-radio-group>
<van-empty v-if="filteredList.length === 0" description="暂无数据" />
<div v-if="loading" class="loading-wrap">
<van-loading type="spinner" size="24" color="#2b7de9">加载中...</van-loading>
</div>
<template v-else>
<van-radio-group v-model="selected">
<div v-for="item in filteredList" :key="item.virId || item.id || item.virName"
class="bureau-row" @click="selected = item">
<van-radio :name="item" class="bureau-radio" @click.stop />
<span class="bureau-name">{{ item.virName }}</span>
</div>
</van-radio-group>
<van-empty v-if="filteredList.length === 0" description="暂无数据" />
</template>
</div>
</div>
</van-popup>
@ -38,17 +40,19 @@ import { computed } from 'vue';
const props = defineProps({
show: { type: Boolean, default: false },
list: { type: Array, default: () => [] }
list: { type: Array, default: () => [] },
loading: { type: Boolean, default: false }
});
const emit = defineEmits(['update:show', 'confirm']);
const searchKey = ref('');
const selected = ref('');
const selected = ref(null);
const filteredList = computed(() => {
if (!searchKey.value) return props.list;
return props.list.filter(item => item.includes(searchKey.value));
return props.list.filter(item => (item.virName || '').includes(searchKey.value));
});
const onUpdateShow = (val) => {
@ -67,7 +71,7 @@ const onCancel = () => {
watch(() => props.show, (val) => {
if (val) {
searchKey.value = '';
selected.value = '';
selected.value = null;
}
});
</script>
@ -132,4 +136,37 @@ watch(() => props.show, (val) => {
color: #333;
}
}
.bureau-row {
display: flex;
align-items: center;
padding: 0.25rem 0.3rem;
background: #fff;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
.bureau-radio {
margin-right: 0.2rem;
flex-shrink: 0;
}
.bureau-name {
flex: 1;
font-size: 0.32rem;
color: #333;
word-break: break-all;
}
}
.loading-wrap {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 0;
:deep(.van-loading__text) {
font-size: 0.3rem;
color: #999;
}
}
</style>

33
zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/ComplaintItemPicker.vue

@ -32,11 +32,12 @@
<!-- 列表 -->
<div class="complaint-item-list">
<div v-for="item in filteredList" :key="item.id" class="complaint-item-card" @click="onSelect(item)">
<div class="item-time">执法时间:{{ item.time }}</div>
<div v-if="item.time" class="item-time">执法时间:{{ item.time }}</div>
<div class="item-title">{{ item.title }}</div>
<div class="item-info">执法单位:{{ item.unit }}</div>
<div class="item-info">执法人员:{{ item.personnel.join('、') }}</div>
<div class="item-info">执法类别:{{ item.category }}</div>
<div v-if="item.subName && item.subName !== item.title" class="item-info">{{ item.subName }}</div>
<div v-if="item.unit" class="item-info">执法单位:{{ item.unit }}</div>
<div v-if="item.personnel && item.personnel.length" class="item-info">执法人员:{{ item.personnel.join('、') }}</div>
<div v-if="item.category" class="item-info">执法类别:{{ item.category }}</div>
</div>
<div class="no-more">没有更多了</div>
</div>
@ -86,18 +87,23 @@ const getDatePart = (datetime) => datetime.split(' ')[0];
const doFilter = () => {
let result = props.list;
if (startDate.value) {
result = result.filter(item => getDatePart(item.time) >= startDate.value);
result = result.filter(item => item.time && getDatePart(item.time) >= startDate.value);
}
if (endDate.value) {
result = result.filter(item => getDatePart(item.time) <= endDate.value);
result = result.filter(item => item.time && getDatePart(item.time) <= endDate.value);
}
if (searchKey.value) {
const key = searchKey.value;
result = result.filter(item =>
item.title.includes(key) ||
item.unit.includes(key) ||
item.personnel.some(p => p.includes(key))
);
result = result.filter(item => {
const title = item.title || '';
const subName = item.subName || '';
const unit = item.unit || '';
const personnel = Array.isArray(item.personnel) ? item.personnel : [];
return title.includes(key)
|| subName.includes(key)
|| unit.includes(key)
|| personnel.some(p => p.includes(key));
});
}
filteredList.value = result;
};
@ -165,6 +171,11 @@ watch(() => props.show, (val) => {
filteredList.value = [...props.list];
}
});
// 列表数据延迟返回时(弹窗已打开、接口随后才返回),同步更新过滤结果
watch(() => props.list, () => {
doFilter();
}, { deep: false });
</script>
<style lang="less" scoped>

9
zdxt-web-client/zdxt-efcode-enterprise-app/src/components/complaint/ComplaintTypePicker.vue

@ -9,15 +9,18 @@ const props = defineProps({
show: { type: Boolean, default: false },
columns: { type: Array, default: () => [] }
});
const emit = defineEmits(['update:show', 'confirm']);
const onUpdateShow = (val) => {
emit('update:show', val);
};
const onConfirm = ({ selectedValues }) => {
emit('confirm', selectedValues[0]);
const onConfirm = ({ selectedValues, selectedOptions }) => {
const option = selectedOptions && selectedOptions[0] ? selectedOptions[0] : null;
emit('confirm', {
text: option ? option.text : '',
value: selectedValues[0]
});
emit('update:show', false);
};

2
zdxt-web-client/zdxt-efcode-enterprise-app/src/config/env.js

@ -1,6 +1,8 @@
let baseUrl = import.meta.env.VITE_BASE_URL || "未定义";
let appEnv = import.meta.env.VITE_APP_ENV || "未定义";
let fileUrl = import.meta.env.VITE_FILE_URL || "";
export {
baseUrl,
appEnv,
fileUrl,
}

97
zdxt-web-client/zdxt-efcode-enterprise-app/src/util/dictUtil.js

@ -0,0 +1,97 @@
/**
* 字典工具 —— 通用字典加载、缓存、翻译
* 使用方式:
* import { loadDict, getDictText, getDictColumns } from '@/util/dictUtil';
* loadDict('caseType'); // 加载字典(自动缓存)
* getDictText('caseType', 0); // 翻译 value → text
* getDictColumns('caseType'); // 获取 [{text, value}] 用于 van-picker
*/
import { queryDict } from '@/api/enterprise';
// 内存缓存 { dictType → [{text, value}] }
const dictStore = {};
// sessionStorage key 生成
const cacheKey = (dictType) => `DICT_CACHE::${dictType}`;
/**
* 解析接口响应,兼容多种结构
*/
const parseRows = (res) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.rows)) return res.rows;
if (res && Array.isArray(res.data)) return res.data;
if (res && res.data && Array.isArray(res.data.rows)) return res.data.rows;
return [];
};
/**
* 标准化为 [{text, value}]
*/
const normalizeColumns = (rows) => {
return rows.map(item => ({
text: item.dictLabel || item.label || item.name || '',
value: item.dictValue || item.value || item.code || ''
})).filter(c => c.text);
};
/**
* 加载指定字典类型(含 sessionStorage 缓存)
* @param {string} dictType 字典类型
* @param {Function} [onSuccess] 可选回调,接收 columns [{text, value}]
* @param {Function} [onError] 可选失败回调
*/
export const loadDict = (dictType, onSuccess, onError) => {
// 内存命中直接返回
if (dictStore[dictType] && dictStore[dictType].length > 0) {
onSuccess && onSuccess(dictStore[dictType]);
return;
}
// sessionStorage 命中
const cached = sessionStorage.getItem(cacheKey(dictType));
if (cached) {
try {
const columns = JSON.parse(cached);
if (Array.isArray(columns) && columns.length > 0) {
dictStore[dictType] = columns;
onSuccess && onSuccess(columns);
return;
}
} catch (e) { /* 解析失败继续走接口 */ }
}
// 请求接口
queryDict({ dictType }, (res) => {
const columns = normalizeColumns(parseRows(res));
dictStore[dictType] = columns;
if (columns.length > 0) {
sessionStorage.setItem(cacheKey(dictType), JSON.stringify(columns));
}
onSuccess && onSuccess(columns);
}, (err) => {
onError && onError(err);
});
};
/**
* 获取字典翻译文本
* @param {string} dictType 字典类型
* @param {*} val 字典值
* @param {string} [fallback='-'] 未匹配时的降级显示
* @returns {string}
*/
export const getDictText = (dictType, val, fallback) => {
if (val === undefined || val === null || val === '') return fallback !== undefined ? fallback : '-';
const columns = dictStore[dictType] || [];
const key = String(val);
const found = columns.find(c => String(c.value) === key);
return found ? found.text : (fallback !== undefined ? fallback : val);
};
/**
* 获取字典列表(用于 van-picker :columns)
* @param {string} dictType
* @returns {Array} [{text, value}]
*/
export const getDictColumns = (dictType) => {
return dictStore[dictType] || [];
};

2
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/demo/TestEnfDemoList.vue

@ -22,7 +22,7 @@
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
finished-text=""
@load="onLoad"
:offset="50"
>

13
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementCaseInfo.vue

@ -2,16 +2,16 @@
<div class="case-info-page">
<!-- 顶部导航栏 -->
<van-nav-bar title="执法案件信息" left-text="关闭" left-arrow @click-left="onClickLeft">
<template #right>
<!-- <template #right>
<van-icon name="filter-o" size="20" @click="onFilterClick" />
<van-icon name="search" size="20" style="margin-left: 12px;" @click="onSearchClick" />
</template>
</template> -->
</van-nav-bar>
<!-- 可滚动列表区域 -->
<div class="list-container">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad"
:offset="50">
<!-- 空状态 -->
<van-empty v-if="finished && list.length === 0" description="暂无数据" />
@ -75,7 +75,7 @@ const getTagClass = (category) => {
const map = {
'1': 'tag-blue', // 行政检查
'2': 'tag-purple', // 行政处罚
'3': 'tag-orange', // 行政强制
'4': 'tag-orange', // 行政强制
};
return map[String(category)] || 'tag-blue';
};
@ -196,8 +196,9 @@ const toDetailView = (item) => {
router.push({
path: '/enterprise/registrationDetail',
query: {
id: item.idNumber,
category: item.lawEnforcementType
id: item.id,
category: item.lawEnforcementType,
lawEnforcementType:item.lawEnforcementType
}
});
};

227
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluate.vue

@ -1,12 +1,7 @@
<template>
<div class="evaluate-page">
<!-- 顶部导航栏 -->
<van-nav-bar
title="执法评价"
left-text="关闭"
left-arrow
@click-left="onClickLeft"
>
<van-nav-bar title="执法评价" left-text="关闭" left-arrow @click-left="onClickLeft">
<template #right>
<van-icon name="filter-o" size="20" @click="onFilterClick" />
<van-icon name="search" size="20" style="margin-left: 12px;" @click="onSearchClick" />
@ -24,21 +19,12 @@
</template>
<!-- 待评价列表 -->
<van-pull-refresh v-model="pendingRefreshing" @refresh="onPendingRefresh">
<van-list
v-model:loading="pendingLoading"
:finished="pendingFinished"
finished-text="没有更多了"
@load="onPendingLoad"
:offset="50"
>
<van-list v-model:loading="pendingLoading" :finished="pendingFinished" finished-text=""
@load="onPendingLoad" :offset="50">
<van-empty v-if="pendingFinished && pendingList.length === 0" description="暂无待评价" />
<div
class="evaluate-card"
v-for="item in pendingList"
:key="item.id"
@click="toEvaluateDetail(item)"
>
<div class="evaluate-card" v-for="item in pendingList" :key="item.id"
@click="toEvaluateDetail(item, 0)">
<div class="card-time">执法时间:{{ item.enforcementTime }}</div>
<div class="card-title">{{ item.title }}</div>
<div class="card-row">
@ -48,8 +34,11 @@
<div class="card-row">
<span class="row-label">执法人员</span>
<div class="row-value enforcer-list">
<div v-for="(enforcer, idx) in item.enforcers" :key="idx">
{{ enforcer.name }} {{ enforcer.phone }}
<div>
{{ item.lawEnforcer }}{{ item.lawEnforcerId }}
</div>
<div>
{{ item.lawEnforcerTwo }}{{ item.lawEnforcerIdTwo }}
</div>
</div>
</div>
@ -65,21 +54,12 @@
<van-tab title="已评价">
<!-- 已评价列表 -->
<van-pull-refresh v-model="evaluatedRefreshing" @refresh="onEvaluatedRefresh">
<van-list
v-model:loading="evaluatedLoading"
:finished="evaluatedFinished"
finished-text="没有更多了"
@load="onEvaluatedLoad"
:offset="50"
>
<van-list v-model:loading="evaluatedLoading" :finished="evaluatedFinished" finished-text=""
@load="onEvaluatedLoad" :offset="50">
<van-empty v-if="evaluatedFinished && evaluatedList.length === 0" description="暂无已评价" />
<div
class="evaluate-card"
v-for="item in evaluatedList"
:key="item.id"
@click="toEvaluateDetail(item)"
>
<div class="evaluate-card" v-for="item in evaluatedList" :key="item.id"
@click="toEvaluateDetail(item, 1)">
<div class="card-time">执法时间:{{ item.enforcementTime }}</div>
<div class="card-title">{{ item.title }}</div>
<div class="card-row">
@ -89,8 +69,11 @@
<div class="card-row">
<span class="row-label">执法人员</span>
<div class="row-value enforcer-list">
<div v-for="(enforcer, idx) in item.enforcers" :key="idx">
{{ enforcer.name }} {{ enforcer.phone }}
<div>
{{ item.lawEnforcer }}{{ item.lawEnforcerId }}
</div>
<div>
{{ item.lawEnforcerTwo }}{{ item.lawEnforcerIdTwo }}
</div>
</div>
</div>
@ -110,108 +93,141 @@
<script setup name="enforcementEvaluate">
import { showToast } from 'vant';
import 'vant/es/toast/style';
import { queryEnforcementRegistrationPageList } from '@/api/enterprise';
const router = useRouter();
// ========== Tab 状态 ==========
const activeTab = ref(0);
// ========== 分页配置 ==========
const PAGE_SIZE = 10;
// ========== 待评价列表状态 ==========
const pendingList = ref([]);
const pendingLoading = ref(false);
const pendingFinished = ref(false);
const pendingRefreshing = ref(false);
const pendingCount = ref(3);
const pendingCount = ref(0);
const pendingPage = ref(1);
// ========== 已评价列表状态 ==========
const evaluatedList = ref([]);
const evaluatedLoading = ref(false);
const evaluatedFinished = ref(false);
const evaluatedRefreshing = ref(false);
// ========== 生成假数据 ==========
const generatePendingMockData = () => {
return [
{
id: 1,
title: '深圳xx公司季度检查',
bureauDeptName: '深圳市司法局',
enforcementTime: '2026-03-23 09:00 ~ 12:00',
enterpriseName: '新粤云(深圳)信息技术有限公司',
enforcers: [
{ name: '唐晓唐', phone: '190 2345 1234' },
{ name: '宋晓峰', phone: '190 2345 1234' },
],
},
{
id: 2,
title: '深圳xx公司季度检查',
bureauDeptName: '深圳市司法局',
enforcementTime: '2026-03-23 09:00 ~ 12:00',
enterpriseName: '新粤云(深圳)信息技术有限公司',
enforcers: [
{ name: '唐晓唐', phone: '190 2345 1234' },
{ name: '宋晓峰', phone: '190 2345 1234' },
],
},
{
id: 3,
title: '深圳xx公司季度检查',
bureauDeptName: '深圳市司法局',
enforcementTime: '2026-03-23 09:00 ~ 12:00',
enterpriseName: '新粤云(深圳)信息技术有限公司',
enforcers: [
{ name: '唐晓唐', phone: '190 2345 1234' },
{ name: '宋晓峰', phone: '190 2345 1234' },
],
},
];
const evaluatedPage = ref(1);
// ========== 接口行数据 → 卡片渲染结构 ==========
const parseEnforcers = (item) => {
// 可能的字段:enforcers / lawEnforcers / enforcementPersonnel / enforcerList
const raw = item.enforcers || item.lawEnforcers || item.enforcementPersonnel
|| item.enforcerList || item.personList || [];
if (Array.isArray(raw)) {
return raw.map(p => {
if (typeof p === 'string') return { name: p, phone: '' };
return {
name: p.name || p.userName || p.realName || p.nickName || '',
phone: p.phone || p.mobile || p.tel || p.phoneNumber || ''
};
}).filter(p => p.name);
}
if (typeof raw === 'string' && raw) {
return raw.split(/[,,、]/).map(s => ({ name: s.trim(), phone: '' })).filter(p => p.name);
}
return [];
};
const generateEvaluatedMockData = () => {
return [
{
id: 101,
title: '年度安全生产检查',
bureauDeptName: '深圳市市场监管局',
enforcementTime: '2026-03-20 14:00 ~ 16:00',
enterpriseName: '新粤云(深圳)信息技术有限公司',
enforcers: [
{ name: '张小明', phone: '138 0013 8000' },
],
const mapEvaluateItem = (item) => ({
id: item.id || item.caseId || item.relId || '',
title: item.caseName || item.lawEnforcementItemText || item.title || item.itemName || '',
bureauDeptName: item.lawEnforcement || item.bureauDeptName || item.deptName || '',
enforcementTime: item.lawEnforcementTime || item.enforcementTime || item.enforceTime || item.createTime || '',
enterpriseName: item.enterpriseName || item.companyName || '',
// 执法人员双人字段直接透到列表项
lawEnforcer: item.lawEnforcer || '',
lawEnforcerId: item.lawEnforcerId || '',
lawEnforcerTwo: item.lawEnforcerTwo || '',
lawEnforcerIdTwo: item.lawEnforcerIdTwo || '',
enforcers: parseEnforcers(item),
raw: item
});
// ========== 统一调用分页接口 ==========
// hasEvaluate: 0=待评价, 1=已评价
const fetchList = (hasEvaluate, pageNum, onSuccess, onError) => {
queryEnforcementRegistrationPageList(
{ hasEvaluate, pageSize: PAGE_SIZE, pageNum },
(res) => {
let rows = [];
let total = 0;
if (res && Array.isArray(res.rows)) {
rows = res.rows;
total = Number(res.total) || 0;
} else if (res && res.data && Array.isArray(res.data.rows)) {
rows = res.data.rows;
total = Number(res.data.total) || 0;
} else if (Array.isArray(res)) {
rows = res;
total = rows.length;
}
onSuccess(rows.map(mapEvaluateItem), total);
},
];
() => {
onError && onError();
}
);
};
// ========== 加载待评价数据 ==========
const fetchPendingList = () => {
setTimeout(() => {
const rows = generatePendingMockData();
if (pendingRefreshing.value) {
pendingList.value = rows;
const isRefresh = pendingRefreshing.value;
const page = isRefresh ? 1 : pendingPage.value;
fetchList(0, page, (list, total) => {
if (isRefresh) {
pendingList.value = list;
pendingRefreshing.value = false;
pendingPage.value = 2;
} else {
pendingList.value = [...pendingList.value, ...rows];
pendingList.value = pendingList.value.concat(list);
pendingPage.value = page + 1;
}
pendingFinished.value = true;
pendingLoading.value = false;
pendingCount.value = pendingList.value.length;
}, 500);
pendingFinished.value = total > 0
? pendingList.value.length >= total
: list.length < PAGE_SIZE;
pendingCount.value = total || pendingList.value.length;
}, () => {
pendingLoading.value = false;
pendingRefreshing.value = false;
pendingFinished.value = true;
showToast('待评价列表加载失败');
});
};
// ========== 加载已评价数据 ==========
const fetchEvaluatedList = () => {
setTimeout(() => {
const rows = generateEvaluatedMockData();
if (evaluatedRefreshing.value) {
evaluatedList.value = rows;
const isRefresh = evaluatedRefreshing.value;
const page = isRefresh ? 1 : evaluatedPage.value;
fetchList(1, page, (list, total) => {
if (isRefresh) {
evaluatedList.value = list;
evaluatedRefreshing.value = false;
evaluatedPage.value = 2;
} else {
evaluatedList.value = [...evaluatedList.value, ...rows];
evaluatedList.value = evaluatedList.value.concat(list);
evaluatedPage.value = page + 1;
}
evaluatedFinished.value = true;
evaluatedLoading.value = false;
}, 500);
evaluatedFinished.value = total > 0
? evaluatedList.value.length >= total
: list.length < PAGE_SIZE;
}, () => {
evaluatedLoading.value = false;
evaluatedRefreshing.value = false;
evaluatedFinished.value = true;
showToast('已评价列表加载失败');
});
};
// ========== 触底加载 ==========
@ -229,12 +245,14 @@ const onEvaluatedLoad = () => {
const onPendingRefresh = () => {
pendingFinished.value = false;
pendingLoading.value = true;
pendingPage.value = 1;
fetchPendingList();
};
const onEvaluatedRefresh = () => {
evaluatedFinished.value = false;
evaluatedLoading.value = true;
evaluatedPage.value = 1;
fetchEvaluatedList();
};
@ -254,10 +272,11 @@ const onSearchClick = () => {
};
// ========== 跳转评价详情 ==========
const toEvaluateDetail = (item) => {
// hasEvaluate: 0=待评价(可提交),1=已评价(只读)
const toEvaluateDetail = (item, hasEvaluate) => {
router.push({
path: '/enterprise/enforcementEvaluateDetail',
query: { id: item.id },
query: { id: item.id, hasEvaluate },
});
};
</script>

291
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementEvaluateDetail.vue

@ -1,12 +1,7 @@
<template>
<div class="evaluate-detail-page">
<!-- 顶部导航栏 -->
<van-nav-bar
:title="detail.title || '执法评价'"
left-text="关闭"
left-arrow
@click-left="onClickLeft"
/>
<van-nav-bar :title="detail.title || '执法评价'" left-text="关闭" left-arrow @click-left="onClickLeft" />
<!-- 内容区域 -->
<div class="content-area">
@ -19,7 +14,7 @@
</div>
<div class="info-row">
<span class="info-label">统一社会信用代码</span>
<span class="info-value">{{ detail.creditCode }}</span>
<span class="info-value">{{ detail.enterpriseNumber }}</span>
</div>
</div>
@ -28,27 +23,30 @@
<div class="card-title">执法信息</div>
<div class="info-row">
<span class="info-label">执法单位</span>
<span class="info-value">{{ detail.bureauDeptName }}</span>
<span class="info-value">{{ detail.lawEnforcement }}</span>
</div>
<div class="info-row">
<span class="info-label">执法人员</span>
<div class="info-value enforcer-value">
<div v-for="(enforcer, idx) in detail.enforcers" :key="idx">
{{ enforcer.name }} {{ enforcer.phone }}
<div>
{{ detail.lawEnforcer }}{{ detail.lawEnforcerId }}
</div>
<div>
{{ detail.lawEnforcerTwo }}{{ detail.lawEnforcerIdTwo }}
</div>
</div>
</div>
<div class="info-row">
<span class="info-label">执法类别</span>
<span class="info-value">{{ detail.category }}</span>
<span class="info-value">{{ getDictText('law_enforce_category', detail.lawEnforceCategory) }}</span>
</div>
<div class="info-row">
<span class="info-label">任务来源</span>
<span class="info-value">{{ detail.source }}</span>
<span class="info-value">{{ getDictText('check_category', detail.checkCategory) }}</span>
</div>
<div class="info-row">
<span class="info-label">执法事项</span>
<span class="info-value">{{ detail.matter }}</span>
<span class="info-value">{{ detail.lawEnforcementItem }}</span>
</div>
<div class="info-row">
<span class="info-label">执法时间</span>
@ -57,20 +55,21 @@
<div class="info-row">
<span class="info-label">受委托执法</span>
<span class="info-value">
<van-radio-group v-model="detail.isEntrusted" direction="horizontal" disabled>
<van-radio :name="true">是</van-radio>
<van-radio :name="false">否</van-radio>
<van-radio-group v-model="detail.hasAuto" direction="horizontal" disabled>
<van-radio :name="1">是</van-radio>
<van-radio :name="0">否</van-radio>
</van-radio-group>
</span>
</div>
<div class="info-row">
<span class="info-label">委托行政机关</span>
<span class="info-value">{{ detail.entrustDept }}</span>
<span class="info-value">{{ detail.entrustDeptName }}</span>
</div>
<div class="info-row result-row">
<div class="info-row result-row" >
<span class="info-label">结果</span>
<span class="info-value">
<span class="result-tag" :class="detail.result === '合格' ? 'pass' : 'fail'">{{ detail.result }}</span>
<span class="result-tag" :class="detail.reslut === 0 ? 'pass' : 'fail'">{{ detail.reslut === 0 ? '合格' : '不合格'
}}</span>
<span class="view-detail-link" @click="onViewCaseDetail">查看案件详情 ></span>
</span>
</div>
@ -87,46 +86,28 @@
</div>
</div>
<!-- 评价卡片 -->
<div class="evaluate-card">
<!-- 评价卡片(已评价:仅当后端返回 enterpriseEvaluateInfoVo 有数据时显示;待评价:始终显示) -->
<div class="evaluate-card" v-if="showEvaluateCard">
<div class="card-title">评价</div>
<!-- 表情选择 -->
<div class="emoji-section">
<div
class="emoji-item"
:class="{ active: evaluateForm.satisfaction === '满意' }"
@click="evaluateForm.satisfaction = '满意'"
>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '满意' ? veryGoodImg : veryGoodDisImg"
alt="满意"
/>
<div class="emoji-item" :class="{ active: evaluateForm.satisfaction === '满意', disabled: readonly }"
@click="!readonly && (evaluateForm.satisfaction = '满意')">
<img class="emoji-img" :src="evaluateForm.satisfaction === '满意' ? veryGoodImg : veryGoodDisImg"
alt="满意" />
<span class="emoji-label">满意</span>
</div>
<div
class="emoji-item"
:class="{ active: evaluateForm.satisfaction === '较满意' }"
@click="evaluateForm.satisfaction = '较满意'"
>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '较满意' ? goodImg : goodDisImg"
alt="较满意"
/>
<div class="emoji-item" :class="{ active: evaluateForm.satisfaction === '较满意', disabled: readonly }"
@click="!readonly && (evaluateForm.satisfaction = '较满意')">
<img class="emoji-img" :src="evaluateForm.satisfaction === '较满意' ? goodImg : goodDisImg"
alt="较满意" />
<span class="emoji-label">较满意</span>
</div>
<div
class="emoji-item"
:class="{ active: evaluateForm.satisfaction === '不满意' }"
@click="evaluateForm.satisfaction = '不满意'"
>
<img
class="emoji-img"
:src="evaluateForm.satisfaction === '不满意' ? badImg : badDisImg"
alt="不满意"
/>
<div class="emoji-item" :class="{ active: evaluateForm.satisfaction === '不满意', disabled: readonly }"
@click="!readonly && (evaluateForm.satisfaction = '不满意')">
<img class="emoji-img" :src="evaluateForm.satisfaction === '不满意' ? badImg : badDisImg"
alt="不满意" />
<span class="emoji-label">不满意</span>
</div>
</div>
@ -135,22 +116,26 @@
<div class="rating-section">
<div class="rating-row">
<span class="rating-label">执法文明</span>
<van-rate v-model="evaluateForm.civility" :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
<van-rate v-model="evaluateForm.civility" :size="20" color="#ffd21e" void-icon="star"
void-color="#eee" :readonly="readonly" />
<span class="rating-text">{{ getRatingText(evaluateForm.civility) }}</span>
</div>
<div class="rating-row">
<span class="rating-label">执法效率</span>
<van-rate v-model="evaluateForm.efficiency" :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
<van-rate v-model="evaluateForm.efficiency" :size="20" color="#ffd21e" void-icon="star"
void-color="#eee" :readonly="readonly" />
<span class="rating-text">{{ getRatingText(evaluateForm.efficiency) }}</span>
</div>
<div class="rating-row">
<span class="rating-label">执法态度</span>
<van-rate v-model="evaluateForm.attitude" :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
<van-rate v-model="evaluateForm.attitude" :size="20" color="#ffd21e" void-icon="star"
void-color="#eee" :readonly="readonly" />
<span class="rating-text">{{ getRatingText(evaluateForm.attitude) }}</span>
</div>
<div class="rating-row">
<span class="rating-label">合 法 性</span>
<van-rate v-model="evaluateForm.legality" :size="20" color="#ffd21e" void-icon="star" void-color="#eee" />
<van-rate v-model="evaluateForm.legality" :size="20" color="#ffd21e" void-icon="star"
void-color="#eee" :readonly="readonly" />
<span class="rating-text">{{ getRatingText(evaluateForm.legality) }}</span>
</div>
</div>
@ -158,7 +143,8 @@
<!-- 其他问题开关 -->
<div class="switch-section">
<span class="switch-label">其他问题</span>
<van-switch v-model="evaluateForm.hasOtherIssues" size="20" active-color="#2b7de9" />
<van-switch v-model="evaluateForm.hasOtherIssues" size="20" active-color="#2b7de9"
:disabled="readonly" />
</div>
<!-- 问题列表 -->
@ -166,19 +152,13 @@
<div class="question-item" v-for="(q, idx) in questionList" :key="idx">
<div class="question-text">{{ idx + 1 }}、{{ q.text }}</div>
<div class="question-options">
<div
class="option-btn"
:class="{ active: q.answer === '是' }"
@click="q.answer = '是'"
>
<div class="option-btn" :class="{ active: q.answer === '是', disabled: readonly }"
@click="!readonly && (q.answer = '是')">
<van-icon name="success" v-if="q.answer === '是'" />
是
</div>
<div
class="option-btn"
:class="{ active: q.answer === '否' }"
@click="q.answer = '否'"
>
<div class="option-btn" :class="{ active: q.answer === '否', disabled: readonly }"
@click="!readonly && (q.answer = '否')">
<van-icon name="success" v-if="q.answer === '否'" />
否
</div>
@ -188,8 +168,8 @@
</div>
</div>
<!-- 底部提交按钮 -->
<div class="footer-bar">
<!-- 底部提交按钮(仅待评价状态显示) -->
<div class="footer-bar" v-if="!readonly">
<van-button type="primary" block round @click="onSubmit">提交评价</van-button>
</div>
</div>
@ -204,41 +184,64 @@ import goodImg from '@/assets/images/eval-good.png';
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';
import {
getEvaluateDetail,
getEnforcementRegistrationDetailById,
submitEvaluate,
} from '@/api/enterprise';
import { loadDict, getDictText } from '@/util/dictUtil';
const router = useRouter();
const route = useRoute();
const id = route.query.id;
// 只读标识:hasEvaluate=1 已评价只能查看,0/空 为待评价可提交
const readonly = computed(() => String(route.query.hasEvaluate) === '1');
// 已评价返回的评价信息对象(后端 enterpriseEvaluateInfoVo)
const evaluateInfo = ref(null);
// 评价卡片是否显示:待评价始终显示;已评价仅当 enterpriseEvaluateInfoVo 有数据时显示
const showEvaluateCard = computed(() => {
if (!readonly.value) return true;
const info = evaluateInfo.value;
return !!(info && typeof info === 'object' && Object.keys(info).length > 0);
});
// 预加载执法类别字典(用于翻译 detail.lawEnforceCategory)
const lawEnforceCategoryLoaded = ref(false);
loadDict('law_enforce_category', () => { lawEnforceCategoryLoaded.value = true; });
// 预加载任务来源字典(用于翻译 detail.checkCategory)
const checkCategoryLoaded = ref(false);
loadDict('check_category', () => { checkCategoryLoaded.value = true; });
// ========== 详情数据 ==========
const detail = ref({
title: 'xxx信息技术有限公司检查',
enterpriseName: 'xxx信息技术有限公司',
creditCode: '91440300769163989U',
bureauDeptName: '深圳市司法局',
enforcers: [
{ name: '刘新忠', phone: '19028763225' },
{ name: '安旭明', phone: '19028763225' },
],
category: '行政检查',
source: '日常巡查',
matter: 'xxx信息技术有限公司',
enforcementTime: '2026-03-23 09:00 ~ 12:00',
title: '',
enterpriseName: '',
creditCode: '',
bureauDeptName: '',
enforcers: [],
category: '',
source: '',
matter: '',
enforcementTime: '',
isEntrusted: false,
entrustDept: '深圳市发展和改革委员会',
result: '合格',
entrustDept: '',
result: '',
attachments: [],
});
// ========== 评价表单 ==========
// ========== 评价表单(默认值:满意 / 5 星 / 其他问题打开)==========
const evaluateForm = ref({
satisfaction: '满意',
civility: 5,
efficiency: 4,
attitude: 3,
efficiency: 5,
attitude: 5,
legality: 5,
hasOtherIssues: true,
});
// ========== 问题列表 ==========
// ========== 问题列表(默认模板)==========
const questionList = ref([
{ text: '是否存在和执法,接受被执法企业的馈赠、报酬、福利等?', answer: '否' },
{ text: '是否存在要求被执法企业提供宴请、娱乐、旅游等活动?', answer: '否' },
@ -247,9 +250,89 @@ const questionList = ref([
{ text: '是否存在变相执法,以咨询、培训、考核等名义行执法之实?', answer: '否' },
]);
// ========== 字段映射工具:对后端多种字段名容错 ==========
const pickFirst = (obj, keys, fallback = '') => {
if (!obj) return fallback;
for (const k of keys) {
const v = obj[k];
if (v !== undefined && v !== null && v !== '') return v;
}
return fallback;
};
// 解析执法人员
const parseEnforcers = (data) => {
const list = [];
if (data.lawEnforcer) list.push({ name: data.lawEnforcer, phone: data.lawEnforcerId || '' });
if (data.lawEnforcerTwo) list.push({ name: data.lawEnforcerTwo, phone: data.lawEnforcerIdTwo || '' });
const raw = data.enforcers || data.enforcerList || [];
if (Array.isArray(raw) && raw.length) {
raw.forEach(p => {
if (typeof p === 'string') list.push({ name: p, phone: '' });
else list.push({
name: p.name || p.userName || p.realName || '',
phone: p.phone || p.mobile || p.tel || ''
});
});
}
return list.filter(p => p.name);
};
// 获取执法详情
const fetchRegistrationDetail = () => {
if (!id) return;
getEnforcementRegistrationDetailById({ id }, (res) => {
const data = (res && (res.data || res.rows?.[0] || res)) || {};
detail.value = data.enforcementRegistrationVo
|| {};
// 保存企业评价信息(可能为 null),用于已评价识别评价卡片是否显示
evaluateInfo.value = data.enterpriseEvaluateInfoVo || null;
// 已评价:将后端评价数据回填到表单供只读展示
if (evaluateInfo.value) fillEvaluateFormFromInfo(evaluateInfo.value);
}, () => {
showToast('执法详情加载失败');
});
};
// satisfaction 文字 <-> 数字 映射:不满意=1、较满意=2、非常满意(满意)=3
const SATISFACTION_LIST = [
{ num: 1, text: '不满意' },
{ num: 2, text: '较满意' },
{ num: 3, text: '满意', alias: ['非常满意'] },
];
const SATISFACTION_TO_NUM = SATISFACTION_LIST.reduce((map, it) => {
map[it.text] = it.num;
(it.alias || []).forEach(a => { map[a] = it.num; });
return map;
}, {});
const SATISFACTION_TO_TEXT = SATISFACTION_LIST.reduce((map, it) => {
map[it.num] = it.text;
return map;
}, {});
// 将后端 enterpriseEvaluateInfoVo 回填到表单/问题列表
const fillEvaluateFormFromInfo = (info) => {
evaluateForm.value = {
satisfaction: SATISFACTION_TO_TEXT[Number(info.satisfaction)] || evaluateForm.value.satisfaction,
civility: Number(info.serviceExperience) || 0,
efficiency: Number(info.checkQuality) || 0,
attitude: Number(info.checkAttitude) || 0,
legality: Number(info.proceduralLegality) || 0,
hasOtherIssues: String(info.otherProblemSwitch) === '1',
};
questionList.value = questionList.value.map((q, idx) => ({
...q,
answer: String(info['problem' + (idx + 1)]) === '1' ? '是' : '否',
}));
};
onMounted(() => {
fetchRegistrationDetail();
});
// ========== 评分文字映射 ==========
const getRatingText = (score) => {
const map = { 1: '不满意', 2: '基本满意', 3: '满意', 4: '非常满意', 5: '非常满意' };
const map = { 1: '不满意', 2: '基本满意', 3: '满意', 4: '比较满意', 5: '非常满意' };
return map[score] || '';
};
@ -265,10 +348,30 @@ const onViewCaseDetail = () => {
// ========== 提交评价 ==========
const onSubmit = () => {
showToast('评价提交成功');
setTimeout(() => {
router.back();
}, 1500);
if (readonly.value) return;
// 按后端约定字段构造提交参数
const params = {
enforcementRegistrationId: id,
satisfaction: SATISFACTION_TO_NUM[evaluateForm.value.satisfaction] || 3,
serviceExperience: evaluateForm.value.civility, // 执法文明
checkAttitude: evaluateForm.value.attitude, // 执法态度
checkQuality: evaluateForm.value.efficiency, // 执法效率
proceduralLegality: evaluateForm.value.legality, // 合法性
otherProblemSwitch: evaluateForm.value.hasOtherIssues ? 1 : 0,
};
// 问题列表 problem1~problem5:是=1 否=0;关闭"其他问题"开关时全部传 0
questionList.value.forEach((q, idx) => {
const val = evaluateForm.value.hasOtherIssues ? (q.answer === '是' ? 1 : 0) : 0;
params['problem' + (idx + 1)] = val;
});
submitEvaluate(params, () => {
showToast('评价提交成功');
setTimeout(() => {
router.back();
}, 1500);
}, () => {
showToast('评价提交失败');
});
};
</script>
@ -538,4 +641,10 @@ const onSubmit = () => {
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
z-index: 10;
}
/* ===== 只读状态可点击元素设置不可交互光标 ===== */
.emoji-item.disabled,
.option-btn.disabled {
cursor: not-allowed;
}
</style>

2
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementNoticeList.vue

@ -19,7 +19,7 @@
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
finished-text=""
@load="onLoad"
:offset="50"
>

14
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/enforcementRegistrationList.vue

@ -36,7 +36,7 @@
<!-- 可滚动列表区域 -->
<div class="list-container">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad"
:offset="50">
<!-- 空状态 -->
<van-empty v-if="finished && list.length === 0" description="暂无数据" />
@ -133,7 +133,7 @@ const fetchList = () => {
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value,
enterpriseNumber: useCommon.enterpriseNumber,
// enterpriseNumber: useCommon.enterpriseNumber,
};
// 关键字搜索
if (searchText.value) {
@ -251,10 +251,10 @@ const onDateConfirm = (value) => {
onSearch();
};
// ========== 跳转详情 ==========
// ========== 跳转详情(与评价详情页保持一致)==========
const toDetailView = (id) => {
router.push({
path: '/enterprise/registrationDetail',
path: '/enterprise/enforcementEvaluateDetail',
query: { id }
});
};
@ -292,7 +292,7 @@ const toEvaluate = (id, checkItem) => {
flex-direction: row;
padding: 0.2rem 0.27rem 0;
gap: 0.15rem;
margin-bottom: 8px;
.search-btn {
flex: 1;
height: 0.7rem;
@ -323,7 +323,7 @@ const toEvaluate = (id, checkItem) => {
.card-item {
background: #fff;
border-radius: 0.2rem;
margin-top: 0.27rem;
margin-bottom: 0.27rem;
padding: 0.2rem 0.3rem;
position: relative;
}
@ -342,7 +342,7 @@ const toEvaluate = (id, checkItem) => {
color: #fff;
font-size: 0.32rem;
background: #1367fe;
padding: 10px;
padding: 6px;
border-radius: 5px;
}
}

194
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/enforcement/registrationDetail.vue

@ -8,41 +8,46 @@
<div class="card-title">执法信息</div>
<div class="info-row">
<span class="info-label">执法单位</span>
<span class="info-value">{{ detail.bureauDeptName }}</span>
<span class="info-value">{{ lawDetail.bureauDeptName }}</span>
</div>
<div class="info-row">
<span class="info-label">执法人员</span>
<div class="info-value-block">
<div v-for="(p, idx) in detail.personnel" :key="idx">{{ p }}</div>
<div>{{ lawDetail.lawEnforcer }},{{ lawDetail.lawEnforcerId }}</div>
<div>{{ lawDetail.lawEnforcerTwo }},{{ lawDetail.lawEnforcerIdTwo }}</div>
</div>
</div>
<div class="info-row">
<span class="info-label">执法类别</span>
<span class="info-value">{{ detail.lawEnforceCategoryText }}</span>
<span class="info-value">{{ lawDetail.lawEnforceCategoryText }}</span>
</div>
<div class="info-row">
<span class="info-label">任务来源</span>
<span class="info-value">{{ detail.taskSource }}</span>
<span class="info-value">{{ getDictText('check_category', lawDetail.checkCategory) }}</span>
</div>
<div class="info-row">
<span class="info-label">执法事项</span>
<span class="info-value">{{ detail.lawEnforcementItem }}</span>
<span class="info-value">{{ lawDetail.lawEnforcementItem }}</span>
</div>
<div class="info-row">
<span class="info-label">执法时间</span>
<span class="info-value">{{ detail.enforcementTime }}</span>
<span class="info-value">{{ lawDetail.enforcementTime }}</span>
</div>
<div class="info-row">
<span class="info-label">受委托执法</span>
<span class="info-value">{{ detail.entrusted }}</span>
<span class="info-value">{{ lawDetail.hasAuto === '1' ? '是' : '否' }}</span>
</div>
<div class="info-row">
<span class="info-label">委托行政机关</span>
<span class="info-value">{{ detail.entrustingOrgan }}</span>
<span class="info-value">{{ lawDetail.entrustDeptName }}</span>
</div>
<div class="info-row">
<span class="info-label">附件</span>
<span class="info-value">{{ detail.attachment }}</span>
<div class="info-value attachment-list">
<img v-for="(f, idx) in attachmentList" :key="idx" :src="f.url" class="attachment-img"
@click="previewImage(idx)" />
<span v-if="!attachmentList.length">-</span>
</div>
</div>
</div>
@ -51,47 +56,47 @@
<div class="card-title">案件信息</div>
<div class="info-row">
<span class="info-label">案件名称</span>
<span class="info-value">{{ detail.caseName }}</span>
<span class="info-value">{{ caseDetail.caseName }}</span>
</div>
<div class="info-row">
<span class="info-label">行政相对人类型</span>
<span class="info-value">{{ detail.counterpartType }}</span>
<span class="info-label">行政当事人类型</span>
<span class="info-value">{{ getDictText('partyType', caseDetail.counterpartType) }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查决定书名称</span>
<span class="info-value">{{ detail.decisionName }}</span>
<span class="info-value">{{ caseDetail.decisionDocumentName }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查决定书文号</span>
<span class="info-value">{{ detail.decisionNumber }}</span>
<span class="info-value">{{ caseDetail.decisionDocumentNumber }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查类型</span>
<span class="info-value">{{ detail.checkType }}</span>
<span class="info-value">{{ caseDetail.checkType }}</span>
</div>
<div class="info-row">
<span class="info-label">检查结果日期</span>
<span class="info-value">{{ detail.resultDate }}</span>
<span class="info-value">{{ caseDetail.resultDate }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查内容</span>
<span class="info-value">{{ detail.checkContent }}</span>
<span class="info-value">{{ caseDetail.checkContent }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查结果</span>
<span class="info-value">{{ detail.checkResult }}</span>
<span class="info-value">{{ caseDetail.checkResult }}</span>
</div>
<div class="info-row">
<span class="info-label">行政检查决定</span>
<span class="info-value">{{ detail.checkDecision }}</span>
<span class="info-value">{{ caseDetail.decisionDate }}</span>
</div>
<div class="info-row">
<span class="info-label">执法主体名称</span>
<span class="info-value">{{ detail.enforcementSubject }}</span>
<span class="info-value">{{ caseDetail.lawEnforcementSubjectName }}</span>
</div>
<div class="info-row">
<span class="info-label">备注</span>
<span class="info-value">{{ detail.remark }}</span>
<span class="info-value">{{ caseDetail.remark }}</span>
</div>
</div>
@ -100,47 +105,67 @@
<div class="card-title">案件信息</div>
<div class="info-row">
<span class="info-label">案件名称</span>
<span class="info-value">{{ detail.caseName }}</span>
<span class="info-value">{{ caseDetail.caseName }}</span>
</div>
<div class="info-row">
<span class="info-label">行政相对人类型</span>
<span class="info-value">{{ detail.counterpartType }}</span>
<span class="info-label">行政当事人类型</span>
<span class="info-value">{{ getDictText('partyType', caseDetail.partyType) }}</span>
</div>
<div class="info-row">
<span class="info-label">{{ category === '2' ? '行政处罚类型' : '行政强制措施类型' }}</span>
<span class="info-value">{{ detail.measureType }}</span>
<div class="info-row" v-if="category === '4'">
<span class="info-label">行政强制类型</span>
<span class="info-value">{{ caseDetail.penaltyCategory }}</span>
</div>
<div class="info-row">
<span class="info-label">决定文书名称</span>
<span class="info-value">{{ detail.decisionDocName }}</span>
<span class="info-value">{{ caseDetail.decisionDocumentName }}</span>
</div>
<div class="info-row">
<span class="info-label">决定文书号</span>
<span class="info-value">{{ detail.decisionDocNumber }}</span>
<span class="info-value">{{ caseDetail.decisionDocumentNumber }}</span>
</div>
<div class="info-row">
<span class="info-label">{{ category === '2' ? '行政处罚理由' : '行政强制措施理由' }}</span>
<span class="info-value">{{ detail.measureReason }}</span>
<span class="info-label">{{ category === '2' ? '违法事实' : '行政强制措施理由' }}</span>
<span class="info-value">{{ caseDetail.fact }}</span>
</div>
<div class="info-row">
<span class="info-label">{{ category === '2' ? '行政处罚依据' : '行政强制措施依据' }}</span>
<span class="info-value">{{ detail.measureBasis }}</span>
<span class="info-label">{{ category === '2' ? '处罚依据' : '行政强制措施依据' }}</span>
<span class="info-value">{{ caseDetail.penaltyBasis }}</span>
</div>
<div class="info-row">
<span class="info-label">{{ category === '2' ? '处罚内容' : '强制所行内容' }}</span>
<span class="info-value">{{ detail.measureContent }}</span>
<div class="info-row" v-if="category === '4'">
<span class="info-label">强制所示内容</span>
<span class="info-value">{{ caseDetail.penaltyBasis }}</span>
</div>
<div class="info-row">
<span class="info-label">执法主体名称</span>
<span class="info-value">{{ detail.enforcementSubject }}</span>
<div class="info-row" v-if="category === '2'">
<span class="info-label">处罚类型</span>
<span class="info-value">{{ getMultiDictText('law_enforce_category', caseDetail.penaltyCategory) }}</span>
</div>
<div class="info-row" v-if="category === '2'">
<span class="info-label">处罚内容</span>
<span class="info-value">{{ caseDetail.content }}</span>
</div>
<div class="info-row" v-if="category === '2'">
<span class="info-label">罚款金额(万元)</span>
<span class="info-value">{{ caseDetail.penaltyAmount }}</span>
</div>
<div class="info-row" v-if="category === '2'">
<span class="info-label">没收违法所得、没收非法财物的金额(万元)</span>
<span class="info-value">{{ caseDetail.collectionAmount }}</span>
</div>
<div class="info-row">
<span class="info-label">{{ category === '2' ? '处罚决定日期' : '强制措施决定日期' }}</span>
<span class="info-value">{{ detail.decisionDate }}</span>
<span class="info-value">{{ caseDetail.decisionDate }}</span>
</div>
<div class="info-row">
<span class="info-label">执法主体名称</span>
<span class="info-value">{{ caseDetail.lawEnforcementSubjectName }}</span>
</div>
<div class="info-row">
<span class="info-label">备注</span>
<span class="info-value">{{ detail.remark }}</span>
<span class="info-value">{{ caseDetail.remark }}</span>
</div>
</div>
</div>
@ -149,37 +174,81 @@
<script setup name="registrationDetail">
import { getLawDetail, getLawCaseDetail } from '@/api/enterprise';
import { showToast } from 'vant';
import { loadDict, getDictText } from '@/util/dictUtil';
import { fileUrl } from '@/config/env';
import { showToast, showImagePreview } from 'vant';
import 'vant/es/toast/style';
import 'vant/es/image-preview/style';
const router = useRouter();
const route = useRoute();
const id = route.query.id;
const lawEnforcementType = String(route.query.lawEnforcementType || '1');
const category = String(route.query.category || '1');
// 预加载任务来源字典(用于翻译 detail.taskSource)
const taskSourceLoaded = ref(false);
loadDict('check_category', () => { taskSourceLoaded.value = true; });
// 预加载行政当事人类型字典(用于翻译 detail.partyType)
const partyTypeLoaded = ref(false);
loadDict('party_type', () => { partyTypeLoaded.value = true; });
// 预加载处罚类型字典(用于翻译 caseDetail.penaltyCategory,支持多值逗号拼接)
const penaltyCategoryLoaded = ref(false);
loadDict('law_enforce_category', () => { penaltyCategoryLoaded.value = true; });
// 多值字典翽译:按 中文/英文逗号、顿号、分号拆分,逐个查字典,再用逗号拼接
const getMultiDictText = (dictType, val) => {
if (val === undefined || val === null || val === '') return '-';
const arr = String(val).split(/[,,、;;]/).map(s => s.trim()).filter(Boolean);
if (!arr.length) return '-';
return arr.map(v => getDictText(dictType, v)).join(',');
};
// 附件列表:用后端文件服务地址 fileUrl 拼 uploadPath,得到完整绝对 URL
const attachmentList = computed(() => {
const list = Array.isArray(lawDetail.value?.fileList) ? lawDetail.value.fileList : [];
const base = (fileUrl || '').replace(/\/+$/, '');
return list
.filter(f => f && f.uploadPath)
.map(f => {
const path = f.uploadPath;
if (/^https?:\/\//i.test(path)) return { url: path };
const p = path.startsWith('/') ? path : '/' + path;
return { url: base + p };
});
});
// 图片预览
const previewImage = (startPosition) => {
showImagePreview({ images: attachmentList.value.map(f => f.url), startPosition });
};
const pageTitle = computed(() => {
const map = { '1': '行政检查详情', '2': '行政处罚详情', '3': '行政强制详情' };
const map = { '1': '行政检查详情', '2': '行政处罚详情', '4': '行政强制详情' };
return map[category] || '执法详情';
});
// 执法类别文字映射
const getCategoryText = (val) => {
const map = { '1': '行政检查', '2': '行政处罚', '3': '行政强制' };
const map = { '1': '行政检查', '2': '行政处罚', '4': '行政强制' };
return map[String(val)] || '-';
};
// 详情数据(合并执法信息 + 案件信息)
const detail = ref({});
// 详情数据拆分为两个 ref,避免两个接口同名字段互相覆盖
const lawDetail = ref({}); // 执法信息
const caseDetail = ref({}); // 案件信息
// 获取执法信息详情
const fetchLawDetail = () => {
getLawDetail({ id }, (res) => {
if (res.code === 200) {
const data = res.data || res.rows?.[0] || {};
detail.value = { ...detail.value, ...data };
if (!detail.value.lawEnforceCategoryText) {
detail.value.lawEnforceCategoryText = getCategoryText(data.lawEnforcementType || category);
getLawDetail({ id, lawEnforcementType }, (res) => {
if (res) {
const data = res || res.rows?.[0] || {};
lawDetail.value = { ...data };
if (!lawDetail.value.lawEnforceCategoryText) {
lawDetail.value.lawEnforceCategoryText = getCategoryText(data.lawEnforcementType || category);
}
} else {
showToast(res.msg || '执法信息加载失败');
@ -191,10 +260,10 @@ const fetchLawDetail = () => {
// 获取执法案件信息详情
const fetchLawCaseDetail = () => {
getLawCaseDetail({ id }, (res) => {
if (res.code === 200) {
const data = res.data || res.rows?.[0] || {};
detail.value = { ...detail.value, ...data };
getLawCaseDetail({ id, lawEnforcementType }, (res) => {
if (res) {
const data = res || res.rows?.[0] || {};
caseDetail.value = { ...data };
} else {
showToast(res.msg || '案件信息加载失败');
}
@ -295,4 +364,17 @@ const onClickLeft = () => {
line-height: 1.6;
}
}
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 0.15rem;
.attachment-img {
width: 1.6rem;
height: 1.6rem;
border-radius: 0.1rem;
object-fit: cover;
}
}
</style>

53
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/home/enterpriseHomeV2.vue

@ -29,7 +29,7 @@
<!-- 执法监督码卡片 -->
<div class="supervision-card" @click="showEnterpriseQRCode">
<h3>执法监督码</h3>
<div class="card-title">执法监督码</div>
<div class="qr-icon-wrap">
<img src="@/assets/images/icon_jdm.jpg" class="qr-image" alt="" />
</div>
@ -39,7 +39,7 @@
<div class="card-row">
<!-- 历史执法信息 -->
<div class="info-card" @click="toEnforcementRegistrationListView">
<h4>历史执法信息</h4>
<div class="card-title">历史执法信息</div>
<div class="card-icon">
<img src="@/assets/images/icon_zfxx.jpg" class="card-icon-img" alt="" />
</div>
@ -47,7 +47,7 @@
<!-- 执法案件信息 -->
<div class="info-card info-enforce" @click="toEnforcementCaseInfoView">
<h4>执法案件信息</h4>
<div class="card-title">执法案件信息</div>
<div class="card-icon">
<img src="@/assets/images/icon_ajxx.jpg" class="card-icon-img" alt="" />
</div>
@ -352,8 +352,8 @@ onMounted(() => {
/* ===== 内容区域 ===== */
.content-area {
padding: 0 16px;
margin-top: 10px;
padding: 0 10px;
margin-top: 8px;
position: relative;
z-index: 2;
flex: 1;
@ -366,7 +366,7 @@ onMounted(() => {
background: #fffbe6;
border-radius: 10px;
padding: 8px 10px;
margin-bottom: 14px;
margin-bottom: 8px;
display: flex;
align-items: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
@ -439,15 +439,17 @@ onMounted(() => {
background: url('../../../assets/images/bg_jdm.jpg') center center no-repeat, linear-gradient(180deg, #7cc5fe 0%, #fff 100%);
background-size: 100% 100%;
border-radius: 16px;
padding: 18px 20px;
padding: 10px;
margin-bottom: 14px;
position: relative;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
h3 {
font-size: 14px;
color: #1a1a2e;
font-weight: 600;
height: 120px;
.card-title {
font-size: 16px;
color: #000;
// font-weight: 600;
margin-bottom: 6px;
margin-top: 6px;
}
@ -476,17 +478,17 @@ onMounted(() => {
.info-card {
flex: 1;
border-radius: 16px;
padding: 18px 16px;
padding: 8px 16px;
position: relative;
overflow: hidden;
min-height: 90px;
min-height: 100px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
background: url('../../../assets/images/bg_zfxx.png') center center no-repeat, linear-gradient(180deg, #bbecfd 0%, #fff 100%);
background: url('../../../assets/images/bg_zfxx.png') center center no-repeat, linear-gradient(180deg, #bbecfd 0%, #fff 100%);
background-size: 100% 100%;
h4 {
.card-title {
font-size: 14px;
color: #1a1a2e;
font-weight: 600;
color: #000;
margin-bottom: 4px;
margin-top: 4px;
position: relative;
@ -494,26 +496,29 @@ onMounted(() => {
}
.card-icon {
width: 52px;
height: 52px;
border-radius: 14px;
width: 60px;
height: 40px;
// border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
margin-top: 12px;
.card-icon-img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50px;
}
}
}
.info-enforce{
background: url('../../../assets/images/bg_ajxx.png') center center no-repeat, linear-gradient(180deg, #a6d1f9 0%, #fff 100%);
.info-enforce {
background: url('../../../assets/images/bg_ajxx.png') center center no-repeat, linear-gradient(180deg, #a6d1f9 0%, #fff 100%);
background-size: 100% 100%;
}
}
/* ===== 底部导航 ===== */
.footer_body {
flex-shrink: 0;

2
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/law/lawServiceList.vue

@ -19,7 +19,7 @@
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
finished-text=""
@load="onLoad"
:offset="50"
>

13
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintList.vue

@ -7,19 +7,19 @@
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
finished-text=""
@load="onLoad"
>
<!-- 列表项 -->
<div class="complaint-card" v-for="item in list" :key="item.id" @click="toDetail(item)">
<div class="card-time">投诉时间:{{ formatTime(item.enforcementTime || item.time) }}</div>
<div class="card-time">投诉时间:{{ formatTime(item.createTime || item.time) }}</div>
<div class="card-row">
<span class="card-label">执法单位</span>
<span class="card-value">{{ item.complaintDepartments }}</span>
</div>
<div class="card-row">
<span class="card-label">投诉类型</span>
<span class="card-value">{{ item.complaintType }}</span>
<span class="card-value">{{ getComplaintTypeText(item.enforceType) }}</span>
</div>
<div class="card-row">
<span class="card-label">投诉内容</span>
@ -44,6 +44,7 @@
<script setup name="complaintList">
import { getComplaintList } from '@/api/enterprise';
import { loadDict, getDictText } from '@/util/dictUtil';
import { showToast } from 'vant';
import 'vant/es/toast/style';
@ -56,6 +57,12 @@ const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = ref(10);
// ========== 投诉类型字典 ==========
const complaintTypeLoaded = ref(false);
loadDict('caseType', () => { complaintTypeLoaded.value = true; });
const getComplaintTypeText = (val) => getDictText('caseType', val);
// ========== 格式化时间 ==========
const formatTime = (val) => {
if (!val) return '-';

261
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/complaintSuggestion.vue

@ -28,7 +28,7 @@
<!-- 附件 -->
<div class="form-card">
<div class="content-label">附件<span class="sub-text">(不多于8张)</span></div>
<van-uploader v-model="form.images" multiple :max-count="8" class="uploader" />
<van-uploader v-model="form.images" multiple :max-count="8" :after-read="onAfterReadImage" class="uploader" />
</div>
<!-- 涉事地址 -->
@ -62,7 +62,7 @@
]" class="contact-field" required />
<!-- 是否授权 -->
<div class="authorize-bar">
<van-icon name="info" color="#2b7de9" size="18" />
<van-icon name="info" color="#2b7de9" size="32" />
<span class="authorize-text">是否授权</span>
<van-radio-group v-model="form.authorized" direction="horizontal">
<van-radio :name="true">
@ -99,7 +99,7 @@
<!-- 地址级联选择弹窗 -->
<van-popup v-model:show="showAddressPicker" position="bottom" :style="{ width: '100%', height: '60%' }">
<van-cascader v-model="selectedAddress" title="选择所在地区" :options="addressOptions"
@close="showAddressPicker = false" @finish="onAddressFinish" />
@close="showAddressPicker = false" @change="onAddressChange" @finish="onAddressFinish" />
</van-popup>
<!-- 投诉类型选择弹窗 -->
@ -107,14 +107,15 @@
@confirm="onComplaintTypeConfirm" />
<!-- 执法单位查询弹窗 -->
<BureauPicker v-model:show="showBureauPicker" :list="bureauList" @confirm="onBureauConfirm" />
<BureauPicker v-model:show="showBureauPicker" :list="bureauList" :loading="bureauLoading" @confirm="onBureauConfirm" />
</div>
</template>
<script setup name="complaintSuggestion">
import { showToast } from 'vant';
import 'vant/es/toast/style';
import { mockBureauList, mockComplaintTypeList, mockComplaintItemList, mockAddressOptions } from '@/mock/complaint';
import { queryDict, getDeptList, getComplaintItemList, addComplaint } from '@/api/enterprise';
import { uploadFile } from '@/util/fileUpload';
import BureauPicker from '@/components/complaint/BureauPicker.vue';
import ComplaintTypePicker from '@/components/complaint/ComplaintTypePicker.vue';
import ComplaintItemPicker from '@/components/complaint/ComplaintItemPicker.vue';
@ -123,9 +124,12 @@ const router = useRouter();
// ========== 表单数据 ==========
const form = ref({
bureauDept: '',
complaintType: '',
complaintItem: '',
bureauDept: '', // 执法单位名称
bureauDeptId: '', // 执法单位 id
complaintType: '', // 投诉类型显示文本
complaintTypeCode: '', // 投诉类型字典值(提交用)
complaintItem: '', // 投诉事项名称
complaintItemCode: '', // 投诉事项编码(提交用)
content: '',
images: [],
addressArea: '',
@ -139,29 +143,168 @@ const form = ref({
// ========== 执法单位弹窗数据 ==========
const showBureauPicker = ref(false);
const bureauList = ref(mockBureauList);
const bureauList = ref([]);
const bureauLoading = ref(false);
// 执法单位 sessionStorage 缓存 key(接口字段从旧接口迁移到 virtualUnit/list,同时 bump V2 以使旧缓存自动失效)
const DEPT_CACHE_KEY = 'DEPT_LIST_CACHE_V2';
// 加载执法单位列表(含缓存,关闭浏览器自动清除)
const loadBureauList = () => {
const cached = sessionStorage.getItem(DEPT_CACHE_KEY);
if (cached) {
try {
const parsed = JSON.parse(cached);
// 校验缓存数据结构是否含 virName 字段,否则丢弃缓存重拉
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0] && parsed[0].virName !== undefined) {
bureauList.value = parsed;
return;
}
} catch (e) { /* 解析失败走接口 */ }
}
bureauLoading.value = true;
getDeptList({}, (res) => {
let rows = [];
if (Array.isArray(res)) rows = res;
else if (res && Array.isArray(res.rows)) rows = res.rows;
else if (res && Array.isArray(res.data)) rows = res.data;
else if (res && res.data && Array.isArray(res.data.rows)) rows = res.data.rows;
bureauList.value = rows;
if (rows.length > 0) {
sessionStorage.setItem(DEPT_CACHE_KEY, JSON.stringify(rows));
}
bureauLoading.value = false;
}, () => {
bureauLoading.value = false;
showToast('执法单位加载失败');
});
};
// ========== 投诉类型弹窗数据 ==========
const showComplaintTypePicker = ref(false);
const complaintTypeList = ref(mockComplaintTypeList);
const complaintTypeList = ref([]);
// 加载投诉类型列表(通过公共字典工具)
import { loadDict } from '@/util/dictUtil';
const loadComplaintTypeList = () => {
loadDict('caseType', (columns) => {
complaintTypeList.value = columns;
}, () => {
showToast('投诉类型加载失败');
});
};
const onComplaintTypeConfirm = (value) => {
form.value.complaintType = value;
const onComplaintTypeConfirm = ({ text, value }) => {
form.value.complaintType = text;
form.value.complaintTypeCode = value;
};
// ========== 投诉事项弹窗数据 ==========
const showComplaintItemPicker = ref(false);
const complaintItemList = ref(mockComplaintItemList);
const complaintItemList = ref([]);
// 将接口返回的一行数据适配为弹窗渲染结构
// 接口 /app-ent/complaint/listNotComplaint 返回字段:
// id / relId / complaintItemCodes / complaintItemNames / complaintDepartments
// complaintDepartmentIds / enforcementTime / enterpriseName ...
const mapComplaintItem = (item) => {
return {
id: item.id || item.relId || '',
relId: item.relId || '',
code: item.complaintItemCodes || item.complaintItemCode || item.code || '',
title: item.complaintItemNames || item.complaintItemName || item.title || '',
time: item.enforcementTime || item.enforceTime || item.createTime || '',
unit: item.complaintDepartments || item.bureauName || item.deptName || '',
unitId: item.complaintDepartmentIds || '',
subName: item.enterpriseName || item.subName || '',
raw: item
};
};
// 加载投诉事项列表(不缓存,每次点击都重新拉取)
const loadComplaintItemList = () => {
getComplaintItemList({}, (res) => {
let rows = [];
if (Array.isArray(res)) rows = res;
else if (res && Array.isArray(res.rows)) rows = res.rows;
else if (res && Array.isArray(res.data)) rows = res.data;
else if (res && res.data && Array.isArray(res.data.rows)) rows = res.data.rows;
complaintItemList.value = rows.map(mapComplaintItem);
}, () => {
showToast('投诉事项加载失败');
});
};
const onComplaintItemSelect = (item) => {
form.value.complaintItem = item.title;
form.value.complaintItemCode = item.code || '';
};
// ========== 地址级联选择数据 ==========
const showAddressPicker = ref(false);
const selectedAddress = ref([]);
const selectedAddress = ref('');
const addressOptions = ref([]);
// sessionStorage 缓存 key(关闭浏览器 tab / 项目自动清除)
const DICT_CACHE_PREFIX = 'DICT_CACHE::';
// 统一从 sessionStorage 拿字典,没有再请求
const loadDictCached = (dictType) => {
return new Promise((resolve) => {
const cacheKey = DICT_CACHE_PREFIX + dictType;
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
try { resolve(JSON.parse(cached)); return; } catch (e) { /* 解析失败走接口 */ }
}
queryDict({ dictType: dictType }, (res) => {
let rows = [];
if (Array.isArray(res)) rows = res;
else if (res && Array.isArray(res.rows)) rows = res.rows;
else if (res && Array.isArray(res.data)) rows = res.data;
sessionStorage.setItem(cacheKey, JSON.stringify(rows));
resolve(rows);
}, () => {
showToast('字典加载失败');
resolve([]);
});
});
};
// 将字典 item 转换为 cascader 选项(兼容多种字段命名)
const dictToOption = (item, hasChildren = false) => {
const text = item.dictLabel || item.label || item.name || item.text || '';
const value = item.dictValue || item.value || item.code || '';
const opt = { text, value };
if (hasChildren) opt.children = [];
return opt;
};
// 加载区列表(可点击展开加载街道)
const loadAreas = async () => {
const rows = await loadDictCached('address_type');
addressOptions.value = rows.map(item => dictToOption(item, true));
};
const addressOptions = mockAddressOptions;
// 区→街道字典型映射【FT、LHu、YT、NS、BA、LG、LH、PS、GM、DP、SS】
const AREA_CODES = ['FT', 'LHu', 'YT', 'NS', 'BA', 'LG', 'LH', 'PS', 'GM', 'DP', 'SS'];
// cascader 选择变化事件:一级选中区后,拉取该区对应的街道
const onAddressChange = async ({ value, selectedOptions, tabIndex }) => {
if (tabIndex !== 0) return;
const current = selectedOptions[0];
if (!current) return;
// 未加载过子节点才拉;已加载过的跳过
if (current.children && current.children.length > 0) return;
// 标准化 dictType:优先用区 value;如果 value 不在约定列表里,依然按 value 请求(后端驱动)
const dictType = value;
const rows = await loadDictCached(dictType);
current.children = rows.map(item => dictToOption(item, false));
// cascader 需重新触发响应:赋新引用
addressOptions.value = [...addressOptions.value];
if (current.children.length === 0) {
showToast('该区暂无街道数据');
}
};
const onAddressFinish = ({ selectedOptions }) => {
const addressText = selectedOptions.map(option => option.text).join(' ');
@ -178,35 +321,107 @@ const onClickLeft = () => {
const showPicker = (field) => {
if (field === 'bureauDept') {
showBureauPicker.value = true;
// 未加载时调取执法单位(缓存会同步命中)
if (bureauList.value.length === 0) {
loadBureauList();
}
} else if (field === 'complaintType') {
showComplaintTypePicker.value = true;
// 未加载时调取投诉类型字典(缓存命中不重复调用)
if (complaintTypeList.value.length === 0) {
loadComplaintTypeList();
}
} else if (field === 'complaintItem') {
showComplaintItemPicker.value = true;
// 投诉事项不缓存,每次打开都刷新
loadComplaintItemList();
} else if (field === 'address') {
showAddressPicker.value = true;
// 未加载时触发区级字典请求(已有缓存不重复调用)
if (addressOptions.value.length === 0) {
loadAreas();
}
} else {
showToast(`${field}选择功能开发中`);
}
};
// ========== 执法单位确定 ==========
const onBureauConfirm = (value) => {
if (!value) return;
form.value.bureauDept = value;
const onBureauConfirm = (item) => {
if (!item) return;
// 兼容旧缓存或单一字符串场景
if (typeof item === 'string') {
form.value.bureauDept = item;
form.value.bureauDeptId = '';
} else {
form.value.bureauDept = item.virName || item.name || '';
form.value.bureauDeptId = item.virId || item.id || '';
}
};
// ========== 提交状态 ==========
const submitting = ref(false);
// ========== 附件上传 ==========
const onAfterReadImage = (file) => {
const files = Array.isArray(file) ? file : [file];
files.forEach(f => {
uploadFile(f, (res) => {
const data = res && res.data ? res.data : res;
// 兼容后端返回结构
f.url = (data && (data.url || data.fileUrl || data.path)) || f.url;
f.fileName = (data && (data.fileName || data.name || data.originalFilename)) || (f.file && f.file.name) || '';
});
});
};
// 将 form.images 转换为提交用的 fileList
const buildFileList = () => {
return (form.value.images || [])
.filter(img => img && (img.url || img.content))
.map(img => ({
url: img.url || '',
fileName: img.fileName || (img.file && img.file.name) || ''
}));
};
// ========== 提交表单 ==========
const onSubmit = () => {
if (submitting.value) return;
// 有未上传完的附件则拦截
const uploadingFile = (form.value.images || []).find(img => img && img.status === 'uploading');
if (uploadingFile) {
showToast('附件上传中,请稍后');
return;
}
const enterpriseAddress = ['深圳市', form.value.addressArea, form.value.addressDetail]
.filter(Boolean)
.join(' ');
const payload = {
enforceType: form.value.complaintTypeCode,
linkman: form.value.contactName,
linkmanTel: form.value.contactPhone,
problemDescription: form.value.content,
authorization: form.value.authorized ? 1 : 0,
enterpriseAddress,
fileList: buildFileList(),
complaintItemCodes: form.value.complaintItemCode || '',
complaintItemNames: form.value.complaintItem || '',
complaintDepartments: form.value.bureauDept || '',
complaintDepartmentIds: form.value.bureauDeptId || ''
};
submitting.value = true;
showToast('提交成功,我们将尽快处理');
setTimeout(() => {
addComplaint(payload, () => {
submitting.value = false;
router.back();
}, 1500);
showToast('提交成功,我们将尽快处理');
setTimeout(() => {
// 提交成功后直接回到我的页面,用 replace 清掉中间的投诉相关页面
router.replace({ name: 'myPage' });
}, 1200);
}, (err) => {
submitting.value = false;
showToast((err && err.msg) || '提交失败,请稍后重试');
});
};
// ========== 校验失败 ==========
@ -442,7 +657,7 @@ const onFailed = (errorInfo) => {
.authorize-text {
flex: 1;
font-size: 0.32rem;
font-size:18px;
color: #333;
}

4
zdxt-web-client/zdxt-efcode-enterprise-app/src/views/enterprise/my/myPage.vue

@ -71,7 +71,7 @@ const toSettings = () => {
.header-bg {
padding-top: 26px;
background: url('@/assets/images/bg_msg.jpg') center center no-repeat;
background-size: 100% 100%;
background-size: cover;
height: 270px;
}
@ -103,7 +103,7 @@ const toSettings = () => {
color: #000;
margin-bottom: 6px;
// text-shadow: 0 1px 4px rgba(0,0,0,0.15);
font: 0.6em sans-serif;
font-size: 20px;
}
.user-phone {

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

@ -2,5 +2,7 @@
VITE_PORT = 12001
# VITE_BASE_URL = '/api/ent-ep'
VITE_BASE_URL = '/api'
# 附件/图片等静态资源的后端地址(直接访问,不走 /api 代理)
VITE_FILE_URL = 'http://139.186.148.21:8080'
VITE_DEFAULT_LOGIN = '/enterprise/login'
VITE_APP_ENV = 'dev'

Loading…
Cancel
Save