当前位置: 首页 > article >正文

Java全栈项目-办公自动化OA系统

项目简介

办公自动化系统(OA系统)是一个基于Java开发的企业级应用系统,旨在提高企业的办公效率,实现无纸化办公。本项目采用前后端分离架构,运用当下流行的技术栈,实现了一个功能完善的OA系统。

技术栈

后端技术

  • Spring Boot 2.x
  • Spring Security
  • MyBatis-Plus
  • Redis
  • MySQL
  • JWT

前端技术

  • Vue 3
  • Element Plus
  • Axios
  • Vuex
  • Vue Router

核心功能模块

  1. 用户权限管理

    • 用户管理
    • 角色管理
    • 菜单管理
    • 部门管理
  2. 审批流程管理

    • 审批类型管理
    • 审批模板配置
    • 审批流程定义
    • 审批流程实例
  3. 工作流引擎

    • Activiti工作流集成
    • 自定义表单
    • 流程设计器
    • 流程监控
  4. 消息通知

    • 站内消息
    • 邮件通知
    • 微信推送

项目亮点

  1. 分布式架构

    • 使用Spring Cloud实现微服务架构
    • 服务注册与发现
    • 负载均衡
    • 服务熔断与降级
  2. 安全性

    • 基于RBAC的权限控制
    • JWT token认证
    • 数据加密传输
    • XSS防御
  3. 高性能

    • Redis缓存优化
    • MyBatis-Plus性能优化
    • 数据库索引优化

项目部署

# 后端部署
mvn clean package
java -jar oa-system.jar

# 前端部署
npm install
npm run build

开发建议

  1. 遵循阿里巴巴Java开发规范
  2. 使用Git进行版本控制
  3. 编写完整的单元测试
  4. 注重代码质量和性能优化
  5. 做好文档记录和注释

项目展望

  1. 持续集成更多实用功能
  2. 优化用户体验
  3. 提高系统性能
  4. 加强安全性
  5. 支持更多部署方式

总结

本OA系统项目采用主流技术栈,实现了企业办公自动化的核心需求。通过分布式架构设计,确保了系统的可扩展性和高可用性。项目的开发过程注重代码质量和性能优化,为企业提供了一个可靠的办公自动化解决方案。

OA系统核心模块详解

一、用户权限管理模块

1. 用户管理

功能描述
  • 用户CRUD操作
  • 用户状态管理(启用/禁用)
  • 用户密码重置
  • 用户导入导出
  • 用户登录日志
核心代码示例
@RestController
@RequestMapping("/admin/system/sysUser")
public class SysUserController {
    
    @PostMapping("/save")
    public Result save(@RequestBody SysUser user) {
        // 密码加密
        String passwordEncode = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(passwordEncode);
        // 保存用户信息
        sysUserService.save(user);
        return Result.ok();
    }
    
    @GetMapping("/updateStatus/{id}/{status}")
    public Result updateStatus(@PathVariable Long id, @PathVariable Integer status) {
        // 更新用户状态
        sysUserService.updateStatus(id, status);
        return Result.ok();
    }
}

2. 角色管理

功能描述
  • 角色CRUD操作
  • 角色权限分配
  • 角色用户关联
  • 角色数据权限
数据库设计
-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(100) NOT NULL COMMENT '角色名称',
    role_code VARCHAR(100) COMMENT '角色编码',
    description VARCHAR(255) COMMENT '描述',
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 用户角色关联表
CREATE TABLE sys_user_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL
);

3. 菜单管理

功能描述
  • 菜单树形展示
  • 菜单CRUD操作
  • 按钮权限管理
  • 动态路由生成
核心实现
@Service
public class SysMenuServiceImpl implements SysMenuService {
    
    @Override
    public List<RouterVo> buildRouters(List<SysMenu> menus) {
        List<RouterVo> routers = new ArrayList<>();
        for (SysMenu menu : menus) {
            RouterVo router = new RouterVo();
            router.setPath(getRouterPath(menu));
            router.setComponent(menu.getComponent());
            router.setMeta(new MetaVo(menu.getName(), menu.getIcon()));
            // 处理子路由
            List<SysMenu> children = menu.getChildren();
            if (children != null && children.size() > 0) {
                router.setChildren(buildRouters(children));
            }
            routers.add(router);
        }
        return routers;
    }
}

4. 部门管理

功能描述
  • 部门树形展示
  • 部门CRUD操作
  • 部门人员管理
  • 部门数据权限
数据模型
@Data
@TableName("sys_dept")
public class SysDept {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Long parentId;
    private Integer sort;
    private String leader;
    private String phone;
    private Integer status;
    @TableField(exist = false)
    private List<SysDept> children;
}

