Browse Source

警务评议新增工单和核查工单,以及流程配置

main
wang 1 month ago
parent
commit
258c8b292d
  1. 24
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/controller/WtczController.java
  2. 27
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/form/WtczWorkflowActionForm.java
  3. 10
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/vo/WtczVO.java
  4. 226
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/service/WtczService.java
  5. 7
      smart-flow-web/src/api/business/jwpy/wtcz-api.js
  6. 110
      smart-flow-web/src/views/flow/definition/definition-form.vue
  7. 239
      smart-flow-web/src/views/jwpy/jwpy/flow/components/BizClassificationForm.vue
  8. 196
      smart-flow-web/src/views/jwpy/jwpy/flow/components/CallerInfoForm.vue
  9. 193
      smart-flow-web/src/views/jwpy/jwpy/flow/components/InvolvedTargetForm.vue
  10. 226
      smart-flow-web/src/views/jwpy/jwpy/flow/components/ReviewClosureForm.vue
  11. 261
      smart-flow-web/src/views/jwpy/jwpy/flow/index.vue
  12. 1324
      smart-flow-web/src/views/jwpy/jwpy/flow/wtcz-detail.vue
  13. 420
      smart-flow-web/src/views/jwpy/jwpy/flow/wtcz-form.vue

24
smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/controller/WtczController.java

@ -8,6 +8,7 @@ import jakarta.validation.Valid;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczQueryForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczAddForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczUpdateForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczWorkflowActionForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.vo.WtczVO;
import net.lab1024.sa.admin.module.business.jwpy.service.WtczService;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
@ -31,42 +32,50 @@ public class WtczController {
@Operation(summary = "分页查询评议工单")
@PostMapping("/jwpy/wtcz/queryPage")
@SaCheckPermission("jwpy:flow:query")
// @SaCheckPermission("jwpy:flow:query")
public ResponseDTO<PageResult<WtczVO>> queryPage(@RequestBody @Valid WtczQueryForm queryForm) {
return wtczService.queryPage(queryForm);
}
@Operation(summary = "添加评议工单")
@PostMapping("/jwpy/wtcz/add")
@SaCheckPermission("jwpy:flow:add")
// @SaCheckPermission("jwpy:flow:add")
public ResponseDTO<String> add(@RequestBody @Valid WtczAddForm addForm) {
return wtczService.add(addForm);
}
@Operation(summary = "修改评议工单")
@PostMapping("/jwpy/wtcz/update")
@SaCheckPermission("jwpy:flow:update")
// @SaCheckPermission("jwpy:flow:update")
public ResponseDTO<String> update(@RequestBody @Valid WtczUpdateForm updateForm) {
return wtczService.update(updateForm);
}
@Operation(summary = "办理评议工单流程动作")
@PostMapping("/jwpy/wtcz/workflow/action")
// @SaCheckPermission("jwpy:flow:handle")
public ResponseDTO<String> workflowAction(@RequestBody @Valid WtczWorkflowActionForm form) {
Long employeeId = SmartRequestUtil.getRequestUserId();
return wtczService.workflowAction(form, employeeId);
}
@Operation(summary = "删除评议工单")
@GetMapping("/jwpy/wtcz/delete/{id}")
@SaCheckPermission("jwpy:flow:delete")
// @SaCheckPermission("jwpy:flow:delete")
public ResponseDTO<String> delete(@PathVariable Long id) {
return wtczService.delete(id);
}
@Operation(summary = "批量删除评议工单")
@PostMapping("/jwpy/wtcz/batchDelete")
@SaCheckPermission("jwpy:flow:delete")
// @SaCheckPermission("jwpy:flow:delete")
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
return wtczService.batchDelete(idList);
}
@Operation(summary = "获取工单详情(合并首访与回访记录)")
@GetMapping("/jwpy/wtcz/detail/{id}")
@SaCheckPermission("jwpy:flow:query")
// @SaCheckPermission("jwpy:flow:query")
public ResponseDTO<WtczVO> getDetail(@PathVariable Long id) {
return wtczService.getDetail(id);
}
@ -79,10 +88,9 @@ public class WtczController {
@Operation(summary = "随机认领一个待办工单")
@PostMapping("/jwpy/wtcz/claimRandom")
@SaCheckPermission("task:claim")
// @SaCheckPermission("task:claim")
public ResponseDTO<WtczVO> claimRandom() {
Long employeeId = SmartRequestUtil.getRequestUserId();
return wtczService.claimRandom(employeeId);
}
}

27
smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/form/WtczWorkflowActionForm.java

@ -0,0 +1,27 @@
package net.lab1024.sa.admin.module.business.jwpy.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 警务评议工单流程动作表单
*
* @Author Antigravity
* @Date 2026-06-12
*/
@Data
public class WtczWorkflowActionForm {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "工单ID不能为空")
private Long id;
@Schema(description = "流程动作编码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "流程动作编码不能为空")
private String actionKey;
@Schema(description = "办理意见")
private String remark;
}

10
smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/vo/WtczVO.java

@ -189,10 +189,18 @@ public class WtczVO {
@Schema(description = "群众满意情况")
private String qzmyqk;
@Schema(description = "流程实例ID")
private Long instanceId;
@Schema(description = "流程当前节点名称")
private String nodeName;
@Schema(description = "流程状态")
private String flowStatus;
@Schema(description = "首访通话语音列表")
private java.util.List<WtczYyVO> czzxWtczYyList;
@Schema(description = "二次回访记录列表")
private java.util.List<WtczHfhcVO> czzxWtczHfhcList;
}

226
smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/service/WtczService.java

