From e5bfa0c78ce493e38461b40ac1191de2455a2567 Mon Sep 17 00:00:00 2001 From: wangyu <823267011@qq.com> Date: Thu, 11 Jun 2026 10:12:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=9A=E8=AE=AE=E7=BA=AA=E8=A6=81=EF=BC=9A?= =?UTF-8?q?=E5=8E=9F=E5=A7=8BWIP=E7=89=88=E6=9C=AC=E7=95=99=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../OaMeetingMinutesController.java | 61 ++ .../com/ruoyi/oa/domain/OaMeetingMinutes.java | 58 ++ .../oa/domain/bo/OaMeetingMinutesBo.java | 47 ++ .../oa/domain/vo/OaMeetingMinutesVo.java | 51 ++ .../oa/mapper/OaMeetingMinutesMapper.java | 8 + .../oa/service/IOaMeetingMinutesService.java | 24 + .../impl/OaMeetingMinutesServiceImpl.java | 260 +++++++ ruoyi-ui/src/api/oa/meetingMinutes.js | 21 + ruoyi-ui/src/views/oa/meeting/index.vue | 683 ++++++++++++++++++ sql/oa_meeting.sql | 62 ++ 10 files changed, 1275 insertions(+) create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaMeetingMinutesController.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaMeetingMinutes.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaMeetingMinutesBo.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaMeetingMinutesVo.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaMeetingMinutesMapper.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaMeetingMinutesService.java create mode 100644 ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaMeetingMinutesServiceImpl.java create mode 100644 ruoyi-ui/src/api/oa/meetingMinutes.js create mode 100644 ruoyi-ui/src/views/oa/meeting/index.vue create mode 100644 sql/oa_meeting.sql diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaMeetingMinutesController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaMeetingMinutesController.java new file mode 100644 index 0000000..546bf27 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaMeetingMinutesController.java @@ -0,0 +1,61 @@ +package com.ruoyi.oa.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.oa.domain.bo.OaMeetingMinutesBo; +import com.ruoyi.oa.domain.vo.OaMeetingMinutesVo; +import com.ruoyi.oa.service.IOaMeetingMinutesService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +/** + * 会议纪要 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/oa/meetingMinutes") +public class OaMeetingMinutesController extends BaseController { + + private final IOaMeetingMinutesService service; + + @GetMapping("/list") + public TableDataInfo list(OaMeetingMinutesBo bo, PageQuery pageQuery) { + return service.queryPageList(bo, pageQuery); + } + + @GetMapping("/{id}") + public R getInfo(@NotNull @PathVariable Long id) { + return R.ok(service.queryById(id)); + } + + @Log(title = "会议纪要", businessType = BusinessType.INSERT) + @RepeatSubmit + @PostMapping + public R add(@RequestBody OaMeetingMinutesBo bo) { + return toAjax(service.insertByBo(bo)); + } + + @Log(title = "会议纪要", businessType = BusinessType.UPDATE) + @RepeatSubmit + @PutMapping + public R edit(@RequestBody OaMeetingMinutesBo bo) { + return toAjax(service.updateByBo(bo)); + } + + @Log(title = "会议纪要", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty @PathVariable Long[] ids) { + return toAjax(service.deleteWithValidByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaMeetingMinutes.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaMeetingMinutes.java new file mode 100644 index 0000000..0faa7c1 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaMeetingMinutes.java @@ -0,0 +1,58 @@ +package com.ruoyi.oa.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * 会议纪要 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("oa_meeting_minutes") +public class OaMeetingMinutes extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id") + private Long id; + + private String meetingCode; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date meetingDate; + + /** sys_oa_project.project_id */ + private Long projectId; + + private String meetingType; + private String subject; + private String location; + + /** sys_user.user_id */ + private Long hostUserId; + + /** 参会人员 user_id CSV */ + private String attendeeUserIds; + + private String topic; + private String discussion; + private String decision; + + /** 待办 JSON:[{assigneeUserId, content, deadline, status, taskId}] */ + private String tasksJson; + + /** 1=保存时自动同步生成 sys_oa_task */ + private Integer syncTask; + + @TableLogic + private String delFlag; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaMeetingMinutesBo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaMeetingMinutesBo.java new file mode 100644 index 0000000..4b158e4 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaMeetingMinutesBo.java @@ -0,0 +1,47 @@ +package com.ruoyi.oa.domain.bo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * 会议纪要 BO + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OaMeetingMinutesBo extends BaseEntity { + + private Long id; + private String meetingCode; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date meetingDate; + + private Long projectId; + private String meetingType; + private String subject; + private String location; + private Long hostUserId; + private String attendeeUserIds; + private String topic; + private String discussion; + private String decision; + private String tasksJson; + private Integer syncTask; + + /** 关键字模糊(主题/地点/项目名) */ + private String keyword; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date dateFrom; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date dateTo; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaMeetingMinutesVo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaMeetingMinutesVo.java new file mode 100644 index 0000000..779e99f --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaMeetingMinutesVo.java @@ -0,0 +1,51 @@ +package com.ruoyi.oa.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 会议纪要 VO + */ +@Data +public class OaMeetingMinutesVo implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long id; + private String meetingCode; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date meetingDate; + + private Long projectId; + /** 冗余:项目编号 / 名称(列表联表带出) */ + private String projectNum; + private String projectName; + + private String meetingType; + private String subject; + private String location; + private Long hostUserId; + /** 冗余:主持人昵称 */ + private String hostUserName; + + private String attendeeUserIds; + /** 冗余:参会人员昵称(逗号分隔) */ + private String attendeeUserNames; + + private String topic; + private String discussion; + private String decision; + private String tasksJson; + private Integer syncTask; + + private String createBy; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; + private String updateBy; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateTime; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaMeetingMinutesMapper.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaMeetingMinutesMapper.java new file mode 100644 index 0000000..234d98a --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaMeetingMinutesMapper.java @@ -0,0 +1,8 @@ +package com.ruoyi.oa.mapper; + +import com.ruoyi.common.core.mapper.BaseMapperPlus; +import com.ruoyi.oa.domain.OaMeetingMinutes; +import com.ruoyi.oa.domain.vo.OaMeetingMinutesVo; + +public interface OaMeetingMinutesMapper extends BaseMapperPlus { +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaMeetingMinutesService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaMeetingMinutesService.java new file mode 100644 index 0000000..9cb6685 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaMeetingMinutesService.java @@ -0,0 +1,24 @@ +package com.ruoyi.oa.service; + +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.oa.domain.bo.OaMeetingMinutesBo; +import com.ruoyi.oa.domain.vo.OaMeetingMinutesVo; + +import java.util.Collection; +import java.util.List; + +public interface IOaMeetingMinutesService { + + OaMeetingMinutesVo queryById(Long id); + + TableDataInfo queryPageList(OaMeetingMinutesBo bo, PageQuery pageQuery); + + List queryList(OaMeetingMinutesBo bo); + + Boolean insertByBo(OaMeetingMinutesBo bo); + + Boolean updateByBo(OaMeetingMinutesBo bo); + + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaMeetingMinutesServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaMeetingMinutesServiceImpl.java new file mode 100644 index 0000000..bd2112c --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaMeetingMinutesServiceImpl.java @@ -0,0 +1,260 @@ +package com.ruoyi.oa.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.helper.LoginHelper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.oa.domain.OaMeetingMinutes; +import com.ruoyi.oa.domain.SysOaProject; +import com.ruoyi.oa.domain.SysOaTask; +import com.ruoyi.oa.domain.bo.OaMeetingMinutesBo; +import com.ruoyi.oa.domain.vo.OaMeetingMinutesVo; +import com.ruoyi.oa.mapper.OaMeetingMinutesMapper; +import com.ruoyi.oa.mapper.SysOaProjectMapper; +import com.ruoyi.oa.mapper.SysOaTaskMapper; +import com.ruoyi.oa.service.IOaMeetingMinutesService; +import com.ruoyi.system.mapper.SysUserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class OaMeetingMinutesServiceImpl implements IOaMeetingMinutesService { + + private final OaMeetingMinutesMapper baseMapper; + private final SysOaProjectMapper projectMapper; + private final SysOaTaskMapper taskMapper; + private final SysUserMapper userMapper; + private final ObjectMapper json = new ObjectMapper(); + + @Override + public OaMeetingMinutesVo queryById(Long id) { + OaMeetingMinutesVo vo = baseMapper.selectVoById(id); + enrich(Collections.singletonList(vo)); + return vo; + } + + @Override + public TableDataInfo queryPageList(OaMeetingMinutesBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + enrich(result.getRecords()); + return TableDataInfo.build(result); + } + + @Override + public List queryList(OaMeetingMinutesBo bo) { + List list = baseMapper.selectVoList(buildQueryWrapper(bo)); + enrich(list); + return list; + } + + private LambdaQueryWrapper buildQueryWrapper(OaMeetingMinutesBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + if (bo != null) { + lqw.eq(bo.getProjectId() != null, OaMeetingMinutes::getProjectId, bo.getProjectId()); + lqw.eq(StringUtils.isNotBlank(bo.getMeetingType()), OaMeetingMinutes::getMeetingType, bo.getMeetingType()); + if (StringUtils.isNotBlank(bo.getKeyword())) { + String kw = bo.getKeyword().trim(); + lqw.and(w -> w.like(OaMeetingMinutes::getSubject, kw) + .or().like(OaMeetingMinutes::getLocation, kw)); + } + lqw.ge(bo.getDateFrom() != null, OaMeetingMinutes::getMeetingDate, bo.getDateFrom()); + lqw.le(bo.getDateTo() != null, OaMeetingMinutes::getMeetingDate, bo.getDateTo()); + } + lqw.orderByDesc(OaMeetingMinutes::getMeetingDate) + .orderByDesc(OaMeetingMinutes::getCreateTime); + return lqw; + } + + /** 给列表填上 项目名/编号、主持人/参会人员 昵称 */ + private void enrich(List list) { + if (list == null || list.isEmpty()) return; + Set projectIds = new HashSet<>(); + Set userIds = new HashSet<>(); + for (OaMeetingMinutesVo v : list) { + if (v == null) continue; + if (v.getProjectId() != null) projectIds.add(v.getProjectId()); + if (v.getHostUserId() != null) userIds.add(v.getHostUserId()); + for (Long uid : parseLongCsv(v.getAttendeeUserIds())) userIds.add(uid); + } + Map pMap = projectIds.isEmpty() ? Collections.emptyMap() + : projectMapper.selectList(new QueryWrapper().in("project_id", projectIds)) + .stream().collect(Collectors.toMap(SysOaProject::getProjectId, p -> p, (a, b) -> a)); + Map uMap = userIds.isEmpty() ? Collections.emptyMap() + : userMapper.selectList(new QueryWrapper().in("user_id", userIds)) + .stream().collect(Collectors.toMap(SysUser::getUserId, u -> u, (a, b) -> a)); + + for (OaMeetingMinutesVo v : list) { + if (v == null) continue; + if (v.getProjectId() != null) { + SysOaProject p = pMap.get(v.getProjectId()); + if (p != null) { + v.setProjectName(p.getProjectName()); + v.setProjectNum(p.getProjectNum()); + } + } + if (v.getHostUserId() != null) { + SysUser u = uMap.get(v.getHostUserId()); + if (u != null) v.setHostUserName(u.getNickName()); + } + List attIds = parseLongCsv(v.getAttendeeUserIds()); + if (!attIds.isEmpty()) { + List names = new ArrayList<>(); + for (Long uid : attIds) { + SysUser u = uMap.get(uid); + if (u != null) names.add(u.getNickName()); + } + v.setAttendeeUserNames(String.join(",", names)); + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(OaMeetingMinutesBo bo) { + OaMeetingMinutes add = BeanUtil.toBean(bo, OaMeetingMinutes.class); + if (StringUtils.isBlank(add.getMeetingCode())) { + add.setMeetingCode("MT-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); + } + if (StringUtils.isBlank(add.getMeetingType())) add.setMeetingType("other"); + if (add.getSyncTask() == null) add.setSyncTask(1); + validBeforeSave(add); + boolean ok = baseMapper.insert(add) > 0; + if (ok) { + bo.setId(add.getId()); + if (Integer.valueOf(1).equals(add.getSyncTask())) { + String updated = syncTasks(add); + if (updated != null && !updated.equals(add.getTasksJson())) { + add.setTasksJson(updated); + baseMapper.updateById(add); + } + } + } + return ok; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(OaMeetingMinutesBo bo) { + OaMeetingMinutes upd = BeanUtil.toBean(bo, OaMeetingMinutes.class); + validBeforeSave(upd); + boolean ok = baseMapper.updateById(upd) > 0; + if (ok && Integer.valueOf(1).equals(upd.getSyncTask())) { + String updated = syncTasks(upd); + if (updated != null && !updated.equals(upd.getTasksJson())) { + upd.setTasksJson(updated); + baseMapper.updateById(upd); + } + } + return ok; + } + + private void validBeforeSave(OaMeetingMinutes e) { + if (e.getMeetingDate() == null) throw new IllegalArgumentException("请选择会议日期"); + if (StringUtils.isBlank(e.getSubject())) throw new IllegalArgumentException("请输入会议主题"); + } + + /** + * 将 tasks_json 中的每个待办同步生成 sys_oa_task: + * - 若条目已有 taskId 且仍存在 → 更新内容/截止/完成状态 + * - 否则新建并把 taskId 写回 JSON + */ + private String syncTasks(OaMeetingMinutes meeting) { + if (StringUtils.isBlank(meeting.getTasksJson())) return meeting.getTasksJson(); + try { + JsonNode root = json.readTree(meeting.getTasksJson()); + if (!root.isArray()) return meeting.getTasksJson(); + ArrayNode arr = (ArrayNode) root; + Long currentUser = LoginHelper.getUserId(); + for (int i = 0; i < arr.size(); i++) { + JsonNode n = arr.get(i); + if (!n.isObject()) continue; + ObjectNode o = (ObjectNode) n; + String content = textOf(o, "content"); + if (StringUtils.isBlank(content)) continue; + + Long assignee = longOf(o, "assigneeUserId"); + String deadline = textOf(o, "deadline"); + String status = textOf(o, "status"); + Long existTaskId = longOf(o, "taskId"); + + SysOaTask t = null; + if (existTaskId != null) { + t = taskMapper.selectById(existTaskId); + } + boolean isNew = (t == null); + if (isNew) { + t = new SysOaTask(); + t.setCreateUserId(currentUser); + t.setBeginTime(meeting.getMeetingDate()); + } + t.setProjectId(meeting.getProjectId()); + t.setTaskTitle(content.length() > 200 ? content.substring(0, 200) : content); + t.setContent("来自会议纪要:" + meeting.getSubject()); + t.setWorkerId(assignee); + if (StringUtils.isNotBlank(deadline)) { + try { t.setFinishTime(new SimpleDateFormat("yyyy-MM-dd").parse(deadline)); } catch (Exception ignored) {} + } + // 任务状态:done=2 已完成;progress=1 进行中;pending=0 待办 + if ("done".equals(status)) t.setState(2L); + else if ("progress".equals(status)) t.setState(1L); + else t.setState(0L); + + if (isNew) { + taskMapper.insert(t); + o.put("taskId", t.getTaskId()); + } else { + taskMapper.updateById(t); + } + } + return json.writeValueAsString(arr); + } catch (Exception e) { + // 同步失败不影响主流程,仅日志 + return meeting.getTasksJson(); + } + } + + private String textOf(ObjectNode n, String k) { + JsonNode v = n.get(k); + return v == null || v.isNull() ? null : v.asText(); + } + + private Long longOf(ObjectNode n, String k) { + JsonNode v = n.get(k); + if (v == null || v.isNull()) return null; + try { return v.isNumber() ? v.asLong() : Long.parseLong(v.asText()); } + catch (NumberFormatException e) { return null; } + } + + private List parseLongCsv(String csv) { + if (StringUtils.isBlank(csv)) return Collections.emptyList(); + List r = new ArrayList<>(); + for (String s : csv.split(",")) { + String t = s.trim(); + if (t.isEmpty()) continue; + try { r.add(Long.parseLong(t)); } catch (NumberFormatException ignored) {} + } + return r; + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-ui/src/api/oa/meetingMinutes.js b/ruoyi-ui/src/api/oa/meetingMinutes.js new file mode 100644 index 0000000..63f1a2b --- /dev/null +++ b/ruoyi-ui/src/api/oa/meetingMinutes.js @@ -0,0 +1,21 @@ +import request from '@/utils/request' + +export function listMeetingMinutes (query) { + return request({ url: '/oa/meetingMinutes/list', method: 'get', params: query }) +} + +export function getMeetingMinutes (id) { + return request({ url: '/oa/meetingMinutes/' + id, method: 'get' }) +} + +export function addMeetingMinutes (data) { + return request({ url: '/oa/meetingMinutes', method: 'post', data }) +} + +export function updateMeetingMinutes (data) { + return request({ url: '/oa/meetingMinutes', method: 'put', data }) +} + +export function delMeetingMinutes (ids) { + return request({ url: '/oa/meetingMinutes/' + ids, method: 'delete' }) +} diff --git a/ruoyi-ui/src/views/oa/meeting/index.vue b/ruoyi-ui/src/views/oa/meeting/index.vue new file mode 100644 index 0000000..a53ebdd --- /dev/null +++ b/ruoyi-ui/src/views/oa/meeting/index.vue @@ -0,0 +1,683 @@ + + + + + diff --git a/sql/oa_meeting.sql b/sql/oa_meeting.sql new file mode 100644 index 0000000..7136cba --- /dev/null +++ b/sql/oa_meeting.sql @@ -0,0 +1,62 @@ +-- ===================================================== +-- 智能会议纪要 (Smart Meeting Minutes) +-- - 弃用 oa_meeting_project,改为绑定 sys_oa_project.project_id +-- - 主持人/参会/待办负责人 统一存 user_id +-- - 待办在保存时可同步生成 sys_oa_task +-- ===================================================== + +-- 清理上一版(如有) +DROP TABLE IF EXISTS `oa_meeting_project`; + +-- ---------------- 会议纪要 ---------------- +DROP TABLE IF EXISTS `oa_meeting_minutes`; +CREATE TABLE `oa_meeting_minutes` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `meeting_code` varchar(32) NOT NULL COMMENT '会议编号 MT-yyyyMMddHHmmss', + `meeting_date` date NOT NULL COMMENT '会议日期', + `project_id` bigint(20) DEFAULT NULL COMMENT '关联 sys_oa_project.project_id', + `meeting_type` varchar(20) DEFAULT 'other' COMMENT '类型 tech/project/client/weekly/other', + `subject` varchar(500) NOT NULL COMMENT '会议主题', + `location` varchar(255) DEFAULT NULL COMMENT '会议地点', + `host_user_id` bigint(20) DEFAULT NULL COMMENT '主持人 sys_user.user_id', + `attendee_user_ids` varchar(1000) DEFAULT NULL COMMENT '参会人员 user_id 列表(逗号分隔)', + `topic` text COMMENT '会议议题', + `discussion` text COMMENT '讨论内容', + `decision` text COMMENT '决议事项', + `tasks_json` text COMMENT '待办 JSON:[{assigneeUserId,content,deadline,status,taskId}]', + `sync_task` tinyint(1) DEFAULT 1 COMMENT '是否将待办同步为 OA 任务', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `del_flag` char(1) DEFAULT '0' COMMENT '删除标志:0正常 1删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_meeting_code` (`meeting_code`, `del_flag`), + KEY `idx_date` (`meeting_date`), + KEY `idx_project` (`project_id`), + KEY `idx_type` (`meeting_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会议纪要'; + +-- ---------------- 字典:会议类型 ---------------- +INSERT IGNORE INTO `sys_dict_type` (`dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `remark`) +VALUES ('会议类型', 'oa_meeting_type', '0', 'admin', NOW(), '智能会议纪要-会议类型'); + +INSERT IGNORE INTO `sys_dict_data` + (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`) +VALUES + (1, '技术评审', 'tech', 'oa_meeting_type', '', 'info', 'N', '0', 'admin', NOW(), ''), + (2, '项目推进', 'project', 'oa_meeting_type', '', 'primary', 'N', '0', 'admin', NOW(), ''), + (3, '客户沟通', 'client', 'oa_meeting_type', '', 'warning', 'N', '0', 'admin', NOW(), ''), + (4, '周例会', 'weekly', 'oa_meeting_type', '', 'success', 'N', '0', 'admin', NOW(), ''), + (5, '其他', 'other', 'oa_meeting_type', '', '', 'Y', '0', 'admin', NOW(), ''); + +-- ---------------- 字典:待办状态 ---------------- +INSERT IGNORE INTO `sys_dict_type` (`dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `remark`) +VALUES ('会议待办状态', 'oa_meeting_task_status', '0', 'admin', NOW(), '智能会议纪要-待办状态'); + +INSERT IGNORE INTO `sys_dict_data` + (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`) +VALUES + (1, '待办', 'pending', 'oa_meeting_task_status', '', 'info', 'Y', '0', 'admin', NOW(), ''), + (2, '进行中', 'progress', 'oa_meeting_task_status', '', 'primary', 'N', '0', 'admin', NOW(), ''), + (3, '已完成', 'done', 'oa_meeting_task_status', '', 'success', 'N', '0', 'admin', NOW(), '');