二、审批流程管理模块

1. 审批类型管理

功能描述
  • 审批类型CRUD
  • 审批分类管理
  • 审批图标配置
  • 审批表单关联
核心实现
@Service
public class OaProcessTypeServiceImpl implements OaProcessTypeService {
    
    @Override
    public List<OaProcessType> findProcessType() {
        List<OaProcessType> processTypeList = baseMapper.selectList(null);
        Map<Long, List<OaProcessType>> typeMap = processTypeList.stream()
            .collect(Collectors.groupingBy(OaProcessType::getParentId));
        List<OaProcessType> result = new ArrayList<>();
        recursiveProcessType(result, typeMap, 0L);
        return result;
    }
}

2. 审批模板配置

功能描述
  • 模板CRUD操作
  • 表单设计器
  • 模板权限配置
  • 模板版本管理
表单设计器核心代码
export default {
  data() {
    return {
      formItems: [],
      formConf: {
        size: 'medium',
        labelPosition: 'right',
        labelWidth: '80px'
      }
    }
  },
  methods: {
    generateFormJson() {
      return {
        formItems: this.formItems,
        formConf: this.formConf
      }
    }
  }
}

3. 审批流程定义

功能描述
  • 流程设计器
  • 流程节点配置
  • 审批人设置
  • 条件分支设置
流程定义示例
@RestController
@RequestMapping("/admin/process/processDefinition")
public class ProcessDefinitionController {
    
    @PostMapping("/saveProcessDefinition")
    public Result saveProcessDefinition(@RequestBody ProcessDefinition processDefinition) {
        processDefinitionService.saveProcessDefinition(processDefinition);
        return Result.ok();
    }
}

4. 审批流程实例

功能描述
  • 流程发起
  • 流程审批
  • 流程撤回
  • 流程监控
  • 流程历史查询
流程实例核心代码
@Service
public class ProcessInstanceServiceImpl implements ProcessInstanceService {
    
    @Override
    @Transactional
    public void startProcess(ProcessFormVo processFormVo) {
        // 获取流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
            .processDefinitionKey(processFormVo.getProcessDefinitionKey())
            .latestVersion()
            .singleResult();
            
        // 启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(
            processDefinition.getId(),
            processFormVo.getBusinessKey(),
            processFormVo.getVariables()
        );
        
        // 记录流程实例信息
        ProcessRecord processRecord = new ProcessRecord();
        processRecord.setProcessInstanceId(processInstance.getId());
        processRecord.setStatus(1);
        processRecordMapper.insert(processRecord);
    }
}

总结

以上是OA系统中用户权限管理和审批流程管理两个核心模块的详细设计和实现。系统采用RBAC权限模型,结合Activiti工作流引擎,实现了完整的权限控制和灵活的审批流程管理。每个模块都遵循了高内聚低耦合的设计原则,便于后期维护和扩展。

OA系统工作流与消息模块详解

一、工作流引擎模块

1. Activiti工作流集成

配置集成
@Configuration
public class ActivitiConfig {
    
    @Bean
    public ProcessEngine processEngine(DataSource dataSource) {
        ProcessEngineConfiguration configuration = ProcessEngineConfiguration
            .createStandaloneProcessEngineConfiguration()
            .setDataSource(dataSource)
            .setDatabaseSchemaUpdate("true")
            .setAsyncExecutorActivate(false);
            
        return configuration.buildProcessEngine();
    }
    
    @Bean
    public RepositoryService repositoryService(ProcessEngine processEngine) {
        return processEngine.getRepositoryService();
    }
    
    @Bean
    public RuntimeService runtimeService(ProcessEngine processEngine) {
        return processEngine.getRuntimeService();
    }
}
核心服务实现
@Service
public class WorkflowServiceImpl implements WorkflowService {
    
    @Autowired
    private TaskService taskService;
    
    @Override
    public void completeTask(String taskId, Map<String, Object> variables) {
        // 完成任务
        taskService.complete(taskId, variables);
        
        // 记录审批历史
        saveApprovalHistory(taskId, variables);
    }
    
    @Override
    public List<Task> getUserTasks(String userId) {
        return taskService.createTaskQuery()
            .taskAssignee(userId)
            .orderByTaskCreateTime()
            .desc()
            .list();
    }
}

2. 自定义表单