@ -14,19 +14,34 @@ import net.lab1024.sa.admin.module.business.jwpy.domain.entity.WtczHfhcEntity;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczQueryForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczAddForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczUpdateForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.form.WtczWorkflowActionForm;
import net.lab1024.sa.admin.module.business.jwpy.domain.vo.WtczVO;
import net.lab1024.sa.admin.module.business.jwpy.domain.vo.WtczYyVO;
import net.lab1024.sa.admin.module.business.jwpy.domain.vo.WtczHfhcVO;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.Instance;
import org.dromara.warm.flow.core.enums.SkipType;
import org.dromara.warm.flow.core.service.InsService;
import org.dromara.warm.flow.core.service.TaskService;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 警务评议与问题处置 业务服务类
@ -38,6 +53,8 @@ import java.util.List;
@Slf4j
public class WtczService {
private static final String FLOW_CODE = "jwpy_wtcz";
@Resource
private WtczDao wtczDao;
@ -47,6 +64,18 @@ public class WtczService {
@Resource
private WtczHfhcDao wtczHfhcDao;
@Resource
private InsService insService;
@Resource
private TaskService taskService;
@Resource
private FlowInstanceMapper flowInstanceMapper;
@Resource
private FlowTaskMapper flowTaskMapper;
/**
* 分页查询评议工单列表
*
@ -104,10 +133,37 @@ public class WtczService {
wtczDao.selectPage(page, wrapper);
List<WtczVO> voList = SmartBeanUtil.copyList(page.getRecords(), WtczVO.class);
fillFlowInfo(voList);
PageResult<WtczVO> pageResult = SmartPageUtil.convert2PageResult(page, voList);
return ResponseDTO.ok(pageResult);
}
private void fillFlowInfo(List<WtczVO> voList) {
if (CollectionUtils.isEmpty(voList)) {
return;
}
List<String> businessIdList = voList.stream()
.map(WtczVO::getId)
.map(String::valueOf)
.collect(Collectors.toList());
List<FlowInstance> instanceList = flowInstanceMapper.selectList(Wrappers.<FlowInstance>lambdaQuery()
.in(FlowInstance::getBusinessId, businessIdList));
if (CollectionUtils.isEmpty(instanceList)) {
return;
}
Map<String, FlowInstance> instanceMap = instanceList.stream()
.collect(Collectors.toMap(FlowInstance::getBusinessId, item -> item, (left, right) -> left));
for (WtczVO vo : voList) {
FlowInstance instance = instanceMap.get(String.valueOf(vo.getId()));
if (instance == null) {
continue;
}
vo.setInstanceId(instance.getId());
vo.setNodeName(instance.getNodeName());
vo.setFlowStatus(instance.getFlowStatus());
}
}
/**
* 检测若该小类暂无数据则自动往 t_czzx_wtczt_czzx_wtcz_yyt_czzx_wtcz_hfhc 中注入 3 Mock 数据方便流程联调与演示
*
@ -308,6 +364,7 @@ public class WtczService {
}
WtczVO vo = SmartBeanUtil.copy(entity, WtczVO.class);
fillFlowInfo(List.of(vo));
// 1. 查询并聚合首访通话语音列表
if (StringUtils.isNotBlank(vo.getSlbh())) {
@ -330,6 +387,122 @@ public class WtczService {
return ResponseDTO.ok(vo);
}
/**
* 通过 WarmFlow 办理警务评议工单生成流程实例待办与历史任务
*/
@Transactional(rollbackFor = Exception.class)
public ResponseDTO<String> workflowAction(WtczWorkflowActionForm form, Long employeeId) {
WtczEntity entity = wtczDao.selectById(form.getId());
if (entity == null) {
return ResponseDTO.userErrorParam("工单不存在");
}
WtczWorkflowAction action = WtczWorkflowAction.of(form.getActionKey());
String remark = StringUtils.defaultIfBlank(form.getRemark(), action.defaultRemark);
Instance instance = ensureFlowInstance(entity, employeeId);
List<FlowTask> taskList = flowTaskMapper.selectList(Wrappers.<FlowTask>lambdaQuery()
.eq(FlowTask::getInstanceId, instance.getId())
.orderByAsc(FlowTask::getId));
if (CollectionUtils.isEmpty(taskList) && !action.closeAction) {
throw new BusinessException("当前工单没有可办理的流程任务,请检查流程定义节点配置");
}
if (CollectionUtils.isNotEmpty(taskList)) {
FlowParams flowParams = new FlowParams();
flowParams.skipType(SkipType.PASS.getKey());
flowParams.flowCode(FLOW_CODE);
flowParams.message(remark);
flowParams.variable(buildFlowVariables(entity, action, remark, employeeId));
instance = taskService.skip(taskList.get(0).getId(), flowParams);
}
if (action.closeAction) {
instance = completeRemainingTasks(instance, entity, action, remark, employeeId);
}
syncBusinessStatus(entity, action, remark);
return ResponseDTO.ok("流程办理成功");
}
private Instance ensureFlowInstance(WtczEntity entity, Long employeeId) {
FlowInstance exist = flowInstanceMapper.selectOne(Wrappers.<FlowInstance>lambdaQuery()
.eq(FlowInstance::getBusinessId, String.valueOf(entity.getId())));
if (exist != null) {
return insService.getById(exist.getId());
}
FlowParams flowParams = new FlowParams();
flowParams.flowCode(FLOW_CODE);
flowParams.variable(buildFlowVariables(entity, null, "流程启动", employeeId));
try {
return insService.start(String.valueOf(entity.getId()), flowParams);
} catch (Exception e) {
throw new BusinessException("未找到或未启用警务评议流程定义,请在流程管理中配置流程编码:" + FLOW_CODE);
}
}
private Instance completeRemainingTasks(Instance instance, WtczEntity entity, WtczWorkflowAction action, String remark, Long employeeId) {
Instance current = instance;
for (int i = 0; i < 8; i++) {
List<FlowTask> taskList = flowTaskMapper.selectList(Wrappers.<FlowTask>lambdaQuery()
.eq(FlowTask::getInstanceId, current.getId())
.orderByAsc(FlowTask::getId));
if (CollectionUtils.isEmpty(taskList)) {
return current;
}
FlowParams flowParams = new FlowParams();
flowParams.skipType(SkipType.PASS.getKey());
flowParams.flowCode(FLOW_CODE);
flowParams.message(remark);
flowParams.variable(buildFlowVariables(entity, action, remark, employeeId));
current = taskService.skip(taskList.get(0).getId(), flowParams);
}
return current;
}
private Map<String, Object> buildFlowVariables(WtczEntity entity, WtczWorkflowAction action, String remark, Long employeeId) {
Map<String, Object> variables = new HashMap<>(16);
variables.put("flowCode", FLOW_CODE);
variables.put("initiator", String.valueOf(employeeId));
variables.put("businessId", String.valueOf(entity.getId()));
variables.put("businessTable", FLOW_CODE);
variables.put("actionKey", action == null ? "start" : action.key);
variables.put("remark", remark);
variables.put("sqfl", entity.getSqfl());
variables.put("sjlyNo", entity.getSjlyNo());
variables.put("sjdwCode", entity.getSjdwCode());
variables.put("sjjzNo", entity.getSjjzNo());
return variables;
}
private void syncBusinessStatus(WtczEntity entity, WtczWorkflowAction action, String remark) {
WtczEntity updateEntity = new WtczEntity();
updateEntity.setId(entity.getId());
updateEntity.setGdzt(action.gdzt);
updateEntity.setCljg(remark);
updateEntity.setTjsj(LocalDateTime.now().toString());
if (StringUtils.isNotBlank(action.sfjg)) {
updateEntity.setSfjg(action.sfjg);
}
if (StringUtils.isNotBlank(action.gdmyqk)) {
updateEntity.setGdmyqk(action.gdmyqk);
}
if (StringUtils.isNotBlank(action.qzmyqk)) {
updateEntity.setQzmyqk(action.qzmyqk);
}
if (StringUtils.isNotBlank(action.sfyyq)) {
updateEntity.setSfyyq(action.sfyyq);
}
if (StringUtils.isNotBlank(action.ssqkNo)) {
updateEntity.setSsqkNo(action.ssqkNo);
}
if (StringUtils.isNotBlank(action.wzqkNo)) {
updateEntity.setWzqkNo(action.wzqkNo);
}
wtczDao.updateById(updateEntity);
}
/**
* 获取当前任务大厅中待认领的工单总数
*
@ -380,5 +553,56 @@ public class WtczService {
}
return ResponseDTO.userErrorParam("系统当前繁忙或工单已抢空,请稍后刷新重试");
}
}
private enum WtczWorkflowAction {
FIRST_CLOSE("firstClose", "2", "1", "1", "CZZX_MYD_01", null, null, null, true,
"首访沟通清楚,群众表示满意,工单直接办结归档。"),
DISPATCH("dispatch", "1", "2", "2", "CZZX_MYD_03", null, null, null, false,
"首访后群众仍不满意,诉求具备核查必要,已下发责任单位核查整改。"),
STASH("stash", "0", null, null, null, null, null, null, false,
"首访电话暂未接通,工单暂存,等待群众回拨后继续办理。"),
UNIT_FEEDBACK("unitFeedback", "1", null, null, null, "0", null, null, false,
"责任单位已完成核查,核查意见及佐证材料已上报民意感知中心。"),
EXTENSION("extension", "1", null, null, null, "1", null, null, false,
"责任单位因核查取证需要申请延期,待分局审核后上报民意中心。"),
SECOND_VISIT("secondVisit", "2", null, "1", "CZZX_MYD_01", "0", null, null, true,
"二次回访已完成,整改结果已向群众核实,归档办结。"),
APPEAL_CLOSE("appealClose", "2", null, "2", "CZZX_MYD_03", null, "3", null, true,
"经核查公安机关已依法尽职,群众仍不满意,申诉通过归档,不计为有责工单。"),
MALICIOUS_CLOSE("maliciousClose", "2", null, "2", null, null, "3", null, true,
"经核实为恶意投诉,直接归档,不纳入二次回访。");
private final String key;
private final String gdzt;
private final String sfjg;
private final String gdmyqk;
private final String qzmyqk;
private final String sfyyq;
private final String ssqkNo;
private final String wzqkNo;
private final boolean closeAction;
private final String defaultRemark;
WtczWorkflowAction(String key, String gdzt, String sfjg, String gdmyqk, String qzmyqk, String sfyyq, String ssqkNo, String wzqkNo, boolean closeAction, String defaultRemark) {
this.key = key;
this.gdzt = gdzt;
this.sfjg = sfjg;
this.gdmyqk = gdmyqk;
this.qzmyqk = qzmyqk;
this.sfyyq = sfyyq;
this.ssqkNo = ssqkNo;
this.wzqkNo = wzqkNo;
this.closeAction = closeAction;
this.defaultRemark = defaultRemark;
}
private static WtczWorkflowAction of(String key) {
for (WtczWorkflowAction action : values()) {
if (action.key.equals(key)) {
return action;
}
}
throw new BusinessException("不支持的工单流程动作:" + key);
}
}
}

7
smart-flow-web/src/api/business/jwpy/wtcz-api.js

@ -29,6 +29,13 @@ export const wtczApi = {
return postRequest('/jwpy/wtcz/update', param);
},
/**
* 办理工单流程动作
*/
workflowAction: (param) => {
return postRequest('/jwpy/wtcz/workflow/action', param);
},
/**
* 删除工单
*/

110
smart-flow-web/src/views/flow/definition/definition-form.vue

@ -7,32 +7,20 @@
@cancel="handleCancel"
:confirmLoading="submitLoading"
>
<a-form ref="formRef" :model="form" class="dialogForm" :rules="rules" :label-col="{ style: { width: '150px' } }" :disabled="disabled">
<a-form ref="formRef" :model="form" class="dialogForm" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 17 }" :disabled="disabled">
<a-row :gutter="24">
<!-- 左侧列 -->
<a-col :span="12">
<a-form-item label="流程编码" name="flowCode">
<a-input v-model:value="form.flowCode" placeholder="请输入流程编码" :maxlength="40" show-count />
</a-form-item>
<a-form-item label="流程名称" name="flowName">
<a-input v-model:value="form.flowName" placeholder="请输入流程名称" :maxlength="100" show-count @input="nameChange" />
</a-form-item>
<a-form-item label="设计器模型" name="modelValue">
<a-radio-group v-model:value="form.modelValue" :disabled="!!definitionId" @change="modelValueChange">
<a-radio value="CLASSICS">经典模型</a-radio>
<a-radio value="MIMIC">仿钉钉模型
<span style="color: #ff4949; margin-left: 50px;">切换后重置节点保存后不支持修改</span>
</a-radio>
<a-radio value="MIMIC">仿钉钉模型</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="流程类别" name="category">
<a-tree-select
v-model:value="form.category"
:tree-data="categoryList"
:field-names="{ value: 'id', label: 'name', children: 'children' }"
placeholder="请选择流程类别"
tree-checkable
tree-check-strictly/>
<div style="color: #ff4949; font-size: 12px; margin-top: 4px;">切换后重置节点保存后不支持修改</div>
</a-form-item>
<a-form-item label="自定义表单" name="formCustom">
@ -41,7 +29,7 @@
<span class="flex-hc">
<a-tooltip placement="top" title="填写页面地址:如system/process/approve">
<ExclamationCircleOutlined style="font-size: 14px; margin-left: 5px;" />
<ExclamationCircleOutlined style="font-size: 14px; margin-left: 5px; color: #94a3b8;" />
</a-tooltip>
</span>
</a-radio>
@ -49,12 +37,31 @@
<span class="flex-hc">
<a-tooltip placement="top" title="填写自定义表单的唯一标识:如formCode+version">
<ExclamationCircleOutlined style="font-size: 14px; margin-left: 5px;" />
<ExclamationCircleOutlined style="font-size: 14px; margin-left: 5px; color: #94a3b8;" />
</a-tooltip>
</span>
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<!-- 右侧列 -->
<a-col :span="12">
<a-form-item label="流程名称" name="flowName">
<a-input v-model:value="form.flowName" placeholder="请输入流程名称" :maxlength="100" show-count @input="nameChange" />
</a-form-item>
<a-form-item label="流程类别" name="category">
<a-tree-select
v-model:value="form.category"
:tree-data="categoryList"
:field-names="{ value: 'id', label: 'name', children: 'children' }"
placeholder="请选择流程类别"
tree-checkable
tree-check-strictly
style="width: 100%"
/>
</a-form-item>
<a-form-item label="表单路径" name="formPath" v-if="form.formCustom === 'N'">
<a-input v-model:value="form.formPath" placeholder="请输入审批表单路径" :maxlength="100" show-count/>
@ -65,17 +72,25 @@
v-model:value="form.formPath"
:tree-data="formPathList"
:field-names="{ value: 'id', label: 'name', children: 'children' }"
placeholder="请选择流程类别"
placeholder="请选择自定义表单"
tree-checkable
tree-check-strictly/>
tree-check-strictly
style="width: 100%"
/>
</a-form-item>
<a-form-item name="listenerRows" class="listenerItem">
<a-table v-if="form.listenerRows && form.listenerRows.length > 0" :key="form.listenerRows.length" :data-source="form.listenerRows" :pagination="false" style="width: 100%">
<a-table-column data-index="listenerType" :width="150" title="类型">
</a-col>
</a-row>
<!-- 底部全局监听器表格 -->
<a-row style="margin-top: 16px;">
<a-col :span="24">
<a-form-item label="全局监听器" name="listenerRows" class="listenerItem" :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }">
<div style="border: 1px solid #f1f5f9; padding: 12px; border-radius: 6px; background-color: #fafafa;">
<a-table v-if="form.listenerRows && form.listenerRows.length > 0" :key="form.listenerRows.length" :data-source="form.listenerRows" :pagination="false" size="small" style="width: 100%">
<a-table-column data-index="listenerType" :width="200" title="类型">
<template #default="{ index: scopeIndex, record }">
<a-form-item :name="['listenerRows', scopeIndex, 'listenerType']" :rules="rules.listenerType">
<a-select v-model:value="record.listenerType" placeholder="请选择类型">
<a-form-item :name="['listenerRows', scopeIndex, 'listenerType']" :rules="rules.listenerType" style="margin-bottom: 0;">
<a-select v-model:value="record.listenerType" placeholder="请选择类型" style="width: 100%;">
<a-select-option value="start">开始</a-select-option>
<a-select-option value="assignment">分派</a-select-option>
<a-select-option value="finish">完成</a-select-option>
@ -87,22 +102,28 @@
<a-table-column data-index="listenerPath" title="路径">
<template #default="{ index: scopeIndex, record }">
<a-form-item :name="['listenerRows', scopeIndex, 'listenerPath']" :rules="rules.listenerPath">
<a-input v-model:value="record.listenerPath" placeholder="请输入路径" />
<a-form-item :name="['listenerRows', scopeIndex, 'listenerPath']" :rules="rules.listenerPath" style="margin-bottom: 0;">
<a-input v-model:value="record.listenerPath" placeholder="请输入监听器类路径" />
</a-form-item>
</template>
</a-table-column>
<a-table-column title="操作" :width="65" v-if="!disabled">
<a-table-column title="操作" :width="75" v-if="!disabled" align="center">
<template #default="{ index: scopeIndex }">
<a-button size="small" type="primary" danger @click="handleDeleteRow(scopeIndex)">
<a-button size="small" type="primary" danger ghost @click="handleDeleteRow(scopeIndex)">
<template #icon><DeleteOutlined /></template>
</a-button>
</template>
</a-table-column>
</a-table>
<a-button v-if="!disabled" style="margin-top: 10px;" type="primary" @click="handleAddRow">增加行</a-button>
<div v-else style="text-align: center; color: #94a3b8; padding: 10px 0;">暂无监听器</div>
<a-button v-if="!disabled" style="margin-top: 12px;" type="dashed" block @click="handleAddRow">
+ 增加监听器行
</a-button>
</div>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
@ -234,14 +255,16 @@ function handleDeleteRow(index) {
form.value.listenerRows.splice(index, 1);
}
function validate() {
return new Promise((resolve) => {
proxy.$nextTick(() => {
proxy.$refs.formRef.validate(valid => {
resolve(valid);
});
});
});
async function validate() {
if (!formRef.value) {
return false;
}
try {
await formRef.value.validate();
return true;
} catch (error) {
return false;
}
}
function nameChange(flowName) {
@ -355,9 +378,8 @@ defineExpose({ getFormData, validate, show });
}
.dialogForm {
border-top: 0;
padding: 15px;
width: 1000px;
margin: 50px auto;
padding: 10px 0;
width: 100%;
}
:deep(.listenerItem .ant-form-item-label) {
float: none;

239
smart-flow-web/src/views/jwpy/jwpy/flow/components/BizClassificationForm.vue

@ -0,0 +1,239 @@
<template>
<div class="form-module-wrapper">
<div class="module-header">
<span class="module-title">
<span class="title-bar"></span> 诉求内容与定性分类
</span>
</div>
<div class="module-content">
<a-descriptions
bordered
size="small"
:column="3"
>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>诉求业务分类
</template>
<a-form-item name="sqfl">
<DictSelect :dict-code="DICT_CODE_ENUM.CZZX_SQFL" placeholder="请选择诉求分类" v-model:value="formData.sqfl" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>涉及警务业务
</template>
<a-form-item name="sjlyNo">
<DictSelect :dict-code="DICT_CODE_ENUM.BUSINESS_CLASSIFICATION" placeholder="请选择业务分类" v-model:value="formData.sjlyNo" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="诉求等级">
<a-form-item name="sqdj">
<a-select v-model:value="formData.sqdj" placeholder="选择等级" style="width: 100%">
<a-select-option value="1">一般</a-select-option>
<a-select-option value="2">紧急</a-select-option>
</a-select>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="工单四级标签">
<a-form-item name="gdbq">
<div style="display: flex; gap: 8px;">
<a-button type="dashed" size="middle">选择标签</a-button>
<a-input v-model:value="formData.gdbq" placeholder="例如: 业务-交管-违法" style="flex: 1;" />
</div>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="是否涉企">
<a-form-item name="sfsq">
<DictSelect :dict-code="DICT_CODE_ENUM.IS_ENTERPRISE_RELATED" placeholder="选择是否涉企" v-model:value="formData.sfsq" />
</a-form-item>
</a-descriptions-item>
<template v-if="formData.sfsq == 1 || formData.sfsq === '1'">
<a-descriptions-item label="企业名称">
<a-form-item name="qymc">
<a-input v-model:value="formData.qymc" placeholder="输入企业名称" />
</a-form-item>
</a-descriptions-item>
</template>
<template v-else>
<a-descriptions-item label="是否已延期">
<a-form-item name="sfyyq">
<a-select v-model:value="formData.sfyyq" style="width: 100%">
<a-select-option value="0"></a-select-option>
<a-select-option value="1"></a-select-option>
</a-select>
</a-form-item>
</a-descriptions-item>
</template>
<a-descriptions-item :span="3">
<template #label>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span><span class="required-star">*</span>详细内容描述</span>
<a-button type="link" size="small" style="padding: 0; text-align: left; width: fit-content;">附件上传</a-button>
</div>
</template>
<a-form-item name="wtms">
<a-textarea v-model:value="formData.wtms" :rows="4" maxlength="2000" showCount placeholder="详细录入报案人的诉求原话..." />
</a-form-item>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
import DictSelect from '/@/components/support/dict-select/index.vue';
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
const props = defineProps({
formData: {
type: Object,
required: true
}
});
</script>
<style scoped>
/* 模块分区:融入整体页面,用细线分隔 */
.form-module-wrapper {
padding: 0 32px;
}
.module-header {
padding: 16px 0 10px;
border-bottom: 1px solid #e2e8f0;
}
.module-title {
font-weight: 700;
color: #1e293b;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.title-bar {
width: 4px;
height: 16px;
background: linear-gradient(180deg, #2563eb 0%, #60a5fa 100%);
border-radius: 2px;
}
.module-content {
padding: 16px 0;
}
.required-star {
color: #ef4444;
margin-right: 4px;
font-family: SimSun, sans-serif;
font-weight: bold;
}
/* 统一重写 Ant Design Vue 的 descriptions 样式 */
:deep(.ant-descriptions-bordered .ant-descriptions-view) {
border: 1px solid #e2e8f0 !important;
border-radius: 0 !important;
}
:deep(.ant-descriptions-row) {
border-bottom: 1px solid #f1f5f9 !important;
}
:deep(.ant-descriptions-item-label) {
width: 130px !important;
min-width: 130px !important;
max-width: 130px !important;
background-color: #f8fafc !important;
color: #475569 !important;
font-weight: 600 !important;
font-size: 13px !important;
padding: 10px 16px !important;
border-right: 1px solid #e2e8f0 !important;
}
:deep(.ant-descriptions-item-content) {
background-color: #ffffff !important;
padding: 6px 12px !important;
border-right: 1px solid #f1f5f9 !important;
}
/* 最后一行的 textarea 排除在 46px 限制之外 */
:deep(.ant-descriptions-row:last-child .ant-descriptions-item-label),
:deep(.ant-descriptions-row:last-child .ant-descriptions-item-content) {
height: auto !important;
border-right: 1px solid #e2e8f0 !important;
}
/* 彻底扁平化输入框 */
:deep(.ant-form-item) {
margin-bottom: 0 !important;
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-cascader-picker) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
height: 32px !important;
line-height: 30px !important;
padding: 0 8px !important;
}
:deep(.ant-select-selector .ant-select-selection-item) {
line-height: 30px !important;
}
:deep(.ant-input:hover),
:deep(.ant-select-selector:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-input:focus),
:deep(.ant-select-focused .ant-select-selector) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
:deep(.ant-textarea) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
padding: 8px !important;
}
:deep(.ant-textarea:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-textarea:focus) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
:deep(.ant-input[disabled]) {
background-color: #f1f5f9 !important;
color: #94a3b8 !important;
border-color: #cbd5e1 !important;
}
/* 错误红字绝对定位浮动,防止撑开网格高度 */
:deep(.ant-form-item-control) {
position: relative !important;
}
:deep(.ant-form-item-explain) {
position: absolute !important;
bottom: -4px !important;
left: 10px !important;
z-index: 10 !important;
font-size: 11px !important;
color: #ef4444 !important;
line-height: 1 !important;
background: #ffffff !important;
padding: 0 4px !important;
}
</style>

196
smart-flow-web/src/views/jwpy/jwpy/flow/components/CallerInfoForm.vue

@ -0,0 +1,196 @@
<template>
<div class="form-module-wrapper">
<div class="module-header">
<span class="module-title">
<span class="title-bar"></span> 诉求人与联络档案登记
</span>
</div>
<div class="module-content">
<a-descriptions
bordered
size="small"
:column="3"
>
<a-descriptions-item label="是否匿名">
<a-form-item name="sfnm">
<a-select v-model:value="formData.sfnm" style="width: 100%">
<a-select-option value="0"></a-select-option>
<a-select-option value="1"></a-select-option>
</a-select>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>诉求人姓名
</template>
<a-form-item name="sqrXm">
<a-input v-model:value="formData.sqrXm" placeholder="真实姓名或匿名标识" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="诉求人性别">
<a-form-item name="sqrXb">
<a-select v-model:value="formData.sqrXb" style="width: 100%" placeholder="选择性别">
<a-select-option value="1"></a-select-option>
<a-select-option value="2"></a-select-option>
</a-select>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>联系电话
</template>
<a-form-item name="lxdh">
<a-input v-model:value="formData.lxdh" placeholder="手机号或座机" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="身份证号码" :span="2">
<a-form-item name="sqrZjhm">
<a-input v-model:value="formData.sqrZjhm" placeholder="18位身份证号" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="现住址详址" :span="3">
<a-form-item name="sqrXzdxz">
<a-input v-model:value="formData.sqrXzdxz" placeholder="填写具体的区县与街道门牌号" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="户籍地详址" :span="3">
<a-form-item name="sqrHjdxz">
<a-input v-model:value="formData.sqrHjdxz" placeholder="填写身份证上的地址" />
</a-form-item>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
formData: {
type: Object,
required: true
}
});
</script>
<style scoped>
/* 模块分区:融入整体页面,用细线分隔 */
.form-module-wrapper {
padding: 0 32px;
}
.module-header {
padding: 16px 0 10px;
border-bottom: 1px solid #e2e8f0;
}
.module-title {
font-weight: 700;
color: #1e293b;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.title-bar {
width: 4px;
height: 16px;
background: linear-gradient(180deg, #2563eb 0%, #60a5fa 100%);
border-radius: 2px;
}
.module-content {
padding: 16px 0;
}
.required-star {
color: #ef4444;
margin-right: 4px;
font-family: SimSun, sans-serif;
font-weight: bold;
}
/* 统一重写 Ant Design Vue 的 descriptions 样式 */
:deep(.ant-descriptions-bordered .ant-descriptions-view) {
border: 1px solid #e2e8f0 !important;
border-radius: 0 !important;
}
:deep(.ant-descriptions-row) {
border-bottom: 1px solid #f1f5f9 !important;
}
:deep(.ant-descriptions-item-label) {
width: 130px !important;
min-width: 130px !important;
max-width: 130px !important;
background-color: #f8fafc !important;
color: #475569 !important;
font-weight: 600 !important;
font-size: 13px !important;
padding: 10px 16px !important;
border-right: 1px solid #e2e8f0 !important;
}
:deep(.ant-descriptions-item-content) {
background-color: #ffffff !important;
padding: 6px 12px !important;
border-right: 1px solid #f1f5f9 !important;
}
/* 彻底扁平化输入框 */
:deep(.ant-form-item) {
margin-bottom: 0 !important;
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-cascader-picker) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
height: 32px !important;
line-height: 30px !important;
padding: 0 8px !important;
}
:deep(.ant-select-selector .ant-select-selection-item) {
line-height: 30px !important;
}
:deep(.ant-input:hover),
:deep(.ant-select-selector:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-input:focus),
:deep(.ant-select-focused .ant-select-selector) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
:deep(.ant-input[disabled]) {
background-color: #f1f5f9 !important;
color: #94a3b8 !important;
border-color: #cbd5e1 !important;
}
/* 错误红字绝对定位浮动,防止撑开网格高度 */
:deep(.ant-form-item-control) {
position: relative !important;
}
:deep(.ant-form-item-explain) {
position: absolute !important;
bottom: -4px !important;
left: 10px !important;
z-index: 10 !important;
font-size: 11px !important;
color: #ef4444 !important;
line-height: 1 !important;
background: #ffffff !important;
padding: 0 4px !important;
}
</style>

