diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectPlanController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectPlanController.java index 3b62532..4b1c4b4 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectPlanController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectPlanController.java @@ -8,11 +8,16 @@ import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.business.domain.BizProjectPlan; import com.ruoyi.business.service.IBizProjectPlanService; /** * 项目策划方案Controller + * + * 角色权限: + * - doctor: 只看自己投的稿 (submitter_id = 当前用户); 新建/编辑强制 submitter_id 写自己, status 默认 '0' + * - manager / leader / sponsor / admin: 全部可见, 不强制 submitter */ @RestController @RequestMapping("/business/projectPlan") @@ -23,6 +28,11 @@ public class BizProjectPlanController extends BaseController @GetMapping("/list") public TableDataInfo list(BizProjectPlan BizProjectPlan) { + // 医生角色: 后端兜底只查自己投的稿 + String roleType = SecurityUtils.getLoginUser().getUser().getRoleType(); + if ("doctor".equals(roleType)) { + BizProjectPlan.setSubmitterId(SecurityUtils.getUserId()); + } startPage(); List list = BizProjectPlanService.selectList(BizProjectPlan); return getDataTable(list); @@ -36,12 +46,25 @@ public class BizProjectPlanController extends BaseController @PostMapping public AjaxResult add(@RequestBody BizProjectPlan BizProjectPlan) { + // 医生角色: 强制 submitter_id 写自己 + 状态兜底 '0' (未提交), 防止绕过 + String roleType = SecurityUtils.getLoginUser().getUser().getRoleType(); + if ("doctor".equals(roleType)) { + BizProjectPlan.setSubmitterId(SecurityUtils.getUserId()); + if (BizProjectPlan.getStatus() == null || BizProjectPlan.getStatus().isEmpty()) { + BizProjectPlan.setStatus("0"); + } + } return toAjax(BizProjectPlanService.insert(BizProjectPlan)); } @Log(title = "项目策划方案", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody BizProjectPlan BizProjectPlan) { + // 医生角色: 修改时也强制覆盖 submitter_id, 防止越权篡改 + String roleType = SecurityUtils.getLoginUser().getUser().getRoleType(); + if ("doctor".equals(roleType)) { + BizProjectPlan.setSubmitterId(SecurityUtils.getUserId()); + } return toAjax(BizProjectPlanService.updateByPrimaryKey(BizProjectPlan)); } @Log(title = "项目策划方案", businessType = BusinessType.DELETE) diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSubmissionController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSubmissionController.java deleted file mode 100644 index ff1bca0..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSubmissionController.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.ruoyi.business.controller; - -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import com.ruoyi.common.annotation.Log; -import com.ruoyi.common.core.controller.BaseController; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.core.page.TableDataInfo; -import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.common.utils.SecurityUtils; -import com.ruoyi.business.domain.BizSubmission; -import com.ruoyi.business.enums.SubmissionStatus; -import com.ruoyi.business.service.IBizSubmissionService; - -/** - * 投稿Controller - */ -@RestController -@RequestMapping("/business/submission") -public class BizSubmissionController extends BaseController -{ - @Autowired - private IBizSubmissionService bizSubmissionService; - @GetMapping("/list") - public TableDataInfo list(BizSubmission bizSubmission) - { - // 后端兜底: 只查当前登录用户提交的; admin 跳过此限制 - if (!SecurityUtils.isAdmin()) { - bizSubmission.setSubmitterId(SecurityUtils.getUserId()); - } - startPage(); - List list = bizSubmissionService.selectList(bizSubmission); - return getDataTable(list); - } - @GetMapping("/{submissionId}") - public AjaxResult getInfo(@PathVariable("submissionId") String submissionId) - { - return success(bizSubmissionService.getById(submissionId)); - } - @Log(title = "投稿", businessType = BusinessType.INSERT) - @PostMapping - public AjaxResult add(@RequestBody BizSubmission bizSubmission) - { - // 后端兜底: 强制写入当前登录用户, 防止前端漏传/绕过导致列表过滤查不到 - bizSubmission.setSubmitterId(SecurityUtils.getUserId()); - bizSubmission.setSubmitterName(SecurityUtils.getUsername()); - bizSubmission.setCreateBy(getUsername()); - bizSubmission.setUpdateBy(getUsername()); - // 状态: 新建默认 DRAFT(待提交); 校验必须是英文 code 之一 - if (bizSubmission.getStatus() == null || bizSubmission.getStatus().isEmpty()) { - bizSubmission.setStatus(SubmissionStatus.DRAFT.getCode()); - } else if (!SubmissionStatus.isValid(bizSubmission.getStatus())) { - return AjaxResult.error("状态值非法: " + bizSubmission.getStatus()); - } - return toAjax(bizSubmissionService.insert(bizSubmission)); - } - @Log(title = "投稿", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody BizSubmission bizSubmission) - { - // 修改时也强制覆盖 submitterId/submitterName, 防止越权篡改为他人 - bizSubmission.setSubmitterId(SecurityUtils.getUserId()); - bizSubmission.setSubmitterName(SecurityUtils.getUsername()); - bizSubmission.setUpdateBy(getUsername()); - // 状态校验 (英文 code) - if (bizSubmission.getStatus() != null && !bizSubmission.getStatus().isEmpty() - && !SubmissionStatus.isValid(bizSubmission.getStatus())) { - return AjaxResult.error("状态值非法: " + bizSubmission.getStatus()); - } - return toAjax(bizSubmissionService.updateByPrimaryKey(bizSubmission)); - } - @Log(title = "投稿", businessType = BusinessType.DELETE) - @DeleteMapping("/{ids}") - public AjaxResult remove(@PathVariable String[] ids) - { - return toAjax(bizSubmissionService.deleteByPrimaryKeys(ids)); - } -} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java index cece1ed..cd81c8b 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java @@ -17,6 +17,10 @@ public class BizProjectPlan extends BaseEntity { /** plan_direction */ @Excel(name = "plan_direction") private String planDirection; + /** 项目方向-专项计划ID (关联 biz_special_plan.id) */ + private Long planDirectionId; + /** 项目方向-专项计划名称 (LEFT JOIN biz_special_plan.title, 列表展示用) */ + private String planDirectionTitle; /** plan_category */ @Excel(name = "plan_category") private String planCategory; @@ -61,12 +65,20 @@ public class BizProjectPlan extends BaseEntity { private String auditBy; /** 审核时间 */ private String auditTime; + /** 投稿人用户ID (医生侧投稿归属, 经理/admin 录入为 NULL) */ + private Long submitterId; + /** 投稿人姓名 (LEFT JOIN biz_person.name 优先, sys_user.user_name 兜底; biz_person.user_id 已 UNIQUE, 1:1 无笛卡尔积) */ + private String submitterName; public String getPlanId() { return planId; } public void setPlanId(String planId) { this.planId = planId; } public String getPlanName() { return planName; } public void setPlanName(String planName) { this.planName = planName; } public String getPlanDirection() { return planDirection; } public void setPlanDirection(String planDirection) { this.planDirection = planDirection; } + public Long getPlanDirectionId() { return planDirectionId; } + public void setPlanDirectionId(Long planDirectionId) { this.planDirectionId = planDirectionId; } + public String getPlanDirectionTitle() { return planDirectionTitle; } + public void setPlanDirectionTitle(String planDirectionTitle) { this.planDirectionTitle = planDirectionTitle; } public String getPlanCategory() { return planCategory; } public void setPlanCategory(String planCategory) { this.planCategory = planCategory; } public String getProjectForm() { return projectForm; } @@ -91,4 +103,8 @@ public class BizProjectPlan extends BaseEntity { public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } + public Long getSubmitterId() { return submitterId; } + public void setSubmitterId(Long submitterId) { this.submitterId = submitterId; } + public String getSubmitterName() { return submitterName; } + public void setSubmitterName(String submitterName) { this.submitterName = submitterName; } } \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSubmission.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSubmission.java deleted file mode 100644 index d6e5f5b..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSubmission.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.ruoyi.business.domain; - -import java.math.BigDecimal; -import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.ruoyi.common.annotation.Excel; -import com.ruoyi.common.core.domain.BaseEntity; - -/** 投稿对象 BizSubmission */ -public class BizSubmission extends BaseEntity { - private static final long serialVersionUID = 1L; - /** subId */ - private String subId; - /** title */ - @Excel(name = "title") - private String title; - /** direction */ - @Excel(name = "direction") - private String direction; - /** project_form */ - @Excel(name = "project_form") - private String projectForm; - /** design_file_url */ - @Excel(name = "design_file_url") - private String designFileUrl; - /** status */ - @Excel(name = "status") - private String status; - /** remark */ - @Excel(name = "remark") - private String remark; - /** create_by */ - @Excel(name = "create_by") - private String createBy; - /** create_time */ - @Excel(name = "create_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date createTime; - /** update_by */ - @Excel(name = "update_by") - private String updateBy; - /** update_time */ - @Excel(name = "update_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date updateTime; - /** 投稿人用户ID */ - private Long submitterId; - /** 投稿人姓名 */ - private String submitterName; - /** 项目类别 学术会议类/专项科研类/调研征集类/慈善帮扶类/标准制定类/患者援助类/专业培训类 */ - private String projectCategory; - /** 审核意见 */ - private String auditOpinion; - /** 审核人 */ - private String auditBy; - /** 审核时间 */ - private String auditTime; - public String getSubId() { return subId; } - public void setSubId(String subId) { this.subId = subId; } - public String getTitle() { return title; } - public void setTitle(String title) { this.title = title; } - public String getDirection() { return direction; } - public void setDirection(String direction) { this.direction = direction; } - public String getProjectForm() { return projectForm; } - public void setProjectForm(String projectForm) { this.projectForm = projectForm; } - public String getProjectCategory() { return projectCategory; } - public void setProjectCategory(String projectCategory) { this.projectCategory = projectCategory; } - public String getDesignFileUrl() { return designFileUrl; } - public void setDesignFileUrl(String designFileUrl) { this.designFileUrl = designFileUrl; } - public String getStatus() { return status; } - public void setStatus(String status) { this.status = status; } - public String getRemark() { return remark; } - public void setRemark(String remark) { this.remark = remark; } - public Long getSubmitterId() { return submitterId; } - public void setSubmitterId(Long submitterId) { this.submitterId = submitterId; } - public String getSubmitterName() { return submitterName; } - public void setSubmitterName(String submitterName) { this.submitterName = submitterName; } - public String getCreateBy() { return createBy; } - public void setCreateBy(String createBy) { this.createBy = createBy; } - public Date getCreateTime() { return createTime; } - public void setCreateTime(Date createTime) { this.createTime = createTime; } - public String getUpdateBy() { return updateBy; } - public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } - public Date getUpdateTime() { return updateTime; } - public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } -} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/enums/SubmissionStatus.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/enums/SubmissionStatus.java deleted file mode 100644 index ba3bc0d..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/enums/SubmissionStatus.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.ruoyi.business.enums; - -import java.util.HashMap; -import java.util.Map; - -/** - * 投稿状态枚举 - * - * 数据库存储英文 code (PENDING/APPROVED/REJECTED/DRAFT), 不存中文也不存数字. - * 字典映射集中在这里避免散落. 前端 utils/submissionStatus.js 同步这份字典. - */ -public enum SubmissionStatus -{ - PENDING("PENDING", "待审核"), - APPROVED("APPROVED", "审核通过"), - REJECTED("REJECTED", "已退回"), - DRAFT("DRAFT", "待提交"); - - private final String code; - private final String label; - - SubmissionStatus(String code, String label) { - this.code = code; - this.label = label; - } - - public String getCode() { return code; } - public String getLabel() { return label; } - - private static final Map BY_CODE = new HashMap<>(); - static { - for (SubmissionStatus s : values()) BY_CODE.put(s.code, s); - } - - public static SubmissionStatus fromCode(String val) { - if (val == null) return null; - return BY_CODE.get(val.trim().toUpperCase()); - } - - public static boolean isValid(String val) { - return fromCode(val) != null; - } -} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSubmissionMapper.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSubmissionMapper.java deleted file mode 100644 index 9821935..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSubmissionMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ruoyi.business.mapper; -import java.util.List; -import com.ruoyi.business.domain.BizSubmission; - -/** - * 投稿Mapper接口 - */ -public interface BizSubmissionMapper -{ - BizSubmission selectByPrimaryKey(String subId); - List selectList(BizSubmission entity); - int insert(BizSubmission entity); - int updateByPrimaryKey(BizSubmission entity); - int deleteByPrimaryKey(String subId); - int deleteByPrimaryKeys(String[] subIds); -} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSubmissionService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSubmissionService.java deleted file mode 100644 index b73fae6..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSubmissionService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ruoyi.business.service; - -import java.util.List; -import com.ruoyi.business.domain.BizSubmission; - -/** - * 投稿Service接口 - */ -public interface IBizSubmissionService -{ - BizSubmission getById(String submissionId); - List selectList(BizSubmission entity); - int insert(BizSubmission entity); - int updateByPrimaryKey(BizSubmission entity); - int deleteByPrimaryKey(String submissionId); - int deleteByPrimaryKeys(String[] submissionId); -} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSubmissionServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSubmissionServiceImpl.java deleted file mode 100644 index 86ac9bc..0000000 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSubmissionServiceImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ruoyi.business.service.impl; - -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import com.ruoyi.business.domain.BizSubmission; -import com.ruoyi.business.mapper.BizSubmissionMapper; -import com.ruoyi.business.service.IBizSubmissionService; - -@Service -public class BizSubmissionServiceImpl implements IBizSubmissionService -{ - @Autowired - private BizSubmissionMapper bizSubmissionMapper; - - @Override - public BizSubmission getById(String submissionId) - { return bizSubmissionMapper.selectByPrimaryKey(submissionId); } - @Override - public List selectList(BizSubmission entity) - { return bizSubmissionMapper.selectList(entity); } - @Override - public int insert(BizSubmission entity) { com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "subId"); return bizSubmissionMapper.insert(entity); } - @Override - public int updateByPrimaryKey(BizSubmission entity) - { return bizSubmissionMapper.updateByPrimaryKey(entity); } - @Override - public int deleteByPrimaryKey(String submissionId) - { return bizSubmissionMapper.deleteByPrimaryKey(submissionId); } - @Override - public int deleteByPrimaryKeys(String[] submissionId) - { return bizSubmissionMapper.deleteByPrimaryKeys(submissionId); } -} diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml index 5a9fc66..d736067 100644 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml @@ -5,6 +5,8 @@ + + @@ -12,38 +14,59 @@ + + - select plan_id, plan_name, plan_direction, plan_category, project_form, design_file_url, status, is_settled, project_no, remark, create_by, create_time, update_by, update_time - from biz_project_plan + select p.plan_id, p.plan_name, p.plan_direction, p.plan_direction_id, + s.title as plan_direction_title, + p.plan_category, p.project_form, p.design_file_url, p.status, p.is_settled, + p.project_no, p.remark, p.submitter_id, + COALESCE(bp.name, u.user_name) as submitter_name, + p.create_by, p.create_time, p.update_by, p.update_time + from biz_project_plan p + left join biz_special_plan s on s.id = p.plan_direction_id + left join sys_user u on u.user_id = p.submitter_id + left join biz_person bp on bp.user_id = u.user_id insert into biz_project_plan plan_id, + plan_direction_id, status, remark, + submitter_id, #{planId}, + #{planDirectionId}, #{status}, #{remark}, + #{submitterId}, @@ -51,6 +74,7 @@ plan_name = #{planName}, plan_direction = #{planDirection}, + plan_direction_id = #{planDirectionId}, plan_category = #{planCategory}, project_form = #{projectForm}, design_file_url = #{designFileUrl}, @@ -61,6 +85,7 @@ audit_time = #{auditTime}, status = #{status}, remark = #{remark}, + submitter_id = #{submitterId}, where plan_id = #{planId} diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSubmissionMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSubmissionMapper.xml deleted file mode 100644 index 0db7bbb..0000000 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSubmissionMapper.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - select sub_id, title, direction, project_form, project_category, design_file_url, submitter_id, submitter_name, status, remark, create_by, create_time, update_by, update_time - from biz_submission - - - - - insert into biz_submission - - sub_id, - title, - direction, - project_form, - project_category, - design_file_url, - submitter_id, - submitter_name, - status, - remark, - create_by, - create_time, - update_by, - update_time, - - - #{subId}, - #{title}, - #{direction}, - #{projectForm}, - #{projectCategory}, - #{designFileUrl}, - #{submitterId}, - #{submitterName}, - #{status}, - #{remark}, - #{createBy}, - sysdate(), - #{updateBy}, - sysdate(), - - - - update biz_submission - - submitter_id = #{submitterId}, - submitter_name = #{submitterName}, - project_form = #{projectForm}, - project_category = #{projectCategory}, - design_file_url = #{designFileUrl}, - audit_opinion = #{auditOpinion}, - audit_by = #{auditBy}, - audit_time = #{auditTime}, - title = #{title}, - direction = #{direction}, - status = #{status}, - remark = #{remark}, - update_by = #{updateBy}, - update_time = sysdate(), - - where sub_id = #{subId} - - - delete from biz_submission where sub_id = #{subId} - - - delete from biz_submission where sub_id in - - #{subId} - - - \ No newline at end of file diff --git a/ry-api/sql/migration_submission_to_projectPlan_2026_08_18.sql b/ry-api/sql/migration_submission_to_projectPlan_2026_08_18.sql new file mode 100644 index 0000000..8f2c94c --- /dev/null +++ b/ry-api/sql/migration_submission_to_projectPlan_2026_08_18.sql @@ -0,0 +1,32 @@ +-- ============================================================ +-- 迁移脚本: biz_submission → biz_project_plan +-- 时间: 2026-08-18 +-- 说明: +-- 1. 医生侧 /doctor/submissions 原本走 biz_submission 表 +-- (controller: BizSubmissionController) +-- 2. 整改后与经理侧 /manager/plans 共用 biz_project_plan 表 +-- 3. 测试数据不迁 (用户确认价值不高) +-- 4. 测试/生产库请先备份再执行 +-- ============================================================ + +-- ============== 段 1: biz_project_plan 加投稿人字段 ============== +ALTER TABLE biz_project_plan + ADD COLUMN submitter_id BIGINT NULL COMMENT '投稿人用户ID (医生侧投稿归属, 经理/admin 录入为 NULL)'; + +CREATE INDEX idx_plan_submitter ON biz_project_plan(submitter_id); + +-- ============== 段 1.5: biz_person.user_id 加 UNIQUE,保证 LEFT JOIN 1:1 无笛卡尔积 ============== +-- 当前 dump 已确认 biz_person.user_id 无重复 (12 条数据,user_id 非空 2 条且 user_id=106/107 各一条) +ALTER TABLE biz_person + ADD UNIQUE KEY uk_person_user_id (user_id); + +-- ============== 段 2: 数据迁移 (测试数据不迁,故 0 行) ============== +-- 旧 biz_submission.status 是英文 enum: +-- DRAFT → '0' 未提交 +-- PENDING → '1' 待审核 +-- APPROVED → '2' 通过 +-- REJECTED → '3' 拒绝 +-- (用户已确认测试数据不迁) + +-- ============== 段 3: 删 biz_submission 表 ============== +DROP TABLE IF EXISTS biz_submission; \ No newline at end of file diff --git a/ry-vue3/src/router/index.js b/ry-vue3/src/router/index.js index 0d50f46..d476f38 100644 --- a/ry-vue3/src/router/index.js +++ b/ry-vue3/src/router/index.js @@ -44,8 +44,9 @@ const routes = [ { path: 'orgs', name: 'admin-orgs', component: () => import('@/views/admin/Orgs.vue'), meta: { title: '公司管理' } }, { path: 'article', name: 'admin-article', component: () => import('@/views/admin/BizArticleAdmin.vue'), meta: { title: '协议管理' } }, { path: 'article/edit/:id', name: 'admin-article-edit', component: () => import('@/views/admin/BizArticleEdit.vue'), meta: { title: '编辑文章' } }, - { path: 'special-plan', name: 'admin-special-plan', component: () => import('@/views/admin/BizSpecialPlanAdmin.vue'), meta: { title: '七大专项计划' } }, + { path: 'special-plan', name: 'admin-special-plan', component: () => import('@/views/admin/BizSpecialPlanAdmin.vue'), meta: { title: '专项计划管理' } }, { path: 'special-plan/edit/:id', name: 'admin-special-plan-edit', component: () => import('@/views/admin/BizSpecialPlanEdit.vue'), meta: { title: '编辑专项计划' } }, + { path: 'project-category', name: 'admin-project-category', component: () => import('@/views/admin/ProjectCategory.vue'), meta: { title: '项目类别管理' } }, { path: 'account', name: 'admin-account', component: () => import('@/views/admin/Account.vue'), meta: { title: '账号信息' } } ] }, @@ -92,8 +93,8 @@ const routes = [ { path: 'messages', name: 'doctor-messages', component: () => import('@/views/doctor/Messages.vue'), meta: { title: '消息通知' } }, { path: 'submissions', name: 'doctor-submissions', component: () => import('@/views/doctor/Submissions.vue'), meta: { title: '我的项目设计投稿' } }, { path: 'submission/new', name: 'doctor-submission-new', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '新建项目设计投稿' } }, - { path: 'submission/detail/:subId', name: 'doctor-submission-detail', component: () => import('@/views/doctor/SubmissionDetail.vue'), meta: { title: '投稿详情' } }, - { path: 'submission/edit/:subId', name: 'doctor-submission-edit', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '修改项目设计投稿' } }, + { path: 'submission/detail/:planId', name: 'doctor-submission-detail', component: () => import('@/views/doctor/SubmissionDetail.vue'), meta: { title: '投稿详情' } }, + { path: 'submission/edit/:planId', name: 'doctor-submission-edit', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '修改项目设计投稿' } }, { path: 'account', name: 'doctor-account', component: () => import('@/views/doctor/Account.vue'), meta: { title: '账号信息' } } ] }, diff --git a/ry-vue3/src/utils/submissionStatus.js b/ry-vue3/src/utils/submissionStatus.js deleted file mode 100644 index 8247f65..0000000 --- a/ry-vue3/src/utils/submissionStatus.js +++ /dev/null @@ -1,41 +0,0 @@ -// 投稿状态字典 - 与后端 SubmissionStatus 枚举同步 -// value 用英文 code (PENDING/APPROVED/REJECTED/DRAFT), 前端通过 label 映射中文 -// locked=true 表示锁定(不可修改/提交) -export const SUBMISSION_STATUS = { - PENDING: { code: 'PENDING', label: '待审核', type: 'warning', locked: true }, - APPROVED: { code: 'APPROVED', label: '审核通过', type: 'success', locked: true }, - REJECTED: { code: 'REJECTED', label: '已退回', type: 'danger', locked: false }, - DRAFT: { code: 'DRAFT', label: '待提交', type: 'info', locked: false } -} - -// 容错: 把数据库返回的 status (字符串) 规范成 enum, 找不到原样返回 -export function parseStatus(val) { - if (val === null || val === undefined || val === '') return null - const key = String(val).trim().toUpperCase() - return SUBMISSION_STATUS[key] ? key : null -} - -// 获取状态 label, 容错返回 val 原值 -export function statusLabel(val) { - const key = parseStatus(val) - return key ? SUBMISSION_STATUS[key].label : (val ?? '') -} - -// 获取 el-tag type -export function statusType(val) { - const key = parseStatus(val) - return key ? SUBMISSION_STATUS[key].type : 'info' -} - -// 是否锁定 (待审核/审核通过不可修改/提交) -export function isLocked(val) { - const key = parseStatus(val) - if (!key) return false - return SUBMISSION_STATUS[key].locked === true -} - -// 筛选项列表 (用于 el-select options) -export const STATUS_OPTIONS = Object.values(SUBMISSION_STATUS).map(s => ({ - label: s.label, - value: s.code -})) \ No newline at end of file diff --git a/ry-vue3/src/views/doctor/Home.vue b/ry-vue3/src/views/doctor/Home.vue index 67126ff..5be8b67 100644 --- a/ry-vue3/src/views/doctor/Home.vue +++ b/ry-vue3/src/views/doctor/Home.vue @@ -37,12 +37,12 @@ 更多 →
    -
  • +
  • - {{ s.title }} + {{ s.planName }}
    - - {{ s.status === 'done' ? '已完成' : '待签署' }} + + {{ s.status === '2' ? '已通过' : (s.status === '3' ? '已退回' : (s.status === '1' ? '审核中' : '待提交')) }}
  • 暂无待签协议
  • @@ -109,13 +109,13 @@ async function load() { upcomingMeetings.value = (data?.rows || []).slice(0, 5) } catch (e) { upcomingMeetings.value = [] } - // 待签署协议 + // 待签署协议 (改走 biz_project_plan, 后端 doctor 角色已自动按当前用户过滤) try { - const { data } = await bizList('submission', { pageNum: 1, pageSize: 5 }) + const { data } = await bizList('projectPlan', { pageNum: 1, pageSize: 5 }) pendingAgreements.value = (data?.rows || []).slice(0, 5).map(s => ({ - subId: s.subId, - title: s.title, - status: s.status === '审核通过' ? 'done' : 'pending' + planId: s.planId, + planName: s.planName, + status: s.status })) } catch (e) { pendingAgreements.value = [] } diff --git a/ry-vue3/src/views/doctor/SubmissionDetail.vue b/ry-vue3/src/views/doctor/SubmissionDetail.vue index 014ed27..4fa602e 100644 --- a/ry-vue3/src/views/doctor/SubmissionDetail.vue +++ b/ry-vue3/src/views/doctor/SubmissionDetail.vue @@ -11,12 +11,12 @@ - + - + @@ -29,7 +29,7 @@ - + @@ -57,15 +57,18 @@ - + - - {{ statusLabel(detail.status) }} - + - - + + + + + + @@ -86,6 +89,7 @@ import { ref, computed, onMounted } from 'vue' import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import request from '@/utils/request' +import AuditStatusTag from '@/components/AuditStatusTag.vue' const route = useRoute() const router = useRouter() @@ -103,24 +107,13 @@ function fmtTime(t) { try { return new Date(t).toLocaleString('zh-CN', { hour12: false }) } catch { return t } } -// 状态 → el-tag 类型映射 -const STATUS_TYPE_MAP = { - '待审核': 'warning', - '审核通过': 'success', - '已退回': 'danger', - '未结题': 'info', - '已结题': 'success' -} -function statusType(s) { - return STATUS_TYPE_MAP[s] || 'info' -} const display = computed(() => ({ - title: fmt(detail.value.title), - direction: fmt(detail.value.direction), + planName: fmt(detail.value.planName), + planDirectionTitle: fmt(detail.value.planDirectionTitle || detail.value.planDirection), projectForm: fmt(detail.value.projectForm), - projectCategory: fmt(detail.value.projectCategory), + planCategory: fmt(detail.value.planCategory), remark: fmt(detail.value.remark), - status: fmt(detail.value.status), + submitterName: fmt(detail.value.submitterName), createTime: fmtTime(detail.value.createTime) })) @@ -131,15 +124,15 @@ function goBack() { function downloadName() { const url = detail.value.designFileUrl || '' const last = url.split('/').pop() - return last || `submission-${detail.value.subId}` + return last || `submission-${detail.value.planId}` } async function load() { - const subId = route.params.subId - if (!subId) { ElMessage.warning('参数缺失'); goBack(); return } + const planId = route.params.planId + if (!planId) { ElMessage.warning('参数缺失'); goBack(); return } loading.value = true try { - const res = await request.get(`/business/submission/${subId}`) + const res = await request.get(`/business/projectPlan/${planId}`) if (res?.code === 200) { detail.value = res.data || {} } else { @@ -180,12 +173,6 @@ onMounted(load) font-size: 14px; word-break: break-all; } -.remark-cell { - min-height: 110px; - line-height: 1.6; - padding: 12px 14px; - white-space: pre-wrap; -} .readonly-form :deep(.el-link) { font-weight: 500; } .detail-actions { margin-top: 16px; } diff --git a/ry-vue3/src/views/doctor/SubmissionNew.vue b/ry-vue3/src/views/doctor/SubmissionNew.vue index de2cb5a..7c59b06 100644 --- a/ry-vue3/src/views/doctor/SubmissionNew.vue +++ b/ry-vue3/src/views/doctor/SubmissionNew.vue @@ -10,19 +10,14 @@ - - + + - - - - - - - - + + + @@ -40,16 +35,8 @@ - - - - - - - - - - + + @@ -69,7 +56,7 @@ :on-remove="onDesignRemove" > -
    将设计文件拖到此处,或点击上传
    +
    将设计文件拖到此处,或点击上传
    支持 PDF / Word / 图片, 单文件 ≤ 20MB
    @@ -100,7 +87,9 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { useRoute, useRouter } from 'vue-router' import { useUserStore } from '@/store/user' import request from '@/utils/request' +import { bizAdd, bizUpdate } from '@/api/public' import { uploadToOss } from '@/utils/oss' +import DictSelect from '@/components/DictSelect.vue' const router = useRouter() const route = useRoute() @@ -111,24 +100,33 @@ const saving = ref(false) const loadingDetail = ref(false) const designFileList = ref([]) const designUploading = ref(false) -const isEdit = !!route.params.subId +const isEdit = !!route.params.planId + +// 项目方向 options (复用 manager 侧接口) +const planDirectionOptions = ref([]) +async function loadPlanDirectionOptions() { + try { + const { data } = await request.get('/business/specialPlan/options') + planDirectionOptions.value = (data && data.data) || data || [] + } catch { planDirectionOptions.value = [] } +} const form = reactive({ - subId: null, - title: '', - direction: '', + planId: null, + planName: '', + planDirectionId: null, projectForm: '', - projectCategory: '', + planCategory: '', designFileUrl: '', - status: 'DRAFT', + status: '0', remark: '' }) const rules = { - title: [{ required: true, message: '请输入策划方案名称', trigger: 'blur' }], - direction: [{ required: true, message: '请选择项目方向', trigger: 'change' }], + planName: [{ required: true, message: '请输入策划方案名称', trigger: 'blur' }], + planDirectionId: [{ required: true, message: '请选择项目方向', trigger: 'change' }], projectForm: [{ required: true, message: '请选择项目形式', trigger: 'change' }], - projectCategory: [{ required: true, message: '请选择项目类别', trigger: 'change' }], + planCategory: [{ required: true, message: '请选择项目类别', trigger: 'change' }], designFileUrl: [{ required: true, message: '请上传设计文件', trigger: 'change' }] } @@ -151,7 +149,6 @@ async function uploadDesign(opts) { ElMessage.success('设计文件上传成功') } catch (e) { ElMessage.error('上传失败: ' + (e?.message || e)) - // 上传失败时清空 file-list,避免 el-upload 保留失败文件 designFileList.value = [] } finally { designUploading.value = false @@ -164,24 +161,23 @@ function onDesignRemove() { } async function loadDetail() { - const subId = route.params.subId - if (!subId) return + const planId = route.params.planId + if (!planId) return loadingDetail.value = true try { - const res = await request.get(`/business/submission/${subId}`) + const res = await request.get(`/business/projectPlan/${planId}`) if (res?.code === 200 && res.data) { const d = res.data - form.subId = d.subId - form.title = d.title || '' - form.direction = d.direction || '' + form.planId = d.planId + form.planName = d.planName || '' + form.planDirectionId = d.planDirectionId ?? null form.projectForm = d.projectForm || '' - form.projectCategory = d.projectCategory || '' + form.planCategory = d.planCategory || '' form.designFileUrl = d.designFileUrl || '' - form.status = d.status && String(d.status).trim() ? String(d.status).trim().toUpperCase() : 'DRAFT' + form.status = d.status && String(d.status).trim() ? String(d.status) : '0' form.remark = d.remark || '' - // 已上传的设计文件回显到 file-list if (d.designFileUrl) { - const name = d.designFileUrl.split('/').pop() || `${d.title || '设计文件'}.pdf` + const name = d.designFileUrl.split('/').pop() || `${d.planName || '设计文件'}.pdf` designFileList.value = [{ name, url: d.designFileUrl }] } } else { @@ -202,8 +198,7 @@ function goBack() { } function confirmCancel() { - // 检查表单是否有内容 - const hasContent = form.title || form.direction || form.projectForm || form.projectCategory || form.designFileUrl || form.remark + const hasContent = form.planName || form.planDirectionId || form.projectForm || form.planCategory || form.designFileUrl || form.remark if (!hasContent) { goBack(); return } ElMessageBox.confirm('确定取消新建?未保存的内容将丢失', '提示', { type: 'warning' }) .then(() => goBack()) @@ -221,29 +216,19 @@ async function onSave() { try { let payload = { ...form } if (!isEdit) { - // 新建: 强制设为 DRAFT(待提交) - payload.status = 'DRAFT' - // 写入当前登录用户信息 + // 新建: 强制 status='0' (未提交), submitter 后端兜底 + payload.status = '0' payload.submitterId = userStore.user?.userId || null - payload.submitterName = userStore.user?.userName || userStore.user?.nickName || '' } else if (!payload.status) { - // 修改: 如果状态丢失, 兜底 DRAFT(待提交) - payload.status = 'DRAFT' + payload.status = '0' } - let res if (isEdit) { - // 修改: 走 PUT /business/submission - res = await request.put('/business/submission', payload) + await bizUpdate('projectPlan', payload) } else { - // 新建: 走 POST /business/submission - res = await request.post('/business/submission', payload) - } - if (res?.code === 200) { - ElMessage.success(isEdit ? '修改成功' : '新建成功') - goBack() - } else { - ElMessage.error(res?.msg || (isEdit ? '修改失败' : '保存失败')) + await bizAdd('projectPlan', payload) } + ElMessage.success(isEdit ? '修改成功' : '新建成功') + goBack() } catch (e) { console.error('[submission-new] save failed', e) ElMessage.error(e?.msg || (isEdit ? '修改失败' : '保存失败')) @@ -253,6 +238,7 @@ async function onSave() { } onMounted(() => { + loadPlanDirectionOptions() if (isEdit) loadDetail() }) diff --git a/ry-vue3/src/views/doctor/Submissions.vue b/ry-vue3/src/views/doctor/Submissions.vue index 33dd886..71b1ac2 100644 --- a/ry-vue3/src/views/doctor/Submissions.vue +++ b/ry-vue3/src/views/doctor/Submissions.vue @@ -3,18 +3,29 @@ - - + + + + + + + + + - + + - - + + + + + @@ -28,8 +39,11 @@ - - + + + + + - + @@ -70,24 +82,42 @@