表单设计器组件
<template>
  <div class="form-designer">
    <div class="component-list">
      <draggable v-model="componentList" group="form-items">
        <div v-for="item in basicComponents" :key="item.type">
          {{ item.label }}
        </div>
      </draggable>
    </div>
    
    <div class="form-canvas">
      <draggable v-model="formItems" group="form-items">
        <component 
          v-for="item in formItems"
          :key="item.id"
          :is="item.component"
          v-bind="item.props"
        />
      </draggable>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      basicComponents: [
        { type: 'input', label: '单行文本' },
        { type: 'textarea', label: '多行文本' },
        { type: 'select', label: '下拉选择' },
        { type: 'datepicker', label: '日期选择' }
      ],
      formItems: []
    }
  }
}
</script>
表单数据模型
@Data
@TableName("wf_custom_form")
public class CustomForm {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String formKey;
    
    private String formName;
    
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<FormField> fields;
    
    private String processDefinitionKey;
    
    private Integer version;
    
    private Integer status;
}

3. 流程设计器

流程设计器集成
import BpmnModeler from 'bpmn-js/lib/Modeler'

export default {
  data() {
    return {
      bpmnModeler: null
    }
  },
  
  mounted() {
    this.initBpmnModeler()
  },
  
  methods: {
    initBpmnModeler() {
      this.bpmnModeler = new BpmnModeler({
        container: '#canvas',
        additionalModules: [
          // 自定义模块
          customPalette,
          customContextPad
        ]
      })
    },
    
    async saveProcess() {
      const { xml } = await this.bpmnModeler.saveXML({ format: true })
      // 保存流程定义
      await this.saveProcessDefinition({
        processKey: this.processKey,
        processXml: xml
      })
    }
  }
}

4. 流程监控

监控服务实现
@Service
public class ProcessMonitorServiceImpl implements ProcessMonitorService {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private HistoryService historyService;
    
    @Override
    public ProcessInstanceStats getProcessStats() {
        ProcessInstanceStats stats = new ProcessInstanceStats();
        
        // 运行中的流程
        long running = runtimeService.createProcessInstanceQuery()
            .active()
            .count();
            
        // 已完成的流程
        long completed = historyService.createHistoricProcessInstanceQuery()
            .finished()
            .count();
            
        stats.setRunningCount(running);
        stats.setCompletedCount(completed);
        return stats;
    }
    
    @Override
    public List<ProcessInstanceVO> getRunningInstances() {
        return runtimeService.createProcessInstanceQuery()
            .active()
            .list()
            .stream()
            .map(this::convertToVO)
            .collect(Collectors.toList());
    }
}

二、消息通知模块

1. 站内消息

消息模型
@Data
@TableName("sys_message")
public class SysMessage {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String title;
    
    private String content;
    
    private Long senderId;
    
    private Long receiverId;
    
    private Integer messageType;
    
    private Integer readStatus;
    
    private Date createTime;
}
消息服务实现
@Service
public class MessageServiceImpl implements MessageService {
    
    @Override
    @Transactional
    public void sendMessage(MessageDTO messageDTO) {
        SysMessage message = new SysMessage();
        BeanUtils.copyProperties(messageDTO, message);
        message.setCreateTime(new Date());
        message.setReadStatus(0);
        
        messageMapper.insert(message);
        
        // 发送WebSocket通知
        webSocketService.sendMessage(
            messageDTO.getReceiverId(),
            "新消息提醒"
        );
    }
    
    @Override
    public List<SysMessage> getUserUnreadMessages(Long userId) {
        return messageMapper.selectList(
            new LambdaQueryWrapper<SysMessage>()
                .eq(SysMessage::getReceiverId, userId)
                .eq(SysMessage::getReadStatus, 0)
        );
    }
}

2. 邮件通知

邮件服务配置
@Configuration
public class EmailConfig {
    
    @Value("${spring.mail.host}")
    private String host;
    
    @Value("${spring.mail.username}")
    private String username;
    
    @Value("${spring.mail.password}")
    private String password;
    
    @Bean
    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(host);
        mailSender.setUsername(username);
        mailSender.setPassword(password);
        
        Properties props = mailSender.getJavaMailProperties();
        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        
        return mailSender;
    }
}
邮件服务实现
@Service
public class EmailServiceImpl implements EmailService {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Async
    @Override
    public void sendEmail(String to, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            
            helper.setFrom("system@oa.com");
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            
            mailSender.send(message);
        } catch (Exception e) {
            log.error("发送邮件失败", e);
            throw new BusinessException("邮件发送失败");
        }
    }
}

3. 微信推送

微信配置
@Configuration
@ConfigurationProperties(prefix = "wechat")
@Data
public class WeChatConfig {
    private String appId;
    private String appSecret;
    private String templateId;
}
微信推送服务
@Service
@Slf4j
public class WeChatServiceImpl implements WeChatService {
    
    @Autowired
    private WeChatConfig weChatConfig;
    
    @Autowired
    private RestTemplate restTemplate;
    