193
smart-flow-web/src/views/jwpy/jwpy/flow/components/InvolvedTargetForm.vue

@ -0,0 +1,193 @@
<template>
<div class="form-module-wrapper">
<div class="module-header">
<span class="module-title">
<span class="title-bar"></span> 涉事单位与指派人员
</span>
</div>
<div class="module-content">
<a-descriptions
bordered
size="small"
:column="3"
>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>涉及警种
</template>
<a-form-item name="sjjzNo">
<DictSelect :dict-code="DICT_CODE_ENUM.SYSTEM_JZ" placeholder="请选择涉及警种" v-model:value="formData.sjjzNo" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item :span="2">
<template #label>
<span class="required-star">*</span>被反映单位
</template>
<a-form-item name="sjdwCode">
<a-cascader
v-model:value="formData.sjdwCode"
:options="departmentOptions"
placeholder="请选择区县分局与具体所队"
style="width: 100%"
expand-trigger="hover"
/>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="被反映人身份">
<a-form-item name="sfsjfj">
<a-select v-model:value="formData.sfsjfj" placeholder="警员身份" style="width: 100%">
<a-select-option value="1">民警</a-select-option>
<a-select-option value="2">辅警</a-select-option>
</a-select>
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="被反映人姓名">
<a-form-item name="sjfjxm">
<a-input v-model:value="formData.sjfjxm" placeholder="核实后的具体反映人" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item label="被反映人警号">
<a-form-item name="sjfjjh">
<a-input v-model:value="formData.sjfjjh" placeholder="警号/工号" />
</a-form-item>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
import DictSelect from '/@/components/support/dict-select/index.vue';
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
const props = defineProps({
formData: {
type: Object,
required: true
},
departmentOptions: {
type: Array,
default: () => []
}
});
</script>
<style scoped>
/* 模块分区:融入整体页面,用细线分隔 */
.form-module-wrapper {
padding: 0 32px;
}
.module-header {
padding: 16px 0 10px;
border-bottom: 1px solid #e2e8f0;
}
.module-title {
font-weight: 700;
color: #1e293b;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.title-bar {
width: 4px;
height: 16px;
background: linear-gradient(180deg, #2563eb 0%, #60a5fa 100%);
border-radius: 2px;
}
.module-content {
padding: 16px 0;
}
.required-star {
color: #ef4444;
margin-right: 4px;
font-family: SimSun, sans-serif;
font-weight: bold;
}
/* 统一重写 Ant Design Vue 的 descriptions 样式 */
:deep(.ant-descriptions-bordered .ant-descriptions-view) {
border: 1px solid #e2e8f0 !important;
border-radius: 0 !important;
}
:deep(.ant-descriptions-row) {
border-bottom: 1px solid #f1f5f9 !important;
}
:deep(.ant-descriptions-item-label) {
width: 130px !important;
min-width: 130px !important;
max-width: 130px !important;
background-color: #f8fafc !important;
color: #475569 !important;
font-weight: 600 !important;
font-size: 13px !important;
padding: 10px 16px !important;
border-right: 1px solid #e2e8f0 !important;
}
:deep(.ant-descriptions-item-content) {
background-color: #ffffff !important;
padding: 6px 12px !important;
border-right: 1px solid #f1f5f9 !important;
}
/* 彻底扁平化输入框 */
:deep(.ant-form-item) {
margin-bottom: 0 !important;
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-cascader-picker) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
height: 32px !important;
line-height: 30px !important;
padding: 0 8px !important;
}
:deep(.ant-select-selector .ant-select-selection-item) {
line-height: 30px !important;
}
:deep(.ant-input:hover),
:deep(.ant-select-selector:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-input:focus),
:deep(.ant-select-focused .ant-select-selector) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
:deep(.ant-input[disabled]) {
background-color: #f1f5f9 !important;
color: #94a3b8 !important;
border-color: #cbd5e1 !important;
}
/* 错误红字绝对定位浮动,防止撑开网格高度 */
:deep(.ant-form-item-control) {
position: relative !important;
}
:deep(.ant-form-item-explain) {
position: absolute !important;
bottom: -4px !important;
left: 10px !important;
z-index: 10 !important;
font-size: 11px !important;
color: #ef4444 !important;
line-height: 1 !important;
background: #ffffff !important;
padding: 0 4px !important;
}
</style>

