Browse Source

任务大厅和菜单样式修改

main
wang 1 month ago
parent
commit
f64afeb84f
  1. 15
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/controller/WtczController.java
  2. 9
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/entity/WtczEntity.java
  3. 6
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/form/WtczQueryForm.java
  4. 61
      smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/service/WtczService.java
  5. 14
      smart-flow-web/src/api/business/jwpy/wtcz-api.js
  6. 14
      smart-flow-web/src/layout/components/side-menu/recursion-menu.vue
  7. 14
      smart-flow-web/src/layout/components/top-side-menu/recursion-menu.vue
  8. 44
      smart-flow-web/src/store/modules/system/user.js
  9. 88
      smart-flow-web/src/views/jwpy/jwpy/flow/index.vue

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

@ -10,6 +10,7 @@ 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.vo.WtczVO;
import net.lab1024.sa.admin.module.business.jwpy.service.WtczService;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.ValidateList;
@ -69,5 +70,19 @@ public class WtczController {
public ResponseDTO<WtczVO> getDetail(@PathVariable Long id) {
return wtczService.getDetail(id);
}
@Operation(summary = "查询大厅待领工单总数")
@GetMapping("/jwpy/wtcz/unclaimedCount")
public ResponseDTO<Long> getUnclaimedCount() {
return ResponseDTO.ok(wtczService.getUnclaimedCount());
}
@Operation(summary = "随机认领一个待办工单")
@PostMapping("/jwpy/wtcz/claimRandom")
@SaCheckPermission("task:claim")
public ResponseDTO<WtczVO> claimRandom() {
Long employeeId = SmartRequestUtil.getRequestUserId();
return wtczService.claimRandom(employeeId);
}
}

9
smart-flow-api/sa-admin/src/main/java/net/lab1024/sa/admin/module/business/jwpy/domain/entity/WtczEntity.java

@ -195,5 +195,14 @@ public class WtczEntity {
@Schema(description = "群众满意情况")
private String qzmyqk;
@Schema(description = "分派认领状态: 0未认领/未分派, 1已认领/已分派")
private Integer assignStatus;
@Schema(description = "处理人/认领人员工ID (关联 t_employee.employee_id)")
private Long processorId;
@Schema(description = "工单认领时间")
private LocalDateTime claimTime;
}

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

@ -34,4 +34,10 @@ public class WtczQueryForm extends PageParam {
@Schema(description = "删除标记:0未删,1已删")
private Boolean deletedFlag;
@Schema(description = "处理人/认领人员工ID")
private Long processorId;
@Schema(description = "分派认领状态: 0未认领, 1已认领")
private Integer assignStatus;
}

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

@ -89,6 +89,16 @@ public class WtczService {
wrapper.like(WtczEntity::getSjfjxm, queryForm.getSjfjxm());
}
// 处理人/认领人员工ID过滤
if (queryForm.getProcessorId() != null) {
wrapper.eq(WtczEntity::getProcessorId, queryForm.getProcessorId());
}
// 分派认领状态过滤
if (queryForm.getAssignStatus() != null) {
wrapper.eq(WtczEntity::getAssignStatus, queryForm.getAssignStatus());
}
// 按 ID 降序排列展示
wrapper.orderByDesc(WtczEntity::getId);
@ -319,5 +329,56 @@ public class WtczService {
return ResponseDTO.ok(vo);
}
/**
* 获取当前任务大厅中待认领的工单总数
*
* @return 待领总数
*/
public Long getUnclaimedCount() {
return wtczDao.selectCount(Wrappers.<WtczEntity>lambdaQuery()
.eq(WtczEntity::getAssignStatus, 0)
.isNull(WtczEntity::getProcessorId)
);
}
/**
* 随机认领一个未分配工单带乐观锁重试防并发冲突
*
* @param employeeId 认领人员工ID
* @return 认领成功后的工单详情
*/
@Transactional(rollbackFor = Exception.class)
public ResponseDTO<WtczVO> claimRandom(Long employeeId) {
int retryTimes = 3;
for (int i = 0; i < retryTimes; i++) {
// 1. 随机筛选一条待领的工单
LambdaQueryWrapper<WtczEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(WtczEntity::getAssignStatus, 0)
.isNull(WtczEntity::getProcessorId)
.last("ORDER BY random() LIMIT 1");
WtczEntity randomEntity = wtczDao.selectOne(wrapper);
if (randomEntity == null) {
return ResponseDTO.userErrorParam("待分配的任务池已空,无工单可认领");
}
// 2. 尝试使用乐观锁加锁占位
int updated = wtczDao.update(null, Wrappers.<WtczEntity>lambdaUpdate()
.set(WtczEntity::getProcessorId, employeeId)
.set(WtczEntity::getAssignStatus, 1)
.set(WtczEntity::getClaimTime, java.time.LocalDateTime.now())
.eq(WtczEntity::getId, randomEntity.getId())
.isNull(WtczEntity::getProcessorId)
);
// 3. 认领成功,查询其完整的 VO 关联数据并返回
if (updated == 1) {
return getDetail(randomEntity.getId());
}
log.warn("工单ID {} 随机认领时发生并发冲突,开始第 {} 次重试...", randomEntity.getId(), i + 1);
}
return ResponseDTO.userErrorParam("系统当前繁忙或工单已抢空,请稍后刷新重试");
}
}

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