    @Override
    public void pushMessage(String openId, String content) {
        String accessToken = getAccessToken();
        
        String url = String.format(
            "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s",
            accessToken
        );
        
        Map<String, Object> message = new HashMap<>();
        message.put("touser", openId);
        message.put("template_id", weChatConfig.getTemplateId());
        message.put("data", buildMessageData(content));
        
        ResponseEntity<String> response = restTemplate.postForEntity(
            url,
            message,
            String.class
        );
        
        if (response.getStatusCode() != HttpStatus.OK) {
            log.error("微信消息推送失败: {}", response.getBody());
            throw new BusinessException("微信推送失败");
        }
    }
    
    private Map<String, Object> buildMessageData(String content) {
        Map<String, Object> data = new HashMap<>();
        Map<String, Object> first = new HashMap<>();
        first.put("value", "您有新的消息通知");
        first.put("color", "#173177");
        data.put("first", first);
        
        Map<String, Object> keyword1 = new HashMap<>();
        keyword1.put("value", content);
        keyword1.put("color", "#173177");
        data.put("keyword1", keyword1);
        
        return data;
    }
}

总结

工作流引擎模块和消息通知模块是OA系统的重要组成部分:

  1. 工作流引擎基于Activiti实现,提供了完整的流程设计、执行和监控功能
  2. 自定义表单支持灵活的表单设计和数据收集
  3. 消息通知模块实现了多渠道的消息推送,确保重要信息及时送达
  4. 整个系统采用异步处理和缓存机制,保证了消息处理的高效性和可靠性

OA系统前端核心模块实现

一、工作流相关前端实现

1. 流程设计器页面

<template>
  <div class="process-designer">
    <!-- 工具栏 -->
    <div class="toolbar">
      <el-button type="primary" @click="saveProcess">保存</el-button>
      <el-button @click="downloadXML">下载XML</el-button>
      <el-button @click="previewProcess">预览</el-button>
    </div>
    
    <!-- 设计器画布 -->
    <div class="canvas-container">
      <div id="canvas"></div>
    </div>
    
    <!-- 属性面板 -->
    <div class="properties-panel">
      <properties-panel
        v-if="selectedElement"
        :element="selectedElement"
        @properties-update="updateProperties"
      />
    </div>
  </div>
</template>

<script>
import BpmnModeler from 'bpmn-js/lib/Modeler'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import PropertiesPanel from './components/PropertiesPanel.vue'

