Browse Source

执法端-计划

dev-V1.0.1
庄锐波 6 months ago
parent
commit
d0c7349637
  1. 1
      script/sql/zfjd-refactoring-v1/015-索引优化.sql
  2. 91
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java
  3. 4
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/BureauUnitController.java
  4. 39
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfLoginController.java
  5. 22
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/plan.js
  6. 18
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  7. 42
      zdxt-web-client/zdxt-efcode-enforcement-app/src/store/Common.js
  8. 22
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/login.vue
  9. 3
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue
  10. 437
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_edit.vue
  11. 134
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_list.vue
  12. 320
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue
  13. 21
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_other.vue

1
script/sql/zfjd-refactoring-v1/015-索引优化.sql

@ -0,0 +1 @@
CREATE INDEX idx_qymc_tyshxydm ON sszt_jbxx110497_new(QYMC, TYSHXYDM);

91
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/DataFilterUtil.java

@ -342,13 +342,24 @@ public class DataFilterUtil {
* @throws IllegalStateException 当 unitTypeCode=99 或递归查找失败时抛出
*/
public static String getUserOrganId(Long userId) {
SrzDeptVo organDept = getUserOrganDept(userId);
return organDept != null ? organDept.getOrganizationid() : null;
}
/**
* 查询指定用户所在的认证机构(unitTypeCode=10)对应的 SrzDeptVo 对象。
*
* @param userId 用户ID
* @return 认证机构对应的 SrzDeptVo;如果用户不存在或未找到则返回 null
*/
public static SrzDeptVo getUserOrganDept(Long userId) {
String deptId = getUserDeptId(userId);
if (deptId == null) {
return null;
}
SrzDeptMapper srzDeptMapper = SpringUtils.getBean(SrzDeptMapper.class);
return findOrganIdByDeptId(srzDeptMapper, deptId, 0);
return findOrganDeptByDeptId(srzDeptMapper, deptId, 0);
}
/**
@ -357,9 +368,9 @@ public class DataFilterUtil {
* @param srzDeptMapper Mapper
* @param organizationId 当前机构ID
* @param depth 递归深度(防歿循环)
* @return unitTypeCode=10 的 organizationId
* @return unitTypeCode=10 的 SrzDeptVo 对象
*/
private static String findOrganIdByDeptId(SrzDeptMapper srzDeptMapper, String organizationId, int depth) {
private static SrzDeptVo findOrganDeptByDeptId(SrzDeptMapper srzDeptMapper, String organizationId, int depth) {
if (StrUtil.isBlank(organizationId)) {
return null;
}
@ -380,11 +391,11 @@ public class DataFilterUtil {
"用户所在机构(organizationId=" + organizationId + ")的 unitTypeCode=99,不合法的机构类型");
}
if ("10".equals(unitTypeCode)) {
return srzDept.getOrganizationid();
return srzDept;
}
if ("11".equals(unitTypeCode)) {
// 递归查找父级
return findOrganIdByDeptId(srzDeptMapper, srzDept.getParentid(), depth + 1);
return findOrganDeptByDeptId(srzDeptMapper, srzDept.getParentid(), depth + 1);
}
// 其他未知的 unitTypeCode,返回 null
@ -392,21 +403,31 @@ public class DataFilterUtil {
}
public static String getUserDeptId(Long userId) {
SysDeptVo userSysDept = getUserSysDept(userId);
if (userSysDept == null || userSysDept.getDeptId() == null) {
return null;
}
return String.valueOf(userSysDept.getDeptId());
}
/**
* 根据 userId 查询用户的 UserDTO 对象。
*
* @param userId 用户ID
* @return UserDTO 对象;如果用户不存在则返回 null
*/
public static SysDeptVo getUserSysDept(Long userId) {
if (userId == null) {
return null;
}
// 1. 通过 userId 获取用户的 deptId
UserService userService = SpringUtils.getBean(UserService.class);
List<UserDTO> users = userService.selectListByIds(Collections.singletonList(userId));
if (CollUtil.isEmpty(users)) {
return null;
}
UserDTO user = users.get(0);
if (user.getDeptId() == null) {
return null;
}
String deptId = String.valueOf(user.getDeptId());
return deptId;
SysDeptVo userSysDept = new SysDeptVo();
userSysDept.setDeptId(String.valueOf(users.get(0).getDeptId()));
return userSysDept;
}
// ======================== 用户执法单位查询方法 ========================
@ -428,6 +449,28 @@ public class DataFilterUtil {
* @throws IllegalStateException 当某层匹配到多条 vir_show_at_business='是' 的记录,或多条匹配但无法确定唯一执法单位时抛出
*/
public static String getUserBureauDeptId(Long userId) {
SysDeptVo dept = getUserBureauDept(userId);
return dept != null ? dept.getDeptId() : null;
}
/**
* 查询指定用户所在的执法单位的 SysDeptVo 对象。
* <p>
* 逻辑:
* <ol>
* <li>通过 userId 查询用户的 dept_id</li>
* <li>用 dept_id 查找 efcode_virtual_unit(按 sys_dept_id 匹配)</li>
* <li>若存在匹配记录,优先返回 vir_show_at_business='是' 的记录对应的 SysDeptVo;
* 若无 business 记录且只有一条匹配也直接返回</li>
* <li>若不存在匹配记录,通过 sys_dept 查找父节点,再用父节点 dept_id 重复上述查找</li>
* <li>递归向上最多查 4 层父节点</li>
* </ol>
*
* @param userId 用户ID
* @return 用户所在执法单位的 SysDeptVo;如果用户不存在、未分配部门或向上 4 层仍未找到则返回 null
* @throws IllegalStateException 当某层匹配到多条 vir_show_at_business='是' 的记录,或多条匹配但无法确定唯一执法单位时抛出
*/
public static SysDeptVo getUserBureauDept(Long userId) {
if (userId == null) {
return null;
}
@ -458,22 +501,24 @@ public class DataFilterUtil {
.filter(v -> "是".equals(v.getVirShowAtBusiness()))
.collect(Collectors.toList());
String foundDeptId = null;
if (businessUnits.size() == 1) {
return businessUnits.get(0).getSysDeptId();
}
if (businessUnits.size() > 1) {
foundDeptId = businessUnits.get(0).getSysDeptId();
} else if (businessUnits.size() > 1) {
throw new IllegalStateException(
"用户(userId=" + userId + ")的部门(deptId=" + currentDeptId
+ ")在虚拟单位表中匹配到多条 vir_show_at_business='是' 的记录,无法确定唯一执法单位");
} else if (allMatched.size() == 1) {
// 没有 business 记录,如果只有一条匹配则返回
foundDeptId = allMatched.get(0).getSysDeptId();
} else {
// 多条匹配但无 business 标记,无法确定唯一执法单位
throw new IllegalStateException(
"用户(userId=" + userId + ")的部门(deptId=" + currentDeptId
+ ")在虚拟单位表中匹配到多条记录且无 vir_show_at_business='是',无法确定唯一执法单位");
}
// 没有 business 记录,如果只有一条匹配则返回
if (allMatched.size() == 1) {
return allMatched.get(0).getSysDeptId();
}
// 多条匹配但无 business 标记,无法确定唯一执法单位
throw new IllegalStateException(
"用户(userId=" + userId + ")的部门(deptId=" + currentDeptId
+ ")在虚拟单位表中匹配到多条记录且无 vir_show_at_business='是',无法确定唯一执法单位");
return sysDeptService.selectDeptById(foundDeptId);
}
// 不存在匹配记录,通过 sys_dept 查找父节点

4
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/BureauUnitController.java

@ -0,0 +1,4 @@
package com.zdxt.enforcementcode.controller;
public class BureauUnitController {
}

39
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfLoginController.java

@ -1,7 +1,9 @@
package com.zdxt.enforcementcode.controller.app.enforcement;
import cn.dev33.satoken.annotation.SaIgnore;
import com.zdxt.enforcementcode.common.util.DataFilterUtil;
import com.zdxt.enforcementcode.controller.app.LoginController;
import com.zdxt.enforcementcode.domain.vo.SrzDeptVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
@ -75,18 +77,30 @@ public class AppEnfLoginController extends LoginController {
}
// 7. 查询部门信息
String deptName = "";
if (user.getDeptId() != null && !user.getDeptId().isBlank()) {
SysDeptVo dept = sysDeptService.selectDeptById(user.getDeptId());
if (dept != null) {
deptName = dept.getDeptName();
}
}
SrzDeptVo srzOrgan = DataFilterUtil.getUserOrganDept(user.getUserId());
SysDeptVo sysDept = DataFilterUtil.getUserSysDept(user.getUserId());
SysDeptVo bureauDept = DataFilterUtil.getUserBureauDept(user.getUserId());
// 7. 构建LoginUser并生成SaToken
String token = doSaTokenLogin(user.getUserId(), user.getUserName(), user.getNickName(),
user.getDeptId(), "enforcement_user", "enforcement_app", "enforcement");
// 验证关键组织信息不能为空
if (srzOrgan == null) {
return R.fail("该用户未关联认证机构,无法登录执法端");
}
if (sysDept == null) {
return R.fail("该用户未关联部门,无法登录执法端");
}
if (bureauDept == null) {
return R.fail("该用户未关联执法单位,无法登录执法端");
}
if (!sysDept.getDeptId().equals(user.getDeptId())){
return R.fail("部门ID查找错误!");
}
// 8. 构造返回数据
Map<String, Object> data = new HashMap<>();
data.put("token", token);
@ -95,8 +109,15 @@ public class AppEnfLoginController extends LoginController {
data.put("loginName", user.getLoginName());
data.put("nickName", user.getNickName());
data.put("phonenumber", user.getPhonenumber() != null ? user.getPhonenumber() : "");
data.put("deptId", user.getDeptId() != null ? user.getDeptId() : "");
data.put("deptName", deptName);
data.put("deptId", user.getDeptId());
data.put("deptName", sysDept.getDeptName());
data.put("organId", srzOrgan.getOrganizationid());
data.put("organName", srzOrgan.getName());
data.put("bureauUnitId", bureauDept.getDeptId());
data.put("bureauUnitName", bureauDept.getDeptId());
return R.ok(data);
}

22
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/plan.js

@ -0,0 +1,22 @@
import { _get, _post, _put, _delete } from "../../http/index";
// region 计划管理接口
const planBaseUrl = "/app-enf/cpPlan";
// 计划列表(分页)
export const getPlanList = (params, success, error) => _get(planBaseUrl + "/list", params, success, error);
// 计划详情
export const getPlanDetail = (id, success, error) => _get(planBaseUrl + "/" + id, {}, success, error);
// 新增计划
export const addPlan = (params, success, error) => _post(planBaseUrl, params, success, error);
// 编辑计划
export const editPlan = (params, success, error) => _put(planBaseUrl, params, success, error);
// 删除计划
export const deletePlan = (ids, success, error) => _delete(planBaseUrl + "/" + ids, {}, success, error);
// endregion

18
zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js

@ -63,6 +63,24 @@ const routes = [
isKeepAlive: true,
},
},
// 执法端-计划列表
{
path: "/enforcement/plan",
name: "cpPlanList",
component: () => import("@/views/enforcement/my/plan/cp_plan_list.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端-计划编辑(新增/编辑)
{
path: "/enforcement/plan/edit",
name: "cpPlanEdit",
component: () => import("@/views/enforcement/my/plan/cp_plan_edit.vue"),
meta: {
isKeepAlive: false,
},
},
];
export {routes};

42
zdxt-web-client/zdxt-efcode-enforcement-app/src/store/Common.js

@ -12,8 +12,18 @@ export default defineStore('useCommon', {
enterpriseId:"",
//当前登录用户ID
userId:"",
//执法主体
orgId:"",
//部门ID
deptId:"",
//部门名称
deptName:"",
//认证机构ID
organId:"",
//认证机构名称
organName:"",
//执法单位ID
bureauUnitId:"",
//执法单位名称
bureauUnitName:"",
// token
token: "",
//当前登录用户的手机号
@ -53,9 +63,29 @@ export default defineStore('useCommon', {
SET_USER_ID(data){
this.userId = data;
},
//设置执法主体ID
SET_ORG_ID(data){
this.orgId = data;
//设置部门ID
SET_DEPT_ID(data){
this.deptId = data;
},
//设置部门名称
SET_DEPT_NAME(data){
this.deptName = data;
},
//设置认证机构ID
SET_ORGAN_ID(data){
this.organId = data;
},
//设置认证机构名称
SET_ORGAN_NAME(data){
this.organName = data;
},
//设置执法单位ID
SET_BUREAU_UNIT_ID(data){
this.bureauUnitId = data;
},
//设置执法单位名称
SET_BUREAU_UNIT_NAME(data){
this.bureauUnitName = data;
},
//设置用户token
SET_TOKEN(data){
@ -115,6 +145,6 @@ export default defineStore('useCommon', {
},
persist: {
storage: sessionStorage,
pick: ['token', 'userId', 'enterpriseNumber', 'enterpriseName', 'enterpriseId', 'phonenumber', 'orgId', 'hasLaw'],
pick: ['token', 'userId', 'enterpriseNumber', 'enterpriseName', 'enterpriseId', 'phonenumber', 'deptId', 'deptName', 'organId', 'organName', 'bureauUnitId', 'bureauUnitName', 'hasLaw'],
},
});

22
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/login.vue

@ -73,11 +73,29 @@
const encryptedKey = Encrypt(secretKey);
enforcementLogin({"secretName": secretName, "secretKey": encryptedKey}, response => {
if (response.code === 200) {
// 验证关键组织信息
if (!response.data.deptId) {
showFailToast("该用户未关联部门,无法登录");
return;
}
if (!response.data.organId) {
showFailToast("该用户未关联认证机构,无法登录");
return;
}
if (!response.data.bureauUnitId) {
showFailToast("该用户未关联执法单位,无法登录");
return;
}
// 执法端登录成功 → 赋值用户基本信息
useCommon.SET_USER_PHONENUMBER(response.data.phonenumber.toString());
useCommon.SET_USER_PHONENUMBER(response.data.phonenumber || "");
useCommon.SET_USER_ID(response.data.userId);
useCommon.SET_TOKEN(response.data.token);
useCommon.SET_ORG_ID(response.data.deptId || "");
useCommon.SET_DEPT_ID(response.data.deptId);
useCommon.SET_DEPT_NAME(response.data.deptName || "");
useCommon.SET_ORGAN_ID(response.data.organId);
useCommon.SET_ORGAN_NAME(response.data.organName || "");
useCommon.SET_BUREAU_UNIT_ID(response.data.bureauUnitId);
useCommon.SET_BUREAU_UNIT_NAME(response.data.bureauUnitName || "");
console.log("enforcement login success", useCommon.userId);
router.push({name: "enforcementHome"});
} else {

3
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue

@ -128,8 +128,7 @@ const onMenuClick = (item) => {
});
break;
case 'plan':
// TODO: 对接执法计划列表页
showToast('执法计划功能开发中');
router.push({ path: '/enforcement/plan' });
break;
case 'setting':
// TODO: 对接设置页

437
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_edit.vue

@ -0,0 +1,437 @@
<template>
<div class="plan-edit-page app_content">
<!-- 顶部导航 -->
<van-nav-bar
:title="isEdit ? '编辑计划' : '新增计划'"
left-arrow
@click-left="onBack"
/>
<div class="content">
<!-- 提示条 -->
<van-notice-bar
v-if="showNotice"
left-icon="info-o"
text="请各执法单位,于本月25日前提交次月的计划"
:scrollable="false"
background="#fff9dc"
color="#7d661c"
class="notice-bar"
closeable
@close="showNotice = false"
/>
<!-- 表单 -->
<div class="form-card">
<!-- 计划类别 -->
<van-field name="planType" label="计划类别" required>
<template #input>
<van-radio-group v-model="form.planType" direction="horizontal">
<van-radio :name="1">日常计划</van-radio>
<van-radio :name="2">专项计划</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 计划时间 -->
<van-field
v-model="planDateDisplay"
label="计划时间"
placeholder="请选择时间"
required
readonly
is-link
@click="showDatePicker = true"
/>
<!-- 执行时段 -->
<van-field name="planTime" label="执行时段" required>
<template #input>
<van-radio-group v-model="form.planTime" direction="horizontal">
<van-radio :name="1">上午</van-radio>
<van-radio :name="2">下午</van-radio>
<van-radio :name="3">其他</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 执法事项 -->
<van-field
v-model="form.enforceContent"
label="执法事项"
type="textarea"
placeholder="请输入执法事项"
required
:maxlength="1000"
show-word-limit
rows="3"
autosize
/>
<!-- 执法单位 -->
<van-field
v-model="form.bureauDeptName"
label="执法单位"
required
readonly
/>
<!-- 企业名称 -->
<van-field
v-model="form.enterpriseName"
label="企业名称"
placeholder="请输入执法企业"
required
/>
<!-- 执法人员 -->
<van-field label="执法人员" required>
<template #input>
<div class="person-box">
<div
v-for="(person, index) in personList"
:key="index"
class="person-line"
>
<span class="person-name">{{ person.name }}</span>
<span class="person-phone">{{ person.phone }}</span>
<van-icon
v-if="personList.length > 1"
name="close"
class="person-remove"
@click="removePerson(index)"
/>
</div>
<div class="add-person-line" @click="onAddPerson">
<span class="placeholder-text">请输入</span>
<van-icon name="add-o" color="#2382cf" size="20" />
</div>
</div>
</template>
</van-field>
<!-- 是否公开 -->
<van-field name="isPublic" label="是否公开" required>
<template #input>
<van-radio-group v-model="form.isPublic" direction="horizontal">
<van-radio :name="1">是</van-radio>
<van-radio :name="0">否</van-radio>
</van-radio-group>
</template>
</van-field>
<!-- 参与联合 -->
<van-field name="isUnion" label="参与联合" required>
<template #input>
<van-radio-group v-model="form.isUnion" direction="horizontal">
<van-radio :name="1">是</van-radio>
<van-radio :name="0">否</van-radio>
</van-radio-group>
</template>
</van-field>
</div>
<!-- 提交按钮 -->
<div class="submit-wrap">
<van-button
type="primary"
block
round
:loading="submitting"
loading-text="提交中..."
@click="onSubmit"
>
提交
</van-button>
</div>
</div>
<!-- 日期选择器 -->
<van-popup v-model:show="showDatePicker" position="bottom" round>
<van-date-picker
v-model="datePickerValue"
title="选择计划时间"
:min-date="minDate"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
/>
</van-popup>
</div>
</template>
<script setup name="cpPlanEdit">
import { useRouter, useRoute } from 'vue-router';
import { showToast } from 'vant';
import { addPlan, editPlan, getPlanDetail } from '@/api/enforcement/plan';
const router = useRouter();
const route = useRoute();
const isEdit = computed(() => !!route.query.id);
const showNotice = ref(true);
const submitting = ref(false);
const showDatePicker = ref(false);
const minDate = new Date();
// 表单数据
const form = ref({
planType: 1,
planDate: '',
planTime: 1,
planActualDate: '',
planActualTime: 1,
enforceContent: '',
bureauDeptId: '',
bureauDeptName: '',
enterpriseName: '',
enterpriseNumber: '',
enterpriseId: '',
enforceUserNames: '',
enforceUserIds: '',
enforceUserCodes: '',
isPublic: 1,
isUnion: 0,
reserveStatus: 0,
systemReserved: 0,
manualReserved: 0,
editEnable: 1,
organId: '',
organName: '',
deptId: '',
deptName: '',
isDelete: 0,
});
// 人员列表
const personList = ref([]);
// 日期选择值
const datePickerValue = ref([
String(new Date().getFullYear()),
String(new Date().getMonth() + 1).padStart(2, '0'),
String(new Date().getDate()).padStart(2, '0'),
]);
// 日期显示文本
const planDateDisplay = computed(() => {
if (!form.value.planDate) return '';
const timeText = { 1: '上午', 2: '下午', 3: '其他' };
return form.value.planDate + ' ' + (timeText[form.value.planTime] || '');
});
// 如果是编辑模式,加载详情
onMounted(() => {
if (isEdit.value) {
loadDetail(route.query.id);
}
});
const loadDetail = (id) => {
getPlanDetail(id, (res) => {
if (res.code === 200 && res.data) {
const data = res.data;
Object.keys(form.value).forEach(key => {
if (data[key] !== undefined && data[key] !== null) {
form.value[key] = data[key];
}
});
// 解析人员
if (data.enforceUserNames) {
const names = data.enforceUserNames.split(',');
personList.value = names.map(name => ({ name, phone: '' }));
}
}
});
};
// 日期确认
const onDateConfirm = ({ selectedValues }) => {
form.value.planDate = selectedValues.join('-');
form.value.planActualDate = form.value.planDate;
showDatePicker.value = false;
};
// 添加人员(暂时简单实现)
const onAddPerson = () => {
// TODO: 后续对接人员选择器
showToast('人员选择功能开发中');
};
// 移除人员
const removePerson = (index) => {
personList.value.splice(index, 1);
syncPersonData();
};
// 同步人员数据到表单
const syncPersonData = () => {
form.value.enforceUserNames = personList.value.map(p => p.name).join(',');
form.value.enforceUserIds = personList.value.map(p => p.id || '').join(',');
form.value.enforceUserCodes = personList.value.map(p => p.code || '').join(',');
};
// 返回
const onBack = () => {
router.back();
};
// 表单校验
const validate = () => {
if (!form.value.planDate) {
showToast('请选择计划时间');
return false;
}
if (!form.value.enforceContent) {
showToast('请输入执法事项');
return false;
}
if (!form.value.enterpriseName) {
showToast('请输入企业名称');
return false;
}
return true;
};
// 提交
const onSubmit = () => {
if (!validate()) return;
submitting.value = true;
form.value.planActualTime = form.value.planTime;
const api = isEdit.value ? editPlan : addPlan;
api(
form.value,
(res) => {
submitting.value = false;
if (res.code === 200) {
showToast(isEdit.value ? '修改成功' : '新增成功');
router.back();
} else {
showToast(res.msg || '操作失败');
}
},
() => {
submitting.value = false;
showToast('网络错误,请重试');
}
);
};
</script>
<style lang="less" scoped>
.plan-edit-page {
display: flex;
flex-direction: column;
height: 100%;
background: #eaf4ff;
overflow: hidden;
}
:deep(.van-nav-bar) {
background: #347bc3;
flex-shrink: 0;
.van-nav-bar__title {
color: #fff;
font-size: 18px;
font-weight: 600;
}
.van-icon {
color: #fff;
}
}
.content {
flex: 1;
overflow-y: auto;
padding: 12px;
}
.notice-bar {
border-radius: 8px;
margin-bottom: 12px;
border: 1px solid #f3dfa5;
}
.form-card {
background: #fff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 4px 14px rgba(44, 116, 190, 0.08);
:deep(.van-field) {
padding: 14px;
}
:deep(.van-field__label) {
width: 80px;
font-size: 15px;
color: #333;
}
:deep(.van-radio-group--horizontal) {
gap: 24px;
}
:deep(.van-radio__label) {
font-size: 15px;
}
}
.person-box {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.person-line {
display: flex;
align-items: center;
font-size: 15px;
color: #666;
gap: 8px;
.person-name {
margin-right: 4px;
}
.person-phone {
color: #999;
}
.person-remove {
margin-left: auto;
color: #999;
font-size: 16px;
}
}
.add-person-line {
display: flex;
align-items: center;
justify-content: space-between;
color: #c4c9cf;
font-size: 15px;
cursor: pointer;
.placeholder-text {
color: #c4c9cf;
}
}
.submit-wrap {
padding: 22px 0 0;
:deep(.van-button) {
height: 50px;
font-size: 17px;
font-weight: 500;
background: linear-gradient(135deg, #1890ff, #0b63d1);
border: none;
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.28);
}
}
</style>

134
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_list.vue

@ -0,0 +1,134 @@
<template>
<div class="plan-page app_content">
<!-- 顶部导航 -->
<van-nav-bar
title="计划"
left-arrow
@click-left="onBack"
/>
<!-- Tab 切换 -->
<van-tabs
v-model:active="activeTab"
title-active-color="#111"
title-inactive-color="#666"
color="#2f8bd3"
line-width="36px"
line-height="2px"
:border="false"
>
<van-tab title="我的计划" name="mine">
<cp-plan-mine @edit="onEdit" />
</van-tab>
<van-tab title="其他计划" name="other">
<cp-plan-other />
</van-tab>
</van-tabs>
<!-- 悬浮新增按钮 -->
<div class="add-btn" @click="onAdd">
<van-icon name="plus" size="22" color="#fff" />
</div>
</div>
</template>
<script setup name="cpPlanList">
import { useRouter } from 'vue-router';
import CpPlanMine from './cp_plan_mine.vue';
import CpPlanOther from './cp_plan_other.vue';
const router = useRouter();
const activeTab = ref('mine');
const onBack = () => {
router.back();
};
const onAdd = () => {
router.push({ path: '/enforcement/plan/edit' });
};
const onEdit = (item) => {
router.push({ path: '/enforcement/plan/edit', query: { id: item.id } });
};
</script>
<style lang="less" scoped>
.plan-page {
display: flex;
flex-direction: column;
height: 100%;
background: #eaf4ff;
position: relative;
overflow: hidden;
}
:deep(.van-nav-bar) {
background: #347bc3;
flex-shrink: 0;
.van-nav-bar__title {
color: #fff;
font-size: 18px;
font-weight: 600;
}
.van-icon {
color: #fff;
}
}
:deep(.van-tabs) {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
:deep(.van-tabs__wrap) {
height: 54px;
background: #fff;
border-bottom: 1px solid #e4edf7;
flex-shrink: 0;
}
:deep(.van-tab) {
font-size: 16px;
}
:deep(.van-tab--active) {
font-weight: 600;
}
:deep(.van-tabs__content),
:deep(.van-tabs__track),
:deep(.van-tab__panel-wrapper),
:deep(.van-tab__pane) {
flex: 1;
min-height: 0;
height: 100%;
}
:deep(.van-tabs__content) {
overflow: hidden;
}
.add-btn {
position: fixed;
right: 22px;
bottom: 28px;
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, #1890ff, #0b63d1);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.38);
z-index: 100;
&:active {
transform: scale(0.96);
}
}
</style>

320
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_mine.vue

@ -0,0 +1,320 @@
<template>
<div class="plan-mine">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="plan-list">
<!-- 第一条高亮卡片 -->
<section
v-for="(item, index) in planList"
:key="item.id"
class="plan-card"
:class="index === 0 ? 'primary-card' : 'normal-card'"
>
<div class="card-header">
<div class="card-type">{{ item.isUnion === 1 ? '联合执法' : '普通执法' }}</div>
<div class="status" :class="index === 0 ? '' : 'blue'">
{{ getStatusText(item.reserveStatus) }}
</div>
</div>
<div class="plan-title">{{ item.enforceContent }}</div>
<div class="info-row">
<div class="label" :class="index !== 0 ? 'dark' : ''">执法单位:</div>
<div class="value" :class="index !== 0 ? 'gray' : ''">{{ item.bureauDeptName }}</div>
</div>
<div class="info-row">
<div class="label" :class="index !== 0 ? 'dark' : ''">执法人员:</div>
<div class="value" :class="[index !== 0 ? 'gray' : '', 'multi']">
<span v-for="(name, i) in getPersonList(item)" :key="i">{{ name }}</span>
</div>
</div>
<div class="info-row">
<div class="label" :class="index !== 0 ? 'dark' : ''">计划时间:</div>
<div class="value" :class="index !== 0 ? 'gray' : ''">
{{ item.planDate }} {{ getPlanTimeText(item.planTime) }}
</div>
</div>
<div class="info-row">
<div class="label" :class="index !== 0 ? 'dark' : ''">企业名称:</div>
<div class="value" :class="index !== 0 ? 'gray' : ''">{{ item.enterpriseName }}</div>
</div>
<div class="card-actions" :class="index === 0 ? 'primary-actions' : ''">
<div
class="action-item"
:class="index !== 0 ? 'blue-action' : ''"
@click="onEditPlan(item)"
>
<van-icon name="edit" />
<span>编辑</span>
</div>
<div
class="action-item"
:class="index !== 0 ? 'blue-action' : ''"
@click="onDeletePlan(item)"
>
<van-icon name="delete-o" />
<span>删除</span>
</div>
</div>
</section>
</div>
<!-- 空状态 -->
<van-empty v-if="!loading && planList.length === 0" description="暂无计划" />
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup name="cpPlanMine">
import { showConfirmDialog, showToast } from 'vant';
import { getPlanList, deletePlan } from '@/api/enforcement/plan';
const emit = defineEmits(['edit']);
const planList = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = 10;
// 状态文本映射
const getStatusText = (status) => {
const map = { 0: '草稿', 1: '待预约', 2: '预约中', 3: '预约完成' };
return map[status] || '待确认';
};
// 时段文本
const getPlanTimeText = (time) => {
const map = { 1: '上午', 2: '下午', 3: '其他' };
return map[time] || '';
};
// 获取人员列表
const getPersonList = (item) => {
if (!item.enforceUserNames) return [];
return item.enforceUserNames.split(',');
};
// 加载数据
const onLoad = () => {
fetchList();
};
// 下拉刷新
const onRefresh = () => {
pageNum.value = 1;
finished.value = false;
planList.value = [];
fetchList();
};
// 请求列表
const fetchList = () => {
loading.value = true;
getPlanList(
{ pageNum: pageNum.value, pageSize },
(res) => {
refreshing.value = false;
loading.value = false;
if (res.code === 200) {
const rows = res.rows || [];
if (pageNum.value === 1) {
planList.value = rows;
} else {
planList.value.push(...rows);
}
if (rows.length < pageSize) {
finished.value = true;
} else {
pageNum.value++;
}
} else {
finished.value = true;
}
},
() => {
refreshing.value = false;
loading.value = false;
finished.value = true;
}
);
};
// 编辑
const onEditPlan = (item) => {
emit('edit', item);
};
// 删除
const onDeletePlan = (item) => {
showConfirmDialog({
title: '提示',
message: '确定要删除该计划吗?',
}).then(() => {
deletePlan(item.id, (res) => {
if (res.code === 200) {
showToast('删除成功');
onRefresh();
} else {
showToast(res.msg || '删除失败');
}
});
}).catch(() => {});
};
</script>
<style lang="less" scoped>
.plan-mine {
height: 100%;
overflow-y: auto;
padding: 12px;
box-sizing: border-box;
}
.plan-list {
display: flex;
flex-direction: column;
gap: 14px;
}
/* 卡片通用 */
.plan-card {
border-radius: 14px;
padding: 16px;
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.card-type {
font-size: 17px;
font-weight: 500;
}
.status {
font-size: 14px;
color: #f5c189;
&.blue {
color: #1f75c9;
}
}
.plan-title {
display: block;
font-size: 17px;
font-weight: 700;
line-height: 1.5;
margin-bottom: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 8px;
font-size: 14px;
line-height: 1.5;
}
.label {
width: 72px;
flex-shrink: 0;
color: rgba(255, 255, 255, 0.95);
font-weight: 500;
&.dark {
color: #333;
}
}
.value {
flex: 1;
color: rgba(255, 255, 255, 0.72);
&.gray {
color: #8a8a8a;
}
&.multi {
display: flex;
flex-direction: column;
gap: 4px;
}
}
/* 蓝色高亮卡片 */
.primary-card {
background: linear-gradient(135deg, #0d56c8 0%, #278fe0 100%);
color: #fff;
box-shadow: 0 6px 16px rgba(28, 108, 202, 0.25);
.card-type {
color: rgba(255, 255, 255, 0.82);
}
.plan-title {
color: #fff;
}
}
/* 普通卡片 */
.normal-card {
background: #fff;
box-shadow: 0 4px 14px rgba(44, 116, 190, 0.08);
.card-type {
color: #333;
}
.plan-title {
color: #111;
}
}
/* 操作区域 */
.card-actions {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
display: flex;
justify-content: flex-end;
gap: 24px;
&.primary-actions {
border-top-color: rgba(255, 255, 255, 0.18);
}
}
.action-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: rgba(255, 255, 255, 0.88);
cursor: pointer;
&.blue-action {
color: #2382cf;
}
}
</style>

21
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/plan/cp_plan_other.vue

@ -0,0 +1,21 @@
<template>
<div class="plan-other">
<van-empty
image="search"
description="其他计划功能开发中"
/>
</div>
</template>
<script setup name="cpPlanOther">
</script>
<style lang="less" scoped>
.plan-other {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 0;
}
</style>
Loading…
Cancel
Save