@ -48,5 +48,19 @@ export const wtczApi = {
*/
getDetail: (id) => {
return getRequest(`/jwpy/wtcz/detail/${id}`);
},
/**
* 随机认领一个待办工单
*/
claimRandom: () => {
return postRequest('/jwpy/wtcz/claimRandom');
},
/**
* 查询大厅可领取的工单总数
*/
getUnclaimedCount: () => {
return getRequest('/jwpy/wtcz/unclaimedCount');
}
};

14
smart-flow-web/src/layout/components/side-menu/recursion-menu.vue

@ -33,7 +33,8 @@
import SubMenu from './sub-menu.vue';
import { router } from '/@/router/index';
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { useUserStore, stateIndexMap } from '/@/store/modules/system/user';
import { useUserStore } from '/@/store/modules/system/user';
import { useDictStore } from '/@/store/modules/system/dict';
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
@ -69,10 +70,15 @@
function updateOpenKeysAndSelectKeys() {
let currentMenuId = _.toNumber(currentRoute.name);
//
//
const tabType = currentRoute.params.tabType;
if (tabType && stateIndexMap[tabType]) {
currentMenuId = currentMenuId * 100 + stateIndexMap[tabType];
if (tabType) {
const dictStore = useDictStore();
const statesDict = dictStore.getDictData('WORKFLOW_STATE') || [];
const matchIndex = statesDict.findIndex(item => item.dataValue === tabType);
if (matchIndex !== -1) {
currentMenuId = currentMenuId * 100 + (matchIndex + 1);
}
}
// watch

14
smart-flow-web/src/layout/components/top-side-menu/recursion-menu.vue

@ -29,7 +29,8 @@
import SubMenu from './sub-menu.vue';
import { router } from '/@/router/index';
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { useUserStore, stateIndexMap } from '/@/store/modules/system/user';
import { useUserStore } from '/@/store/modules/system/user';
import { useDictStore } from '/@/store/modules/system/dict';
const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
const flatPattern = computed(() => useAppConfigStore().$state.flatPattern);
@ -63,10 +64,15 @@
function updateOpenKeysAndSelectKeys() {
let currentMenuId = _.toNumber(currentRoute.name);
//
//
const tabType = currentRoute.params.tabType;
if (tabType && stateIndexMap[tabType]) {
currentMenuId = currentMenuId * 100 + stateIndexMap[tabType];
if (tabType) {
const dictStore = useDictStore();
const statesDict = dictStore.getDictData('WORKFLOW_STATE') || [];
const matchIndex = statesDict.findIndex(item => item.dataValue === tabType);
if (matchIndex !== -1) {
currentMenuId = currentMenuId * 100 + (matchIndex + 1);
}
}
// watch

44
smart-flow-web/src/store/modules/system/user.js

@ -15,6 +15,7 @@ import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
import { messageApi } from '/@/api/support/message-api.js';
import { smartSentry } from '/@/lib/smart-sentry.js';
import { localRead, localSave, localRemove } from '/@/utils/local-util';
import { useDictStore } from '/@/store/modules/system/dict';
export const useUserStore = defineStore({
@ -366,26 +367,6 @@ function buildMenuChildren(menu, allMenuList) {
}
// ---------------------------- 动态工单流转状态插拔装配 ----------------------------
export const statePool = {
'all': { title: '全部工单', queryType: 'all' },
'to_handle': { title: '待办工单', queryType: 'to_handle' },
'transferred': { title: '已流转工单', queryType: 'transferred' },
'about_to_expire': { title: '即将超期工单', queryType: 'about_to_expire' },
'expired': { title: '已超期工单', queryType: 'expired' },
'completed': { title: '已办结工单', queryType: 'completed' },
'invalid': { title: '已作废工单', queryType: 'invalid' }
};
export const businessWorkflowConfig = {
default: ['all', 'to_handle', 'transferred', 'about_to_expire', 'expired', 'completed', 'invalid']
};
// 动态根据默认数组顺序生成索引映射,防止后续修改顺序时高亮乱蹦
export const stateIndexMap = {};
businessWorkflowConfig.default.forEach((key, index) => {
stateIndexMap[key] = index + 1;
});
function injectWorkflowSubMenus(menuNode) {
if (menuNode.children && menuNode.children.length > 0) {
for (const child of menuNode.children) {
@ -395,33 +376,22 @@ function injectWorkflowSubMenus(menuNode) {
// 检测组件路径是否为警务评议与问题处置的通用工单列表
if (menuNode.component && menuNode.component.includes('jwpy/flow/index')) {
let states = businessWorkflowConfig.default;
// 支持通过后端菜单的扩展字段配置该业务支持的状态,实现可插拔
if (menuNode.extendInfo) {
try {
const config = JSON.parse(menuNode.extendInfo);
if (config.workflowStates && config.workflowStates.length > 0) {
states = config.workflowStates;
}
} catch (e) { }
}
const dictStore = useDictStore();
// 从数据字典加载流转状态项列表,实现真正的动态插拔
const statesDict = dictStore.getDictData('WORKFLOW_STATE') || [];
const sjlyNo = menuNode.path ? menuNode.path.split('/').pop() : menuNode.menuId.toString();
const children = [];
states.forEach((stateKey, index) => {
const stateObj = statePool[stateKey];
if (stateObj) {
statesDict.forEach((item, index) => {
children.push({
menuId: menuNode.menuId * 100 + (index + 1), // 生成全局唯一数字 ID
parentId: menuNode.menuId,
menuName: stateObj.title,
path: `${menuNode.path}/${stateObj.queryType}`,
menuName: item.dataLabel,
path: `${menuNode.path}/${item.dataValue}`,
component: menuNode.component,
menuType: MENU_TYPE_ENUM.MENU.value,
visibleFlag: true,
disabledFlag: false
});
}
});
menuNode.children = [...(menuNode.children || []), ...children];

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

@ -56,6 +56,12 @@
class="custom-table"
>
<template #bodyCell="{ text, record, column }">
<!-- 业务类型 Tag 展示 -->
<template v-if="column.dataIndex === 'sjlyNo'">
<a-tag :color="getBizTypeColor(text)">
{{ getBizTypeLabel(text) }}
</a-tag>
</template>
<!-- 满意度标签展示 -->
<template v-if="column.dataIndex === 'qzmyqk'">
<DictLabel :dict-code="DICT_CODE_ENUM.CALLBACK_SATISFACTION" :data-value="text" />
@ -103,7 +109,7 @@
</template>
<script setup>
import { ref, computed, reactive, onMounted, watch } from 'vue';
import { ref, computed, reactive, watch } from 'vue';
import { useRoute } from 'vue-router';
import { message, Modal } from 'ant-design-vue';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
@ -115,20 +121,27 @@ import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';
import DictSelect from '/@/components/support/dict-select/index.vue';
import DictLabel from '/@/components/support/dict-label/index.vue';
import { useDictStore } from '/@/store/modules/system/dict.js';
import { useUserStore } from '/@/store/modules/system/user.js';
const route = useRoute();
const userStore = useUserStore();
// ---------------------------- Tab ----------------------------
// ---------------------------- Tab ----------------------------
const activeTab = ref('all');
// URL
//
const isWorkbench = computed(() => {
return route.path.includes('my-workbench');
});
// URL
const currentBizTypeCode = computed(() => {
// 1. query
if (isWorkbench.value) {
return undefined;
}
if (route.query && route.query.sjlyNo) {
return route.query.sjlyNo;
}
// 2. 110
const title = route.meta?.title;
if (title) {
const dictStore = useDictStore();
@ -141,12 +154,12 @@ const currentBizTypeCode = computed(() => {
return matchItem.dataValue;
}
}
return undefined;
});
//
const pageTitle = computed(() => {
if (isWorkbench.value) return '我的工作台';
const code = currentBizTypeCode.value;
if (!code) return '警务评议通用';
const label = useDictStore().getDataLabels(DICT_CODE_ENUM.BUSINESS_CLASSIFICATION, code);
@ -155,15 +168,22 @@ const pageTitle = computed(() => {
//
const currentBizTypeName = computed(() => {
if (isWorkbench.value) return '工作台汇总';
const code = currentBizTypeCode.value;
if (!code) return '未定义';
const label = useDictStore().getDataLabels(DICT_CODE_ENUM.BUSINESS_CLASSIFICATION, code);
return label ? `${label} (${code})` : `未知小类 (${code})`;
});
// ---------------------------- ----------------------------
const columns = [
{ title: '工单受理编号', dataIndex: 'slbh', ellipsis: true },
// ---------------------------- () ----------------------------
const columns = computed(() => {
const list = [];
list.push({ title: '工单受理编号', dataIndex: 'slbh', ellipsis: true });
//
if (isWorkbench.value) {
list.push({ title: '业务类型', dataIndex: 'sjlyNo', width: 130, align: 'center' });
}
list.push(
{ title: '诉求人姓名', dataIndex: 'sqrXm', width: 100, ellipsis: true },
{ title: '联系电话', dataIndex: 'lxdh', width: 130 },
{ title: '涉及单位', dataIndex: 'sjdwMc', ellipsis: true },
@ -171,8 +191,40 @@ const columns = [
{ title: '群众满意度', dataIndex: 'qzmyqk', width: 100, align: 'center' },
{ title: '工单状态', dataIndex: 'gdzt', width: 100, align: 'center' },
{ title: '诉求时间', dataIndex: 'sqsj', width: 150 },
{ title: '操作', dataIndex: 'action', width: 160, align: 'center', fixed: 'right' },
];
{ title: '操作', dataIndex: 'action', width: 160, align: 'center', fixed: 'right' }
);
return list;
});
// ---------------------------- ----------------------------
function getBizTypeLabel(sjlyNo) {
const map = {
'110': '110接处警',
'hz': '户政窗口',
'crj': '出入境',
'cjg': '车驾管',
'ajbl': '案件办理',
'xzsp': '行政审批评议',
'xf': '信访评议',
'wq': '民辅警维权',
'za': '治安管理评议',
'js': '监所管理评议',
'xl': '巡逻防控评议',
'sqfw': '涉企服务评议'
};
return map[sjlyNo] || sjlyNo || '通用业务';
}
function getBizTypeColor(sjlyNo) {
const map = {
'110': 'red',
'hz': 'blue',
'crj': 'purple',
'cjg': 'green',
'ajbl': 'orange'
};
return map[sjlyNo] || 'cyan';
}
// ---------------------------- ----------------------------
const queryFormDefault = {
@ -181,6 +233,8 @@ const queryFormDefault = {
qzmyqk: undefined,
sjlyNo: undefined,
tabType: 'all',
processorId: undefined,
assignStatus: undefined,
pageNum: 1,
pageSize: 10,
};
@ -194,9 +248,15 @@ const total = ref(0);
async function queryData() {
tableLoading.value = true;
try {
//
if (isWorkbench.value) {
queryForm.sjlyNo = undefined;
queryForm.processorId = userStore.employeeId;
queryForm.assignStatus = 1; // /
} else {
queryForm.sjlyNo = currentBizTypeCode.value;
// Tab
queryForm.processorId = undefined;
queryForm.assignStatus = undefined;
}
queryForm.tabType = activeTab.value;
const res = await wtczApi.queryPage(queryForm);
tableData.value = res.data?.list || [];

Loading…
Cancel
Save