export default {
  components: {
    PropertiesPanel
  },
  
  data() {
    return {
      bpmnModeler: null,
      selectedElement: null
    }
  },
  
  mounted() {
    this.initBpmnModeler()
  },
  
  methods: {
    initBpmnModeler() {
      this.bpmnModeler = new BpmnModeler({
        container: '#canvas',
        propertiesPanel: {
          parent: '#properties'
        }
      })
      
      // 监听元素选择
      this.bpmnModeler.on('selection.changed', (e) => {
        const { newSelection } = e
        this.selectedElement = newSelection[0]
      })
    },
    
    async saveProcess() {
      try {
        const { xml } = await this.bpmnModeler.saveXML({ format: true })
        const result = await this.$api.workflow.saveProcessDefinition({
          processKey: this.processKey,
          processName: this.processName,
          processXml: xml
        })
        this.$message.success('保存成功')
      } catch (error) {
        this.$message.error('保存失败')
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.process-designer {
  display: flex;
  height: 100%;
  
  .canvas-container {
    flex: 1;
    height: 100%;
    
    #canvas {
      width: 100%;
      height: 100%;
    }
  }
  
  .properties-panel {
    width: 300px;
    border-left: 1px solid #ddd;
    padding: 20px;
  }
}
</style>

2. 自定义表单设计器

<template>
  <div class="form-designer">
    <!-- 左侧组件列表 -->
    <div class="components-panel">
      <draggable 
        v-model="componentList"
        :group="{ name: 'form-items', pull: 'clone', put: false }"
        :clone="cloneComponent"
      >
        <div
          v-for="item in basicComponents"
          :key="item.type"
          class="component-item"
        >
          <i :class="item.icon"></i>
          <span>{{ item.label }}</span>
        </div>
      </draggable>
    </div>
    
    <!-- 中间画布 -->
    <div class="form-canvas">
      <draggable
        v-model="formItems"
        group="form-items"
        class="form-container"
      >
        <div
          v-for="(item, index) in formItems"
          :key="item.id"
          class="form-item"
          @click="selectItem(item)"
        >
          <component
            :is="item.component"
            v-bind="item.props"
            :disabled="true"
          />
          <div class="form-item-actions">
            <i class="el-icon-delete" @click.stop="deleteItem(index)"></i>
          </div>
        </div>
      </draggable>
    </div>
    
    <!-- 右侧属性面板 -->
    <div class="properties-panel" v-if="selectedItem">
      <el-form label-width="80px">
        <el-form-item label="标签">
          <el-input v-model="selectedItem.props.label"></el-input>
        </el-form-item>
        <el-form-item label="必填">
          <el-switch v-model="selectedItem.props.required"></el-switch>
        </el-form-item>
        <!-- 根据不同组件类型显示不同属性配置 -->
        <template v-if="selectedItem.type === 'select'">
          <el-form-item label="选项">
            <el-button size="small" @click="addOption">添加选项</el-button>
            <div v-for="(option, index) in selectedItem.props.options" :key="index">
              <el-input v-model="option.label" placeholder="选项名">
                <template #append>
                  <el-button @click="removeOption(index)">删除</el-button>
                </template>
              </el-input>
            </div>
          </el-form-item>
        </template>
      </el-form>
    </div>
  </div>
</template>

<script>
import draggable from 'vuedraggable'
import { v4 as uuidv4 } from 'uuid'

export default {
  components: {
    draggable
  },
  
  data() {
    return {
      basicComponents: [
        { type: 'input', label: '单行文本', icon: 'el-icon-edit' },
        { type: 'textarea', label: '多行文本', icon: 'el-icon-edit-outline' },
        { type: 'select', label: '下拉选择', icon: 'el-icon-arrow-down' },
        { type: 'datepicker', label: '日期选择', icon: 'el-icon-date' }
      ],
      formItems: [],
      selectedItem: null
    }
  },
  
  methods: {
    cloneComponent(item) {
      return {
        id: uuidv4(),
        type: item.type,
        component: `el-${item.type}`,
        props: {
          label: item.label,
          required: false,
          options: item.type === 'select' ? [] : undefined
        }
      }
    },
    
    selectItem(item) {
      this.selectedItem = item
    },
    
    deleteItem(index) {
      this.formItems.splice(index, 1)
      if (this.selectedItem === this.formItems[index]) {
        this.selectedItem = null
      }
    },
    
    async saveForm() {
      try {
        await this.$api.workflow.saveCustomForm({
          formKey: this.formKey,
          formName: this.formName,
          formItems: this.formItems
        })
        this.$message.success('保存成功')
      } catch (error) {
        this.$message.error('保存失败')
      }
    }
  }
}
</script>

3. 流程监控页面

<template>
  <div class="process-monitor">
    <!-- 统计卡片 -->
    <el-row :gutter="20" class="statistics">
      <el-col :span="6">
        <el-card shadow="hover">
          <template #header>运行中的流程</template>
          <div class="statistics-number">{{ stats.runningCount }}</div>
        </el-card>
      </el-col>
      <el-col :span="6">
        <el-card shadow="hover">
          <template #header>已完成的流程</template>
          <div class="statistics-number">{{ stats.completedCount }}</div>
        </el-card>
      </el-col>
    </el-row>
    
    <!-- 流程实例列表 -->
    <el-card class="process-list">
      <template #header>
        <div class="card-header">
          <span>流程实例列表</span>
          <el-button type="primary" @click="refreshList">刷新</el-button>
        </div>
      </template>
      
      <el-table :data="processList" border>
        <el-table-column prop="processInstanceId" label="实例ID" width="180" />
        <el-table-column prop="processDefinitionName" label="流程名称" />
        <el-table-column prop="startTime" label="开始时间">
          <template #default="scope">
            {{ formatDate(scope.row.startTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusText(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200">
          <template #default="scope">
            <el-button 
              type="primary" 
              size="small"
              @click="viewProcess(scope.row)"
            >
              查看
            </el-button>
            <el-button 
              type="danger" 
              size="small"
              @click="terminateProcess(scope.row)"
            >
              终止
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      
      <el-pagination
        :current-page="page.current"
        :page-size="page.size"
        :total="page.total"
        @current-change="handlePageChange"
      />
    </el-card>
    
    <!-- 流程图查看对话框 -->
    <el-dialog
      title="流程图"
      v-model="dialogVisible"
      width="80%"
    >
      <div class="process-diagram" ref="processViewer"></div>
    </el-dialog>
  </div>
</template>

<script>
import { formatDate } from '@/utils/date'
import BpmnViewer from 'bpmn-js/lib/Viewer'

export default {
  data() {
    return {
      stats: {
        runningCount: 0,
        completedCount: 0
      },
      processList: [],
      page: {
        current: 1,
        size: 10,
        total: 0
      },
      dialogVisible: false,
      bpmnViewer: null
    }
  },
  
  created() {
    this.loadStatistics()
    this.loadProcessList()
  },
  
  methods: {
    async loadStatistics() {
      const result = await this.$api.workflow.getProcessStats()
      this.stats = result.data
    },
    
    async loadProcessList() {
      const result = await this.$api.workflow.getProcessInstances({
        current: this.page.current,
        size: this.page.size
      })
      this.processList = result.data.records
      this.page.total = result.data.total
    },
    
    async viewProcess(process) {
      this.dialogVisible = true
      this.$nextTick(() => {
        if (!this.bpmnViewer) {
          this.bpmnViewer = new BpmnViewer({
            container: this.$refs.processViewer
          })
        }
        
        // 加载流程图
        this.$api.workflow.getProcessXML(process.processInstanceId)
          .then(response => {
            this.bpmnViewer.importXML(response.data)
          })
      })
    },
    
    async terminateProcess(process) {
      try {
        await this.$confirm('确认终止该流程实例?', '提示', {
          type: 'warning'
        })
        await this.$api.workflow.terminateProcess(process.processInstanceId)
        this.$message.success('操作成功')
        this.loadProcessList()
      } catch (error) {
        // 取消操作
      }
    },
    
    getStatusType(status) {
      const types = {
        1: 'primary',  // 运行中
        2: 'success',  // 已完成
        3: 'danger'    // 已终止
      }
      return types[status] || 'info'
    },
    
    getStatusText(status) {
      const texts = {
        1: '运行中',
        2: '已完成',
        3: '已终止'
      }
      return texts[status] || '未知'
    }
  }
}
</script>

二、消息通知相关前端实现

1. 消息中心页面

<template>
  <div class="message-center">
    <el-container>
      <!-- 左侧消息类型 -->
      <el-aside width="200px">
        <el-menu
          :default-active="activeType"
          @select="handleTypeSelect"
        >
          <el-menu-item index="all">
            <i class="el-icon-message"></i>
            <span>全部消息</span>
          </el-menu-item>
          <el-menu-item index="unread">
            <i class="el-icon-bell"></i>
            <span>未读消息</span>
            <el-badge 
              :value="unreadCount" 
              :hidden="!unreadCount"
              class="unread-badge"
            />
          </el-menu-item>
          <el-menu-item index="system">
            <i class="el-icon-setting"></i>
            <span>系统通知</span>
          </el-menu-item>
        </el-menu>
      </el-aside>
      
      <!-- 右侧消息列表 -->
      <el-main>
        <el-card>
          <template #header>
            <div class="message-header">
              <span>{{ getTypeTitle() }}</span>
              <div>
                <el-button 
                  type="primary" 
                  size="small"
                  @click="markAllRead"
                  v-if="activeType === 'unread'"
                >
                  全部标记已读
                </el-button>
                <el-button 
                  type="danger" 
                  size="small"
                  @click="clearMessages"
                >
                  清空消息
                </el-button>
              </div>
            </div>
          </template>
          
          <el-table :data="messageList" border>
            <el-table-column width="50">
              <template #default="scope">
                <el-badge 
                  is-dot 
                  :hidden="scope.row.readStatus === 1"
                />
              </template>
            </el-table-column>
            <el-table-column prop="title" label="标题" />
            <el-table-column prop="createTime" label="时间" width="180">
              <template #default="scope">
                {{ formatDate(scope.row.createTime) }}
              </template>
            </el-table-column>
            <el-table-column label="操作" width="150">
              <template #default="scope">
                <el-button 
                  type="text"
                  @click="viewMessage(scope.row)"
                >
                  查看
                </el-button>
                <el-button 
                  type="text" 
                  @click="deleteMessage(scope.row)"
                >
                  删除
                </el-button>
              </template>
            </el-table-column>
          </el-table>
          
          <el-pagination
            :current-page="page.current"
            :page-size="page.size"
            :total="page.total"
            @current-change="handlePageChange"
          />
        </el-card>
      </el-main>
    </el-container>
    
    <!-- 消息详情对话框 -->
    <el-dialog
      title="消息详情"
      v-model="dialogVisible"
      width="600px"
    >
      <div class="message-detail">
        <h3>{{ currentMessage.title }}</h3>
        <div class="message-time">
          {{ formatDate(currentMessage.createTime) }}
        </div>
        <div class="message-content" v-html="currentMessage.content"></div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { formatDate } from '@/utils/date'

export default {
  data() {
    return {
      activeType: 'all',
      unreadCount: 0,
      messageList: [],
      page: {
        current: 1,
        size: 10,
        total: 0
      },
      dialogVisible: false,
      currentMessage: {}
    }
  },
  
  created() {
    this.loadMessages()
    this.getUnreadCount()
    // 启动WebSocket连接
    this.initWebSocket()
  },
  
  methods: {
    async loadMessages() {
      const result = await this.$api.message.getMessages({
        type: this.activeType,
        current: this.page.current,
        size: this.page.size
      })
      this.messageList = result.data.records
      this.page.total = result.data.total
    },
    
    async getUnreadCount() {
      const result = await this.$api.message.getUnreadCount()
      this.unreadCount = result.data
    },
    
    initWebSocket() {
      const ws = new WebSocket(process.env.VUE_APP_WS_URL)
      ws.onmessage = (event) => {
        const message = JSON.parse(event.data)
        if (message.type === 'newMessage') {
          this.$notify({
            title: '新消息',
            message: message.content,
            type: 'info'
          })
          this.getUnreadCount()
          if (this.activeType === 'unread') {
            this.loadMessages()
          }
        }
      }
    },
    
    async viewMessage(message) {
      this.currentMessage = message
      this.dialogVisible = true
      
      if (message.readStatus === 0) {
        await this.$api.message.markRead(message.id)
        message.readStatus = 1
        this.getUnreadCount()
      }
    },
    
    async markAllRead() {
      await this.$api.message.markAllRead()
      this.getUnreadCount()
      this.loadMessages()
      this.$message.success('已全部标记为已读')
    }
  }
}
</script>

<style lang="scss" scoped>
.message-center {
  height: 100%;
  
  .el-aside {
    background-color: #fff;
    border-right: 1px solid #ddd;
  }
  
  .message-detail {
    padding: 20px;
    
    .message-time {
      color: #999;
      font-size: 14px;
      margin: 10px 0;
    }
    
    .message-content {
      line-height: 1.6;
    }
  }
  
  .unread-badge {
    margin-top: 10px;
  }
}
</style>

2. 消息发送组件

<template>
  <div class="message-sender">
    <el-form 
      ref="form"
      :model="messageForm"
      :rules="rules"
      label-width="80px"
    >
      <el-form-item label="接收人" prop="receiverId">
        <user-select v-model="messageForm.receiverId" />
      </el-form-item>
      
      <el-form-item label="消息类型" prop="messageType">
        <el-select v-model="messageForm.messageType">
          <el-option label="站内信" :value="1" />
          <el-option label="邮件" :value="2" />
          <el-option label="微信" :value="3" />
        </el-select>
      </el-form-item>
      
      <el-form-item label="标题" prop="title">
        <el-input v-model="messageForm.title" />
      </el-form-item>
      
      <el-form-item label="内容" prop="content">
        <el-input
          type="textarea"
          v-model="messageForm.content"
          :rows="4"
        />
      </el-form-item>
      
      <el-form-item>
        <el-button type="primary" @click="sendMessage">
          发送
        </el-button>
        <el-button @click="resetForm">
          重置
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import UserSelect from './UserSelect.vue'

export default {
  components: {
    UserSelect
  },
  
  data() {
    return {
      messageForm: {
        receiverId: '',
        messageType: 1,
        title: '',
        content: ''
      },
      rules: {
        receiverId: [
          { required: true, message: '请选择接收人' }
        ],
        messageType: [
          { required: true, message: '请选择消息类型' }
        ],
        title: [
          { required: true, message: '请输入标题' }
        ],
        content: [
          { required: true, message: '请输入内容' }
        ]
      }
    }
  },
  
  methods: {
    async sendMessage() {
      try {
        await this.$refs.form.validate()
        await this.$api.message.sendMessage(this.messageForm)
        this.$message.success('发送成功')
        this.resetForm()
      } catch (error) {
        // 表单验证失败或发送失败
      }
    },
    
    resetForm() {
      this.$refs.form.resetFields()
    }
  }
}
</script>

3. 消息通知设置

<template>
  <div class="notification-settings">
    <el-card>
      <template #header>
        <div class="card-header">
          <span>消息通知设置</span>
          <el-button type="primary" @click="saveSettings">
            保存设置
          </el-button>
        </div>
      </template>
      
      <el-form :model="settings" label-width="120px">
        <el-form-item label="站内信通知">
          <el-switch v-model="settings.enableInternalMessage" />
        </el-form-item>
        
        <el-form-item label="邮件通知">
          <el-switch v-model="settings.enableEmail" />
        </el-form-item>
        
        <template v-if="settings.enableEmail">
          <el-form-item label="邮箱地址">
            <el-input v-model="settings.email" />
          </el-form-item>
        </template>
        
        <el-form-item label="微信通知">
          <el-switch v-model="settings.enableWechat" />
        </el-form-item>
        
        <template v-if="settings.enableWechat">
          <el-form-item label="微信绑定">
            <div v-if="settings.wechatBound">
              已绑定微信
              <el-button 
                type="text"
                @click="unbindWechat"
              >
                解除绑定
              </el-button>
            </div>
            <div v-else>
              <el-button 
                type="primary"
                @click="showQrCode"
              >
                绑定微信
              </el-button>
            </div>
          </el-form-item>
        </template>
        
        <el-form-item label="通知时段">
          <el-time-picker
            v-model="settings.notifyTimeRange"
            is-range
            range-separator="至"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
          />
        </el-form-item>
      </el-form>
    </el-card>
    
    <!-- 微信二维码对话框 -->
    <el-dialog
      title="微信绑定"
      v-model="qrCodeVisible"
      width="300px"
    >
      <div class="qrcode-container">
        <img :src="qrCodeUrl" alt="微信二维码">
        <p>请使用微信扫描二维码完成绑定</p>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      settings: {
        enableInternalMessage: true,
        enableEmail: false,
        email: '',
        enableWechat: false,
        wechatBound: false,
        notifyTimeRange: []
      },
      qrCodeVisible: false,
      qrCodeUrl: ''
    }
  },
  
  created() {
    this.loadSettings()
  },
  
  methods: {
    async loadSettings() {
      const result = await this.$api.message.getNotificationSettings()
      this.settings = result.data
    },
    
    async saveSettings() {
      await this.$api.message.saveNotificationSettings(this.settings)
      this.$message.success('设置保存成功')
    },
    
    async showQrCode() {
      const result = await this.$api.message.getWechatQrCode()
      this.qrCodeUrl = result.data
      this.qrCodeVisible = true
      
      // 轮询绑定状态
      this.pollBindStatus()
    },
    
    async pollBindStatus() {
      const timer = setInterval(async () => {
        const result = await this.$api.message.checkWechatBind()
        if (result.data.bound) {
          clearInterval(timer)
          this.qrCodeVisible = false
          this.settings.wechatBound = true
          this.$message.success('微信绑定成功')
        }
      }, 2000)
      
      // 5分钟后停止轮询
      setTimeout(() => {
        clearInterval(timer)
      }, 300000)
    },
    
    async unbindWechat() {
      try {
        await this.$confirm('确认解除微信绑定?', '提示', {
          type: 'warning'
        })
        await this.$api.message.unbindWechat()
        this.settings.wechatBound = false
        this.$message.success('解除绑定成功')
      } catch (error) {
        // 取消操作
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.notification-settings {
  .qrcode-container {
    text-align: center;
    
    img {
      width: 200px;
      height: 200px;
    }
    
    p {
      margin-top: 10px;
      color: #666;
    }
  }
}
</style>

这些前端代码实现了工作流和消息通知模块的主要功能:

  1. 工作流模块:
  • 流程设计器:支持拖拽式流程设计
  • 表单设计器:支持自定义表单设计
  • 流程监控:展示流程统计和实例列表
  1. 消息通知模块:
  • 消息中心:展示各类消息,支持已读/未读管理
  • 消息发送:支持多渠道消息发送
  • 通知设置:支持个性化的消息通知配置

所有组件都采用了Element Plus组件库,并遵循Vue 3的组合式API写法。代码结构清晰,易于维护和扩展。


http://www.kler.cn/a/506501.html

相关文章:

  • Git 版本控制:.gitignore 文件完全指南
  • vue用户点进详情页再返回列表页,停留在原位置
  • Linux 进程前篇(冯诺依曼体系结构和操作系统)
  • opencv进行人脸识别环境搭建
  • Android BottomNavigationView不加icon使text垂直居中,完美解决。
  • MDX语言的数据库交互
  • 计算机网络 (41)文件传送协议
  • 【Linux探索学习】第二十六弹——进程通信:深入理解Linux中的进程通信
  • 网关相关知识
  • 计算机网络 网络层 2
  • PyCharm与GitHub完美对接: 详细步骤指南
  • 基于YOLOv8的卫星图像中船只检测系统
  • 用行动回应“实体清单”,智谱发布了一系列新模型
  • Python 如何操作 PDF 文件?
  • 2025-01-16 思考-人生下半场的归途-那温和的良夜
  • 运行fastGPT 第四步 配置ONE API 添加模型
  • 干货答疑分享记录:as导入问题,LSP含义,分屏进入SplashScreen
  • windows 电源选项卓越性能开启
  • kotlin的dagger hilt依赖注入
  • AI学习之自然语言处理(NLP)
  • 网络安全——常用语及linux系统
  • VUE学习笔记5__vue指令v-html
  • RK3576 Android14 状态栏和导航栏增加显示控制功能
  • 玩转大语言模型——使用graphRAG+Ollama构建知识图谱
  • Linux Top 命令 load average 指标解读
  • 正式开源,Doris Operator 支持高效 Kubernetes 容器化部署方案