226
smart-flow-web/src/views/jwpy/jwpy/flow/components/ReviewClosureForm.vue

@ -0,0 +1,226 @@
<template>
<div class="review-closure-container">
<!-- 1. 首访沟通台 -->
<div class="form-module-wrapper">
<div class="module-header">
<span class="module-title">
<span class="title-bar"></span> 电话回访与沟通记录
</span>
</div>
<div class="module-content">
<a-form layout="vertical">
<a-form-item>
<template #label>
<div style="font-weight: bold; color: #475569; display: flex; align-items: center; gap: 8px;">
首访沟通记录
</div>
</template>
<!-- 在后端实体中这可能是 firstFeedbackOpinion 或者是其他补充字段目前先绑定此字段 -->
<a-textarea v-model:value="formData.firstFeedbackOpinion" :rows="3" placeholder="填写与群众的沟通情况及诉求点..." />
</a-form-item>
</a-form>
</div>
</div>
<!-- 2. 复核结案区 (高权限专属) -->
<div class="form-module-wrapper" v-if="isSuperUser">
<div class="module-header">
<span class="module-title">
<span class="title-bar"></span> 复核结案
</span>
</div>
<div class="module-content">
<a-descriptions
bordered
size="small"
:column="2"
>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>情况是否属实
</template>
<a-form-item name="ssqkNo">
<DictSelect :dict-code="DICT_CODE_ENUM.IS_FACT" placeholder="请选择核查属实情况" v-model:value="formData.ssqkNo" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item>
<template #label>
<span class="required-star">*</span>涉事问责情况
</template>
<a-form-item name="wzqkNo">
<DictSelect :dict-code="DICT_CODE_ENUM.CZZX_WZQK" placeholder="请选择问责定性" v-model:value="formData.wzqkNo" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item :span="2">
<template #label>
<span class="required-star">*</span>复核满意度
</template>
<a-form-item name="secondSatisfaction">
<DictSelect :dict-code="DICT_CODE_ENUM.CALLBACK_SATISFACTION" placeholder="请选择群众最终复核满意度" v-model:value="formData.secondSatisfaction" />
</a-form-item>
</a-descriptions-item>
<a-descriptions-item :span="2">
<template #label>
<span class="required-star">*</span>结案说明
</template>
<a-form-item name="secondFeedbackOpinion">
<a-textarea v-model:value="formData.secondFeedbackOpinion" :rows="3" placeholder="请输入结案说明..." />
</a-form-item>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
import DictSelect from '/@/components/support/dict-select/index.vue';
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
const props = defineProps({
formData: {
type: Object,
required: true
},
// /2
isSuperUser: {
type: Boolean,
default: true
}
});
</script>
<style scoped>
.review-closure-container {
display: flex;
flex-direction: column;
gap: 24px;
}
.form-module-wrapper {
background-color: #ffffff;
border: 1px solid #cbd5e1;
border-radius: 2px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.module-header {
padding: 10px 16px;
border-bottom: 1px solid #e2e8f0;
background-color: #f8fafc;
display: flex;
justify-content: space-between;
align-items: center;
}
.module-title {
font-weight: 800;
color: #334155;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.title-bar {
width: 4px;
height: 14px;
background-color: #3b82f6;
border-radius: 1px;
}
.module-content {
padding: 16px;
}
.required-star {
color: #ef4444;
margin-right: 4px;
font-family: SimSun, sans-serif;
font-weight: bold;
}
/* 统一重写 Ant Design Vue 的 descriptions 样式以高保真还原 */
:deep(.ant-descriptions-bordered .ant-descriptions-view) {
border: 1px solid #cbd5e1 !important;
border-radius: 2px !important;
overflow: hidden;
}
:deep(.ant-descriptions-row) {
border-bottom: 1px solid #cbd5e1 !important;
}
:deep(.ant-descriptions-item-label) {
width: 120px !important;
min-width: 120px !important;
max-width: 120px !important;
background-color: #f8fafc !important;
color: #475569 !important;
font-weight: 700 !important;
font-size: 13px !important;
padding: 8px 12px !important;
border-right: 1px solid #cbd5e1 !important;
height: 46px !important;
}
:deep(.ant-descriptions-item-content) {
background-color: #ffffff !important;
padding: 6px 10px !important;
border-right: 1px solid #cbd5e1 !important;
height: 46px !important;
}
/* 最后一行的 textarea 排除在 46px 限制之外 */
:deep(.ant-descriptions-row:last-child .ant-descriptions-item-label),
:deep(.ant-descriptions-row:last-child .ant-descriptions-item-content) {
height: auto !important;
}
/* 彻底扁平化输入框 */
:deep(.ant-form-item) {
margin-bottom: 0 !important;
}
:deep(.ant-input),
:deep(.ant-select-selector),
:deep(.ant-cascader-picker) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
height: 32px !important;
line-height: 30px !important;
padding: 0 8px !important;
}
:deep(.ant-select-selector .ant-select-selection-item) {
line-height: 30px !important;
}
:deep(.ant-input:hover),
:deep(.ant-select-selector:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-input:focus),
:deep(.ant-select-focused .ant-select-selector) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
:deep(.ant-textarea) {
border-radius: 2px !important;
border: 1px solid #cbd5e1 !important;
box-shadow: none !important;
padding: 8px !important;
}
:deep(.ant-textarea:hover) {
border-color: #94a3b8 !important;
}
:deep(.ant-textarea:focus) {
border-color: #2563eb !important;
box-shadow: 0 0 0 1px #2563eb !important;
}
</style>

261
smart-flow-web/src/views/jwpy/jwpy/flow/index.vue

@ -20,21 +20,24 @@
<DictSelect :dict-code="DICT_CODE_ENUM.CALLBACK_SATISFACTION" placeholder="请选择" v-model:value="queryForm.qzmyqk" width="120px" allowClear />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="onSearch">查询</a-button>
<a-button style="margin-left: 8px" @click="resetQuery">重置</a-button>
<a-space>
<a-button type="primary" size="small" @click="onSearch">查询</a-button>
<a-button size="small" @click="resetQuery">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- 表格顶部操作按钮 -->
<div class="table-operate-row" style="margin-bottom: 12px">
<a-space>
<a-button type="primary" @click="showForm()">
<a-button type="primary" size="small" @click="showForm()">
<template #icon><PlusOutlined /></template>
新建工单
</a-button>
<a-button
type="primary"
danger
size="small"
:disabled="selectedRowKeyList.length === 0"
@click="confirmBatchDelete"
>
@ -68,16 +71,21 @@
</template>
<!-- 处理状态标签 -->
<template v-if="column.dataIndex === 'gdzt'">
<a-tag :color="text === '2' ? 'green' : text === '1' ? 'orange' : 'blue'">
{{ text === '2' ? '已办结' : text === '1' ? '办结中' : '已录入' }}
<a-tag :color="getStatusMeta(text).color">
{{ getStatusMeta(text).label }}
</a-tag>
</template>
<template v-if="column.dataIndex === 'workflowNode'">
<div class="workflow-node-cell">
<a-tag :color="getWorkflowNode(record).color">{{ getWorkflowNode(record).label }}</a-tag>
<span>{{ getWorkflowNode(record).hint }}</span>
</div>
</template>
<!-- 操作列 -->
<template v-if="column.dataIndex === 'action'">
<div class="smart-table-operate">
<a-button type="link" @click="showDetail(record.id)">详情</a-button>
<a-button type="link" @click="showForm(record)">编辑</a-button>
<a-button type="link" danger @click="onDelete(record.id)">删除</a-button>
<a-button type="link" size="small" @click="showDetail(record.id)">详情</a-button>
<a-button type="link" size="small" @click="showDetail(record.id)" v-if="String(record.gdzt) !== '2'">办理</a-button>
</div>
</template>
</template>
@ -104,12 +112,12 @@
<!-- 表单抽屉与详情抽屉 -->
<WtczForm ref="formRef" @reloadList="queryData" />
<WtczDetail ref="detailRef" />
<WtczDetail ref="detailRef" @reloadList="queryData" />
</div>
</template>
<script setup>
import { ref, computed, reactive, watch } from 'vue';
import { ref, computed, reactive, h, watch } from 'vue';
import { useRoute } from 'vue-router';
import { message, Modal } from 'ant-design-vue';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
@ -190,12 +198,75 @@ const columns = computed(() => {
{ title: '涉及民警', dataIndex: 'sjmjMc', width: 100 },
{ title: '群众满意度', dataIndex: 'qzmyqk', width: 100, align: 'center' },
{ title: '工单状态', dataIndex: 'gdzt', width: 100, align: 'center' },
{ title: '当前处理人', dataIndex: 'workflowNode', width: 180 },
{ title: '诉求时间', dataIndex: 'sqsj', width: 150 },
{ title: '操作', dataIndex: 'action', width: 160, align: 'center', fixed: 'right' }
{ title: '操作', dataIndex: 'action', width: 100, align: 'center', fixed: 'right' }
);
return list;
});
function getStatusMeta(status) {
const map = {
'0': { label: '待首访', color: 'blue' },
'1': { label: '办理中', color: 'orange' },
'2': { label: '已办结', color: 'green' }
};
return map[String(status)] || { label: '待生成', color: 'default' };
}
function getWorkflowNode(record) {
if (record.nodeName) {
let handlerName = '民意感知中心';
let nodeName = record.nodeName;
// ""
if (nodeName === '中间节点' || nodeName === '任务节点') {
if (String(record.gdzt) === '1') {
nodeName = '所队核查';
} else if (String(record.gdzt) === '0') {
nodeName = '首访派发';
} else {
nodeName = '流程流转';
}
}
if (nodeName.includes('核查') || nodeName.includes('反馈') || nodeName.includes('所队') || nodeName.includes('承办')) {
handlerName = record.sjdwMc || '责任单位';
} else if (nodeName.includes('复核') || nodeName.includes('审核') || nodeName.includes('二次') || nodeName.includes('回访')) {
handlerName = '民意中心(复核岗)';
} else if (nodeName.includes('派发') || nodeName.includes('分派') || nodeName.includes('首访')) {
handlerName = '民意中心(分派岗)';
}
return {
label: nodeName,
color: record.flowStatus === '8' ? 'green' : 'geekblue',
hint: `处理中: ${handlerName}`,
};
}
if (String(record.gdzt) === '2') {
if (record.cljg?.includes('恶意投诉')) {
return { label: '恶意归档', color: 'red', hint: '不纳入回访' };
}
if (record.cljg?.includes('已依法尽职')) {
return { label: '申诉归档', color: 'cyan', hint: '不计有责' };
}
return { label: '办结归档', color: 'green', hint: record.sfjg === '1' ? '首访闭环' : '复核闭环' };
}
if (String(record.sfyyq) === '1') {
return { label: '延期审核', color: 'purple', hint: '待分局/中心审核' };
}
if (record.cljg?.includes('单位核查反馈')) {
return { label: '二次回访', color: 'geekblue', hint: '待中心回访' };
}
if (String(record.gdzt) === '1') {
return { label: '核查整改', color: 'orange', hint: '责任单位办理' };
}
if (record.firstFeedbackOpinion?.includes('暂存')) {
return { label: '暂存', color: 'default', hint: '待群众回拨' };
}
return { label: '人工首访', color: 'blue', hint: '待电话核实' };
}
// ---------------------------- ----------------------------
function getBizTypeLabel(sjlyNo) {
const map = {
@ -315,6 +386,126 @@ function showDetail(id) {
detailRef.value.show(id);
}
const workflowActionMap = {
firstClose: {
title: '首访满意办结',
content: '首访已解释清楚、群众满意或问题已办结,将直接归档。',
remark: '首访沟通清楚,群众表示满意,工单直接办结归档。',
},
dispatch: {
title: '下发核查整改',
content: '群众仍不满意且诉求合理,将下发责任单位核查整改。',
remark: '首访后群众仍不满意,诉求具备核查必要,已下发责任单位核查整改。',
},
stash: {
title: '暂存待回拨',
content: '电话无人接听、关机或空号时暂存,暂存期间不关闭工单。',
remark: '首访电话暂未接通,工单暂存,等待群众回拨后继续办理。',
},
unitFeedback: {
title: '单位反馈上报',
content: '责任单位已完成核查,经领导审核后上报民意感知中心。',
remark: '责任单位已完成核查,核查意见及佐证材料已上报民意感知中心。',
},
extension: {
title: '延期申请',
content: '责任单位申请延长期限,提交分局审核后上报民意中心。',
remark: '责任单位因核查取证需要申请延期,待分局审核后上报民意中心。',
},
secondVisit: {
title: '二次回访完成',
content: '民意感知中心完成二次电话回访,记录整改结果和群众满意度。',
remark: '二次回访已完成,整改结果已向群众核实。',
},
appealClose: {
title: '已尽职申诉归档',
content: '公安机关已依法尽职,群众仍不满意,经人工判定申请办结。',
remark: '经核查公安机关已依法尽职,群众仍不满意,按申诉通过归档。',
},
maliciousClose: {
title: '恶意投诉归档',
content: '经核实属于恶意投诉,直接办结,不纳入二次回访。',
remark: '经核实为恶意投诉,直接归档,不纳入二次回访。',
},
};
function getAvailableWorkflowActions(record) {
if (String(record.gdzt) === '2') {
return [{ key: 'detailOnly', label: '已办结,仅查看' }];
}
if (String(record.sfyyq) === '1') {
return [
{ key: 'unitFeedback', label: '单位反馈上报' },
{ key: 'secondVisit', label: '二次回访完成' },
];
}
if (record.cljg?.includes('单位核查反馈')) {
return [
{ key: 'secondVisit', label: '二次回访完成' },
{ key: 'appealClose', label: '已尽职申诉归档' },
{ key: 'maliciousClose', label: '恶意投诉归档' },
];
}
if (String(record.gdzt) === '1') {
return [
{ key: 'unitFeedback', label: '单位反馈上报' },
{ key: 'extension', label: '延期申请' },
];
}
return [
{ key: 'firstClose', label: '首访满意办结' },
{ key: 'dispatch', label: '下发核查整改' },
{ key: 'stash', label: '暂存待回拨' },
];
}
function handleWorkflowAction(actionKey, record) {
if (actionKey === 'detailOnly') {
showDetail(record.id);
return;
}
const action = workflowActionMap[actionKey];
if (!action) {
return;
}
const remark = ref(action.remark);
Modal.confirm({
title: action.title,
width: 560,
content: h('div', { class: 'workflow-confirm' }, [
h('p', action.content),
h('textarea', {
class: 'workflow-confirm-textarea',
value: remark.value,
rows: 4,
onInput: (event) => {
remark.value = event.target.value;
},
}),
]),
okText: '确认推进',
cancelText: '取消',
async onOk() {
await submitWorkflowAction(record, actionKey, remark.value);
},
});
}
async function submitWorkflowAction(record, actionKey, remark) {
const action = workflowActionMap[actionKey];
try {
await wtczApi.workflowAction({
id: record.id,
actionKey,
remark: remark || action.remark,
});
message.success(`${action.title}成功`);
queryData();
} catch (err) {
message.error('流程操作失败,请检查工单必填信息');
}
}
// ---------------------------- ----------------------------
function onDelete(id) {
Modal.confirm({
@ -375,4 +566,52 @@ async function requestBatchDelete() {
.icon-wrapper {
background: linear-gradient(135deg, #3f86f8 0%, #1890ff 100%);
}
.workflow-node-cell {
display: flex;
flex-direction: column;
gap: 4px;
line-height: 1.2;
}
.workflow-node-cell span:last-child {
color: #64748b;
font-size: 12px;
}
:deep(.smart-table-operate) {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
white-space: nowrap;
}
:deep(.smart-table-operate .ant-btn-link) {
height: 24px;
padding: 0 4px;
font-size: 12px;
}
</style>
<style>
.workflow-confirm p {
margin: 0 0 8px;
color: #475569;
}
.workflow-confirm-textarea {
width: 100%;
resize: vertical;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 8px 10px;
color: #1f2937;
outline: none;
}
.workflow-confirm-textarea:focus {
border-color: #1677ff;
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.12);
}
</style>

1324
smart-flow-web/src/views/jwpy/jwpy/flow/wtcz-detail.vue

File diff suppressed because it is too large

420
smart-flow-web/src/views/jwpy/jwpy/flow/wtcz-form.vue

@ -1,147 +1,58 @@
<!--
* 警务评议与问题处置 表单抽屉
*
* @Author: Antigravity
* @Date: 2026-06-09
-->
<template>
<a-modal
:open="visibleFlag"
:title="form.id ? '编辑工单' : '登记工单'"
:width="850"
ok-text="确认"
cancel-text="取消"
:title="null"
:width="1440"
:footer="null"
:maskClosable="false"
:destroyOnClose="true"
@ok="onSubmit"
centered
class="work-order-modal"
@cancel="onClose"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<FormTitle title="基本信息" />
<!-- 基础信息三列 -->
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="受理编号" name="slbh">
<a-input v-model:value="form.slbh" placeholder="请输入受理编号" :disabled="!!form.id" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="业务分类" name="sjlyNo">
<DictSelect :dict-code="DICT_CODE_ENUM.BUSINESS_CLASSIFICATION" placeholder="请选择业务分类" v-model:value="form.sjlyNo" width="100%" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="涉及警种" name="sjjzNo">
<DictSelect :dict-code="DICT_CODE_ENUM.SYSTEM_JZ" placeholder="请选择涉及警种" v-model:value="form.sjjzNo" width="100%" />
</a-form-item>
</a-col>
</a-row>
<!-- 页面标题区对齐设计原型 -->
<div class="page-header">
<div class="page-header-left">
<h1 class="page-title">
<span class="title-bar"></span>
{{ form.id ? '编辑工单' : '登记工单' }}
</h1>
</div>
</div>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="涉及单位" name="sjdwMc">
<a-input v-model:value="form.sjdwMc" placeholder="请输入涉及单位名称" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="涉及民警" name="sjmjMc">
<a-input v-model:value="form.sjmjMc" placeholder="请输入涉及民警姓名" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="关键字" name="gjz">
<a-input v-model:value="form.gjz" placeholder="请输入关键字" />
</a-form-item>
</a-col>
</a-row>
<div class="form-scroll-container">
<a-form ref="formRef" :model="form" :rules="rules" layout="horizontal">
<!-- 模块1诉求人与联络档案 -->
<CallerInfoForm :formData="form" />
<FormTitle title="诉求人信息" />
<!-- 模块2业务定性与分类 -->
<BizClassificationForm :formData="form" />
<!-- 诉求人三列 -->
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="诉求人姓名" name="sqrXm">
<a-input v-model:value="form.sqrXm" placeholder="请输入诉求人姓名" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="诉求人电话" name="lxdh">
<a-input v-model:value="form.lxdh" placeholder="请输入诉求人电话" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="身份证号码" name="sqrZjhm">
<a-input v-model:value="form.sqrZjhm" placeholder="请输入身份证号码" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="性别" name="sqrXb">
<a-select v-model:value="form.sqrXb" placeholder="请选择性别">
<a-select-option value="1"></a-select-option>
<a-select-option value="2"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="户籍地详址" name="sqrHjdxz">
<a-input v-model:value="form.sqrHjdxz" placeholder="请输入户籍地详址" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="现住地详址" name="sqrXzdxz">
<a-input v-model:value="form.sqrXzdxz" placeholder="请输入现住地详址" />
</a-form-item>
</a-col>
</a-row>
<FormTitle title="关联业务与问题详述" />
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="关联警情编号" name="gljqBh">
<a-input v-model:value="form.gljqBh" placeholder="请输入关联警情编号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="关联案件编号" name="glajBh">
<a-input v-model:value="form.glajBh" placeholder="请输入关联案件编号" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="问题描述 (诉求内容)" name="wtms">
<a-textarea v-model:value="form.wtms" placeholder="请输入详细的登记问题描述" :rows="4" />
</a-form-item>
<FormTitle title="附件材料" />
<a-form-item name="uuid">
<Upload
accept=".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg"
:maxUploadSize="3"
listType="text"
buttonText="点击上传附件"
:default-file-list="form.uuid"
@change="(fileList) => form.uuid = fileList"
/>
</a-form-item>
<!-- 模块3涉事单位与人员补充 -->
<InvolvedTargetForm :formData="form" :departmentOptions="departmentOptions" />
</a-form>
<!-- 底部操作按钮区 -->
<div class="footer-actions">
<a-button @click="onClose" class="footer-btn footer-btn-default">清空并取消</a-button>
<a-button type="dashed" class="footer-btn" @click="onSubmit('stash')" style="border-color: #d9d9d9;">暂存工单</a-button>
<a-button type="primary" class="footer-btn footer-btn-primary" @click="onSubmit('dispatch')">登记并直接派发</a-button>
</div>
</div>
</a-modal>
</template>
<script setup>
import { reactive, ref, nextTick } from 'vue';
import { reactive, ref, nextTick, computed, watch } from 'vue';
import { message } from 'ant-design-vue';
import _ from 'lodash';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { wtczApi } from '/@/api/business/jwpy/wtcz-api';
import { smartSentry } from '/@/lib/smart-sentry';
import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
import DictSelect from '/@/components/support/dict-select/index.vue';
import Upload from '/@/components/support/file-upload/index.vue';
import FormTitle from './form-title.vue';
import CallerInfoForm from './components/CallerInfoForm.vue';
import BizClassificationForm from './components/BizClassificationForm.vue';
import InvolvedTargetForm from './components/InvolvedTargetForm.vue';
const emits = defineEmits(['reloadList']);
@ -167,87 +78,115 @@ function onClose() {
// ------------------------ ------------------------
const formRef = ref();
//
const departmentOptions = [
{
value: 'hefei_gx',
label: '合肥市公安局高新分局',
children: [
{ value: 'gx_hfpcs', label: '蜀麓派出所' },
{ value: 'gx_gxdd', label: '高新交警大队' }
]
},
{
value: 'hefei_bh',
label: '合肥市公安局包河分局',
children: [
{ value: 'bh_wnpcs', label: '万年埠派出所' },
{ value: 'bh_bhyzd', label: '交警支队包河大队一中队' }
]
}
];
const formDefault = {
id: undefined,
slbh: undefined,
cityCode: undefined,
cityName: undefined,
sqsj: undefined,
gdzt: '0',
sfjg: undefined,
sqrXm: undefined,
sqrXb: undefined,
lxdh: undefined,
sfnm: 0,
sqrZjhm: undefined,
sqrXzdxz: undefined,
sqrHjdxz: undefined,
sqfl: undefined,
gdbq: undefined,
sjlyqd: undefined,
sjlyNo: undefined,
sjdwMc: undefined,
sjdwCode: undefined,
sjjzMc: undefined,
ssqkNo: undefined,
sjmjMc: undefined,
wzqkNo: undefined,
trueFlag: undefined,
sqdj: undefined,
gdbq: undefined,
sfsq: 0,
sqnr: undefined,
cljg: undefined,
dxfsnr: undefined,
dxhfnr: undefined,
dxfssj: undefined,
dxhfsj: undefined,
tjsj: undefined,
orgCode: undefined,
gdmyqk: undefined,
createTime: undefined,
syncTime: undefined,
orgName: undefined,
qymc: undefined,
sfyyq: undefined,
wtms: undefined,
sjjzMc: undefined,
sjdwCode: undefined,
sfsjfj: undefined,
sjfjxm: undefined,
sjfjjh: undefined,
lxdh: undefined,
sqrZjhm: undefined,
sqrXb: undefined,
fpyNo: undefined,
fpyName: undefined,
sjdwName: undefined,
sfnm: 0,
sjdwId: undefined,
sjmjIds: undefined,
sjjzNo: undefined,
sqrId: undefined,
sqrXm: undefined,
sqrHjdxz: undefined,
sqrXzdxz: undefined,
gljqBh: undefined,
glajBh: undefined,
sqdj: undefined,
gjz: undefined,
sfyyq: undefined,
wtms: undefined,
tjrId: undefined,
tjdwId: undefined,
qzmyqk: undefined,
};
let form = reactive({ ...formDefault });
const rules = {
slbh: [{ required: true, message: '工单受理编号 必填' }],
sjlyNo: [{ required: true, message: '请选择业务分类' }],
};
//
watch(
() => form.sfnm,
(newVal) => {
if (newVal === 1) {
form.sqrXm = '匿名';
form.sqrXb = undefined;
form.lxdh = undefined;
form.sqrZjhm = undefined;
form.sqrXzdxz = undefined;
form.sqrHjdxz = undefined;
//
nextTick(() => {
formRef.value?.clearValidate(['sqrXm', 'lxdh']);
});
} else {
if (form.sqrXm === '匿名') {
form.sqrXm = undefined;
}
}
}
);
const rules = computed(() => {
return {
sqrXm: form.sfnm === 1 ? [] : [{ required: true, message: '请输入诉求人姓名', trigger: 'blur' }],
lxdh: form.sfnm === 1 ? [] : [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
sqfl: [{ required: true, message: '请选择诉求业务分类', trigger: 'change' }],
sjlyNo: [{ required: true, message: '请选择涉及警务业务', trigger: 'change' }],
wtms: [{ required: true, message: '请输入详细内容描述', trigger: 'blur' }],
sjjzNo: [{ required: true, message: '请选择涉及警种', trigger: 'change' }],
sjdwCode: [{ required: true, message: '请选择被反映单位', trigger: 'change' }],
};
});
//
function onSubmit() {
function onSubmit(actionType) {
formRef.value
.validate()
.then(async () => {
SmartLoading.show();
try {
if (form.id) {
let wtczId = form.id;
// 1. /
if (wtczId) {
await wtczApi.update(form);
} else {
await wtczApi.add(form);
const res = await wtczApi.add(form);
wtczId = res.data; // addID
}
message.success(`${form.id ? '修改' : '登记'}成功`);
// 2.
if (actionType && wtczId) {
await wtczApi.workflowAction({
id: Number(wtczId),
actionKey: actionType,
remark: actionType === 'dispatch' ? '登记工单并直接派发。' : '登记暂存工单,待后续派发。'
});
}
message.success(`${actionType === 'dispatch' ? '登记并派发' : '暂存'}成功`);
emits('reloadList');
onClose();
} catch (err) {
@ -258,7 +197,7 @@ function onSubmit() {
})
.catch((err) => {
console.log('error', err);
message.error('参数验证错误,请仔细填写必要表单数据!');
message.error('参数验证错误,请仔细检查表单数据!');
});
}
@ -267,4 +206,121 @@ defineExpose({
});
</script>
<style scoped>
/* ========== 弹窗容器:对齐设计原型的全页面感 ========== */
.form-scroll-container {
padding: 0;
background-color: #ffffff;
}
/* 页面标题区(融入页面整体) */
.page-header {
padding: 24px 32px 16px;
}
.page-header-left {
display: flex;
flex-direction: column;
gap: 6px;
}
.page-title {
font-size: 18px;
font-weight: 800;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 10px;
letter-spacing: -0.3px;
}
.title-bar {
width: 4px;
height: 20px;
background: linear-gradient(180deg, #2563eb 0%, #3b82f6 100%);
border-radius: 2px;
flex-shrink: 0;
}
.page-desc {
font-size: 12px;
color: #94a3b8;
margin: 0;
line-height: 1.6;
padding-left: 14px;
}
/* 底部操作按钮区 */
.footer-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px 32px 24px;
}
.footer-btn {
height: 32px;
padding: 0 20px;
font-size: 13px;
}
.footer-btn-default {
background-color: #ffffff;
border: 1px solid #d9d9d9;
color: #333;
border-radius: 6px;
}
.footer-btn-default:hover {
border-color: #4096ff;
color: #4096ff;
}
.footer-btn-primary {
background-color: #1677ff;
border-color: #1677ff;
color: #fff;
border-radius: 6px;
}
.footer-btn-primary:hover {
background-color: #4096ff;
border-color: #4096ff;
}
/* 滚动条美化 */
.form-scroll-container::-webkit-scrollbar { width: 6px; }
.form-scroll-container::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.35);
border-radius: 10px;
}
.form-scroll-container::-webkit-scrollbar-thumb:hover {
background: rgba(148, 163, 184, 0.5);
}
</style>
<!-- 全局样式覆盖 Ant Design Modal 默认外观 -->
<style>
.work-order-modal .ant-modal-content {
padding: 0 !important;
overflow: hidden;
border-radius: 8px !important;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.15) !important;
}
.work-order-modal .ant-modal-header {
display: none !important;
}
.work-order-modal .ant-modal-body {
padding: 0 !important;
}
.work-order-modal .ant-modal-close {
top: 16px !important;
right: 16px !important;
z-index: 100;
}
.work-order-modal .ant-modal-close-x {
width: 36px !important;
height: 36px !important;
line-height: 36px !important;
font-size: 16px !important;
color: #94a3b8 !important;
border-radius: 50% !important;
transition: all 0.2s;
}
.work-order-modal .ant-modal-close-x:hover {
background-color: #f1f5f9 !important;
color: #ef4444 !important;
}
</style>

Loading…
Cancel
Save