feat(项目管理): 招标字段/负责人/导出/下拉/登录短信 完整链路

后端 (ruoyi-business)
- BizProject: +is_bid_project, +execOrgNames(GROUP_CONCAT派生), +leadUserName
- BizProjectMapper.xml: 上述字段全部贯通 selectFields/insert/update, 列表加 exec_org_names 子查询 + lead_user_name JOIN
- BizSysUserQueryMapper (新): 干净查 sys_user by role_type, 避开 @DataScope 切面污染, 用于项目负责人下拉
- BizOrgMapper.selectExecutorOrgOptions (新): JOIN sys_user (parent_user_id IS NULL) 限定 MAIN 账号, 排除同公司普通员工子账号
- BizOrgController: +/business/org/executorOptions 端点
- BizExecutionIntentController: +exportSignupExperts 端点 (POST /business/executionIntent/export)
- BizSignupExpertExportVo (新): 中文列头 Excel 导出 VO (专家姓名/科室/医院/职称/报名时间/手机号)
- BizProjectAssign / SponsorAssign 重命名 + Service 实现调整
- 删除 sql/ 下所有迁移脚本 (按用户要求不再保存 SQL 文件)

前端 (ry-vue3)
- api/system.js: +listExecutorOrgs 走新接口, listExecutor/listSupporters/listManagers 保持 listByRole 兼容
- views/manager/Projects.vue: 执行方下拉改 label=u.orgName / value=u.userId, 数据源走 listExecutorOrgs
  +execOrgNames 列展示 +导出报名专家 dropdown 项 +is_bid_project 表单字段
- views/manager/ProjectsNew.vue + ManagerProjectDetail.vue: +is_bid_project 表单/展示
- views/auth/Login.vue: +手机号验证码登录 tab (复用 SysSmsService, dev 模式 10 开头手机号固定码 1234)
- 路由/布局/角色视角多页面整理 (SponsorOrgs/People/SponsorPersonNew 等)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
郭庆泰
2026-08-17 00:34:13 +08:00
co-authored by Claude
parent ccfd03a6eb
commit e099211cbd
70 changed files with 1378 additions and 1505 deletions
@@ -211,6 +211,7 @@ public class BizAuthController extends BaseController {
// 5. 插入 biz_org (executor 类型, 主账号自己当 contact)
BizOrg org = new BizOrg();
org.setOrgId(null);
org.setUserId(userId); // 关联主账号, 让系统能反查 "我的公司"
org.setOrgName(unitName);
org.setOrgType("executor");
org.setBusinessNature(businessNature);
@@ -224,7 +225,7 @@ public class BizAuthController extends BaseController {
}
/**
* 注册赞助方 (主账号), 流程与 executor 一致:
* 注册支持方 (主账号), 流程与 executor 一致:
* 校验 → 查重 → 写 sys_user + role_type=sponsor → 写 biz_org (sponsor 类型)
*/
@PostMapping("/registerSponsor")
@@ -283,6 +284,7 @@ public class BizAuthController extends BaseController {
// 5. 写 biz_org (sponsor 类型)
BizOrg org = new BizOrg();
org.setOrgId(null);
org.setUserId(userId); // 关联主账号, 让系统能反查 "我的公司"
org.setOrgName(unitName);
org.setOrgType("sponsor");
org.setBusinessNature(businessNature);
@@ -1,6 +1,8 @@
package com.ruoyi.business.controller;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
@@ -10,9 +12,11 @@ import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.business.domain.BizExecutionIntent;
import com.ruoyi.business.domain.BizProject;
import com.ruoyi.business.domain.vo.BizSignupExpertExportVo;
import com.ruoyi.business.service.IBizExecutionIntentService;
import com.ruoyi.business.service.IBizProjectService;
@@ -119,4 +123,32 @@ public class BizExecutionIntentController extends BaseController
{
return toAjax(bizExecutionIntentService.deleteByPrimaryKeys(ids));
}
/**
* 导出报名专家 (按 projectNo 过滤, 项目管理-更多-导出报名专家)
* POST /business/executionIntent/export?projectNo=xxx
* 字段: 专家姓名 科室 医院 职称 报名时间 手机号
*/
@Log(title = "导出报名专家", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void exportSignupExperts(HttpServletResponse response, BizExecutionIntent bizExecutionIntent)
{
if (bizExecutionIntent.getProjectNo() == null || bizExecutionIntent.getProjectNo().isEmpty()) {
throw new IllegalArgumentException("projectNo 不能为空");
}
List<BizExecutionIntent> list = bizExecutionIntentService.selectList(bizExecutionIntent);
List<BizSignupExpertExportVo> exportList = new ArrayList<>(list.size());
for (BizExecutionIntent e : list) {
BizSignupExpertExportVo v = new BizSignupExpertExportVo();
v.setName(e.getName());
v.setDepartment(e.getDepartment());
v.setWorkUnit(e.getWorkUnit());
v.setPosition(e.getPosition());
v.setPhone(e.getPhone());
v.setCreateTime(e.getCreateTime());
exportList.add(v);
}
ExcelUtil<BizSignupExpertExportVo> util = new ExcelUtil<>(BizSignupExpertExportVo.class);
util.exportExcel(response, exportList, "报名专家");
}
}
@@ -1,6 +1,7 @@
package com.ruoyi.business.controller;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
@@ -12,7 +13,7 @@ import com.ruoyi.business.domain.BizOrg;
import com.ruoyi.business.service.IBizOrgService;
/**
* 公司Controller (赞助方 + 执行方 共用)
* 公司Controller (支持方 + 执行方 共用)
* GET /business/org/list?orgType=sponsor|executor
*/
@RestController
@@ -28,6 +29,34 @@ public class BizOrgController extends BaseController {
return getDataTable(list);
}
/**
* 支持方下拉选项: JOIN sys_user 取主账号 user_name, 用于 manager 项目分配弹窗
* GET /business/org/sponsorOptions?orgName=xxx
* 返回 [{ userId, orgName, userName }, ...] (不走分页)
*/
@GetMapping("/sponsorOptions")
public AjaxResult sponsorOptions(BizOrg bizOrg) {
// 强制 orgType=sponsor
bizOrg.setOrgType("sponsor");
List<Map<String, Object>> rows = bizOrgService.selectSponsorOrgOptions(bizOrg);
return success(rows);
}
/**
* 执行方下拉选项: JOIN sys_user MAIN 账号 (parent_user_id IS NULL), 用于 manager 项目分配弹窗
* GET /business/org/executorOptions?orgName=xxx
* 返回 [{ userId, orgName, userName }, ...] (不走分页)
* 关键: label=orgName (执行单位名称), value=MAIN user_id (直接写 biz_project_assign.exec_user_id)
* 已通过 SQL 限定 parent_user_id IS NULL, 自动排除同一公司下的普通员工子账号
*/
@GetMapping("/executorOptions")
public AjaxResult executorOptions(BizOrg bizOrg) {
// 强制 orgType=executor
bizOrg.setOrgType("executor");
List<Map<String, Object>> rows = bizOrgService.selectExecutorOrgOptions(bizOrg);
return success(rows);
}
@GetMapping("/{orgId}")
public AjaxResult getInfo(@PathVariable("orgId") Long orgId) {
return success(bizOrgService.getById(orgId));
@@ -20,6 +20,8 @@ import com.ruoyi.business.domain.BizProjectSponsorAssign;
import com.ruoyi.business.service.IBizProjectAssignService;
import com.ruoyi.business.service.IBizProjectRatingService;
import com.ruoyi.business.service.IBizProjectSponsorAssignService;
import com.ruoyi.system.domain.vo.SysUserExtendVo;
import com.ruoyi.business.mapper.BizSysUserQueryMapper;
/**
* 项目Controller
@@ -41,6 +43,8 @@ public class BizProjectController extends BaseController
private IBizProjectRatingService bizProjectRatingService;
@Autowired
private IBizProjectSponsorAssignService bizProjectSponsorAssignService;
@Autowired
private BizSysUserQueryMapper bizSysUserQueryMapper;
/**
* 我报名的项目 (当前用户在 biz_execution_intent 里有意向的项目)
@@ -162,10 +166,10 @@ public class BizProjectController extends BaseController
}
/**
* 赞助方分配监察员 (写 biz_project_sponsor_assign)
* 支持方分配监察员 (写 biz_project_sponsor_assign)
* POST /business/project/sponsorAssign
*/
@Log(title = "赞助方分配监察员", businessType = BusinessType.INSERT)
@Log(title = "支持方分配监察员", businessType = BusinessType.INSERT)
@PostMapping("/sponsorAssign")
public AjaxResult sponsorAssign(@RequestBody BizProjectSponsorAssign body)
{
@@ -176,11 +180,28 @@ public class BizProjectController extends BaseController
}
/**
* 赞助方批量分配监察员 (多个项目, 同一个监察员 + 同一份说明)
* 按 sys_user.role_type 查用户列表 (供前端下拉用, 不走 system:user:list 权限, 限定只查合规管理员等业务角色)
* GET /business/project/listByRole?roleType=manager&userName=xxx
* 不传 roleType 时返回空表, 防止误用全表扫描
* 走业务模块专属 mapper BizSysUserQueryMapper, 避开 system 模块的 @DataScope 切面
*/
@GetMapping("/listByRole")
public TableDataInfo listByRole(SysUserExtendVo vo)
{
if (vo.getRoleType() == null || vo.getRoleType().isEmpty()) {
return new TableDataInfo();
}
startPage();
List<SysUserExtendVo> list = bizSysUserQueryMapper.selectActiveByRole(vo);
return getDataTable(list);
}
/**
* 支持方批量分配监察员 (多个项目, 同一个监察员 + 同一份说明)
* POST /business/project/sponsorAssignBatch
* body: List<BizProjectSponsorAssign> (每个 item.projectId / monitorUserId / assignDesc / assignPoints)
*/
@Log(title = "赞助方批量分配监察员", businessType = BusinessType.INSERT)
@Log(title = "支持方批量分配监察员", businessType = BusinessType.INSERT)
@PostMapping("/sponsorAssignBatch")
public AjaxResult sponsorAssignBatch(@RequestBody List<BizProjectSponsorAssign> bodies)
{
@@ -7,17 +7,20 @@ import com.ruoyi.common.core.domain.BaseEntity;
/**
* 公司对象 biz_org
* 用途: 赞助方(sponsor) + 执行方(executor) 共用
* 用途: 支持方(sponsor) + 执行方(executor) 共用
* 重构说明: 原 biz_support_unit + biz_execution_unit 合并, 通过 org_type 区分
*/
public class BizOrg extends BaseEntity {
private static final long serialVersionUID = 1L;
/** org_id */
private Long orgId;
/** 主账号 sys_user.user_id (注册时自动关联, 一用户一类型一公司) */
@Excel(name = "user_id")
private Long userId;
/** org_name */
@Excel(name = "org_name")
private String orgName;
/** org_type: sponsor赞助方 / executor执行方 */
/** org_type: sponsor支持方 / executor执行方 */
@Excel(name = "org_type")
private String orgType;
/** 企业性质 私营/国营/中外合资/外资/其他 */
@@ -35,7 +38,7 @@ public class BizOrg extends BaseEntity {
/** contact_phone */
@Excel(name = "contact_phone")
private String contactPhone;
/** intent_count (仅赞助方用) */
/** intent_count (仅支持方用) */
@Excel(name = "intent_count")
private Integer intentCount;
/** status */
@@ -50,6 +53,8 @@ public class BizOrg extends BaseEntity {
public Long getOrgId() { return orgId; }
public void setOrgId(Long orgId) { this.orgId = orgId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getOrgName() { return orgName; }
public void setOrgName(String orgName) { this.orgName = orgName; }
public String getOrgType() { return orgType; }
@@ -32,9 +32,6 @@ public class BizPerson extends BaseEntity {
/** role */
@Excel(name = "role")
private String role;
/** 状态 0正常 1禁用 - 跟 sys_user.status 同步, 前端展示请读 sys_user */
@Deprecated
private String status;
/** create_by */
@Excel(name = "create_by")
private String createBy;
@@ -51,6 +48,9 @@ public class BizPerson extends BaseEntity {
private Date updateTime;
/** 关联系统用户ID */
private Long userId;
/** 启停状态 '0'/'1' (前端 toggle 用, BizPersonServiceImpl 同步到 sys_user.status) - 非持久化字段 */
@com.fasterxml.jackson.annotation.JsonProperty("status")
private transient String status;
/** 所属单位类型 execution执行/sponsor支持 */
private String unitType;
/** 账号类型 (来自 sys_user.account_type, MAIN=主账号 / SUB=子账号) - 仅展示用,不入库 */
@@ -81,8 +81,6 @@ public class BizPerson extends BaseEntity {
public void setPosition(String position) { this.position = position; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
/** 兼容前端调用, 实际读 sys_user.delFlag */
public String getDelFlag() { return null; }
public void setDelFlag(String delFlag) { /* noop - 跟 sys_user 同步 */ }
@@ -96,6 +94,8 @@ public class BizPerson extends BaseEntity {
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getUnitType() { return unitType; }
public void setUnitType(String unitType) { this.unitType = unitType; }
public String getAccountType() { return accountType; }
@@ -49,11 +49,15 @@ public class BizProject extends BaseEntity {
private BigDecimal ratingQ3;
/** 评分维度: 合规安全 */
private BigDecimal ratingQ4;
/** 赞助方负责人用户名(冗余) — 原 org_name */
/** 支持方负责人用户名(冗余) — 原 org_name */
@Excel(name = "sponsor_admin_user_name")
private String sponsorAdminUserName;
/** 支持单位名称 (查询参数 + 列表展示列, JOIN biz_org 取 org_name, org_type='sponsor') */
private String sponsorOrgName;
/** 服务机构名称 (查询条件, 仅匹配 org_type='executor' + org_name LIKE) */
private String execOrgName;
/** 服务机构名称列表, 多个用英文逗号连接 (GROUP_CONCAT 派生, 不入库, 仅展示) */
private String execOrgNames;
/** project_form */
@Excel(name = "project_form")
private String projectForm;
@@ -63,9 +67,17 @@ public class BizProject extends BaseEntity {
/** is_settled */
@Excel(name = "is_settled")
private String isSettled;
/** 赞助方负责人用户ID (sys_user.user_id, role_type=sponsor) — 原 org_id */
/** 支持方负责人用户ID (sys_user.user_id, role_type=sponsor) — 原 org_id */
@Excel(name = "sponsor_admin_user_id")
private Long sponsorAdminUserId;
/** 项目负责人 user_id (sys_user.user_id, role_type=manager 合规管理员) */
@Excel(name = "lead_user_id")
private Long leadUserId;
/** 项目负责人用户名 (JOIN sys_user.user_name 派生, 不入库) */
private String leadUserName;
/** 是否招标项目 Y/N */
@Excel(name = "is_bid_project")
private String isBidProject;
/** create_by */
@Excel(name = "create_by")
private String createBy;
@@ -110,7 +122,7 @@ public class BizProject extends BaseEntity {
/** 公告类型 (邀请函/支持函/通知/日程/公示) */
private String announcementType;
/* ============ 赞助方评分字段 (来自 biz_project_sponsor 中间表, 仅展示用) ============ */
/* ============ 支持方评分字段 (来自 biz_project_sponsor 中间表, 仅展示用) ============ */
/** 当前 login 用户对该项目的平均分 */
private BigDecimal sponsorRating;
private Integer sponsorQ1;
@@ -150,8 +162,12 @@ public class BizProject extends BaseEntity {
public void setRatingQ4(BigDecimal ratingQ4) { this.ratingQ4 = ratingQ4; }
public String getSponsorAdminUserName() { return sponsorAdminUserName; }
public void setSponsorAdminUserName(String sponsorAdminUserName) { this.sponsorAdminUserName = sponsorAdminUserName; }
public String getSponsorOrgName() { return sponsorOrgName; }
public void setSponsorOrgName(String sponsorOrgName) { this.sponsorOrgName = sponsorOrgName; }
public String getExecOrgName() { return execOrgName; }
public void setExecOrgName(String execOrgName) { this.execOrgName = execOrgName; }
public String getExecOrgNames() { return execOrgNames; }
public void setExecOrgNames(String execOrgNames) { this.execOrgNames = execOrgNames; }
public String getProjectForm() { return projectForm; }
public void setProjectForm(String projectForm) { this.projectForm = projectForm; }
public String getIsFinished() { return isFinished; }
@@ -160,6 +176,12 @@ public class BizProject extends BaseEntity {
public void setIsSettled(String isSettled) { this.isSettled = isSettled; }
public Long getSponsorAdminUserId() { return sponsorAdminUserId; }
public void setSponsorAdminUserId(Long sponsorAdminUserId) { this.sponsorAdminUserId = sponsorAdminUserId; }
public Long getLeadUserId() { return leadUserId; }
public void setLeadUserId(Long leadUserId) { this.leadUserId = leadUserId; }
public String getLeadUserName() { return leadUserName; }
public void setLeadUserName(String leadUserName) { this.leadUserName = leadUserName; }
public String getIsBidProject() { return isBidProject; }
public void setIsBidProject(String isBidProject) { this.isBidProject = isBidProject; }
public String getCreateBy() { return createBy; }
public void setCreateBy(String createBy) { this.createBy = createBy; }
public Date getCreateTime() { return createTime; }
@@ -3,27 +3,25 @@ package com.ruoyi.business.domain;
import java.math.BigDecimal;
import com.ruoyi.common.core.domain.BaseEntity;
/** 项目执行方分配对象 BizProjectAssign */
/** 项目执行方分配对象 BizProjectAssign
* 注: 不再缓存 execution_unit_name / exec_user_name / exec_nick_name / exec_org,
* 这 4 个字段都是 JOIN 一查就有的快照, 已从 DB DROP。
* 业务展示统一走 service 层 JOIN biz_org + sys_user。
*/
public class BizProjectAssign extends BaseEntity {
private static final long serialVersionUID = 1L;
/** assign_id */
private String assignId;
/** project_id */
private Long projectId;
/** 执行方用户ID (sys_user.user_id) */
/** 执行单位ID (FK: biz_org.org_id, NOT NULL) - service 层从 execUserId 反查 biz_org 写入 */
private Long executionUnitId;
/** 执行方用户ID (sys_user.user_id, executor 主账号) - 业务主键,前端直接传 */
private Long execUserId;
/** 执行方用户名 (denormalized) */
private String execUserName;
/** 执行方昵称 (denormalized) */
private String execNickName;
/** 执行方所属机构 (denormalized) */
private String execOrg;
/** 分配场次 */
private Integer sessions;
/** 分配金额 */
private BigDecimal amount;
/** 备注 */
private String remark;
/** 状态 0正常 1已撤销 */
private String status;
@@ -31,20 +29,14 @@ public class BizProjectAssign extends BaseEntity {
public void setAssignId(String assignId) { this.assignId = assignId; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getExecutionUnitId() { return executionUnitId; }
public void setExecutionUnitId(Long executionUnitId) { this.executionUnitId = executionUnitId; }
public Long getExecUserId() { return execUserId; }
public void setExecUserId(Long execUserId) { this.execUserId = execUserId; }
public String getExecUserName() { return execUserName; }
public void setExecUserName(String execUserName) { this.execUserName = execUserName; }
public String getExecNickName() { return execNickName; }
public void setExecNickName(String execNickName) { this.execNickName = execNickName; }
public String getExecOrg() { return execOrg; }
public void setExecOrg(String execOrg) { this.execOrg = execOrg; }
public Integer getSessions() { return sessions; }
public void setSessions(Integer sessions) { this.sessions = sessions; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
}
@@ -4,7 +4,7 @@ import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.domain.BaseEntity;
/** 项目-赞助方-监察员分配记录 (biz_project_sponsor_assign) */
/** 项目-支持方-监察员分配记录 (biz_project_sponsor_assign) */
public class BizProjectSponsorAssign extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
@@ -0,0 +1,48 @@
package com.ruoyi.business.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
/**
* 报名专家导出 VO (中文列头)
*
* <p>数据源: biz_execution_intent (公开门户"立即报名"写入的执行意向表, 即已报名专家).
* 仅用于 Excel 导出, 不参与业务逻辑.
*
* @author guoju
*/
public class BizSignupExpertExportVo {
@Excel(name = "专家姓名", sort = 1)
private String name;
@Excel(name = "科室", sort = 2)
private String department;
@Excel(name = "医院", sort = 3)
private String workUnit;
@Excel(name = "职称", sort = 4)
private String position;
@Excel(name = "报名时间", sort = 5, dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Excel(name = "手机号", sort = 6)
private String phone;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getWorkUnit() { return workUnit; }
public void setWorkUnit(String workUnit) { this.workUnit = workUnit; }
public String getPosition() { return position; }
public void setPosition(String position) { this.position = position; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
@@ -1,6 +1,7 @@
package com.ruoyi.business.mapper;
import java.util.List;
import java.util.Map;
import com.ruoyi.business.domain.BizOrg;
public interface BizOrgMapper {
@@ -9,4 +10,12 @@ public interface BizOrgMapper {
int insert(BizOrg entity);
int updateByPrimaryKey(BizOrg entity);
int deleteByPrimaryKeys(Long[] orgIds);
/** 支持方下拉选项 (JOIN sys_user.user_name), 用于 manager 项目分配弹窗 */
List<Map<String, Object>> selectSponsorOrgOptions(BizOrg entity);
/** 执行方下拉选项 (JOIN sys_user MAIN 账号 user_name), 用于 manager 项目分配弹窗
* 返回 Map: userId / orgName / userName
* 过滤条件: orgType='executor' + sys_user MAIN 账号 (parent_user_id IS NULL)
* 注意: value 必须 MAIN user_id (写入 biz_project_assign.exec_user_id);
* label 必须 org_name, 不用 user_name 以避免把同公司的普通员工带出来 */
List<Map<String, Object>> selectExecutorOrgOptions(BizOrg entity);
}
@@ -6,6 +6,6 @@ import com.ruoyi.business.domain.BizProjectSponsorAssign;
public interface BizProjectSponsorAssignMapper {
int insertAssign(BizProjectSponsorAssign entity);
List<BizProjectSponsorAssign> selectByProjectId(String projectId);
/** 按 project_id 全删 (赞助方分配: 先删后插策略) */
/** 按 project_id 全删 (支持方分配: 先删后插策略) */
int deleteByProjectId(String projectId);
}
@@ -0,0 +1,22 @@
package com.ruoyi.business.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.ruoyi.system.domain.vo.SysUserExtendVo;
/**
* 业务模块自用的 sys_user 查询 (不走 system 模块的 mapper,
* 避开 SysUserService.selectUserExtendList 上的 @DataScope 切面,
* 因此不能直接复用 /system/user/list 的权限模型)
*
* 用途: 前端下拉按 role_type 选人 (项目负责人 / 执行方 等)
*/
@Mapper
public interface BizSysUserQueryMapper
{
/**
* 按 role_type + 可选 userName 模糊查 sys_user (status='0' 正常, del_flag='0' 未删除)
* 无 dataScope 注入, 所有合规业务角色都可查
*/
List<SysUserExtendVo> selectActiveByRole(SysUserExtendVo vo);
}
@@ -1,6 +1,7 @@
package com.ruoyi.business.service;
import java.util.List;
import java.util.Map;
import com.ruoyi.business.domain.BizOrg;
public interface IBizOrgService {
@@ -9,4 +10,9 @@ public interface IBizOrgService {
int insert(BizOrg entity);
int updateByPrimaryKey(BizOrg entity);
int deleteByPrimaryKeys(Long[] orgIds);
/** 支持方下拉选项 (userId/orgName/userName), 用于 manager 项目分配弹窗 */
List<Map<String, Object>> selectSponsorOrgOptions(BizOrg entity);
/** 执行方下拉选项 (userId/orgName/userName), 用于 manager 项目分配弹窗
* 只返回 biz_org.org_type='executor' 对应的 MAIN 账号 (parent_user_id IS NULL) */
List<Map<String, Object>> selectExecutorOrgOptions(BizOrg entity);
}
@@ -4,7 +4,7 @@ import java.util.List;
import com.ruoyi.business.domain.BizProjectSponsorAssign;
public interface IBizProjectSponsorAssignService {
/** 赞助方分配 (策略: 按 project_id 先删后插, 一个项目只分配一个 sponsor) */
/** 支持方分配 (策略: 按 project_id 先删后插, 一个项目只分配一个 sponsor) */
int insertAssign(BizProjectSponsorAssign entity);
List<BizProjectSponsorAssign> listByProjectId(String projectId);
int deleteByProjectId(String projectId);
@@ -1,6 +1,7 @@
package com.ruoyi.business.service.impl;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.domain.BizOrg;
@@ -34,4 +35,14 @@ public class BizOrgServiceImpl implements IBizOrgService {
for (Long id : orgIds) { rows += bizOrgMapper.deleteByPrimaryKeys(new Long[]{id}); }
return rows;
}
@Override
public List<Map<String, Object>> selectSponsorOrgOptions(BizOrg entity) {
return bizOrgMapper.selectSponsorOrgOptions(entity);
}
@Override
public List<Map<String, Object>> selectExecutorOrgOptions(BizOrg entity) {
return bizOrgMapper.selectExecutorOrgOptions(entity);
}
}
@@ -47,20 +47,25 @@ public class BizPersonServiceImpl implements IBizPersonService
throw new ServiceException("登录账号已存在");
}
// 0.5 兜底: 如果前端没传 orgId, 按 (orgName + unitType) 查 biz_org 回填
// (前端从 org 页面跳来时已带 orgId; 直接访问 new 页面没带 orgId 时走这条路径)
if (entity.getOrgId() == null && entity.getOrgName() != null && !entity.getOrgName().isEmpty()
// 0.5 主账号 → 自己公司的 biz_org 反查 (按 user_id + org_type, 一用户一类型一公司)
// 解决: 注册时建了公司但前端拿不到, 子账号瞎填 orgName 报错的问题
if (entity.getOrgId() == null && mainUserId != null
&& entity.getUnitType() != null && !entity.getUnitType().isEmpty()) {
BizOrg q = new BizOrg();
q.setOrgName(entity.getOrgName().trim());
q.setUserId(mainUserId);
q.setOrgType(entity.getUnitType());
List<BizOrg> matched = bizOrgMapper.selectList(q);
if (matched != null && !matched.isEmpty()) {
entity.setOrgId(matched.get(0).getOrgId());
BizOrg mine = matched.get(0);
entity.setOrgId(mine.getOrgId());
// 兜底同步 orgName, 避免 biz_person.org_name 为空
if (entity.getOrgName() == null || entity.getOrgName().isEmpty()) {
entity.setOrgName(mine.getOrgName());
}
}
}
if (entity.getOrgId() == null) {
throw new ServiceException("请填写所属公司 (需先在 [支持单位管理/服务机构管理] 录入 orgType=" + entity.getUnitType() + " 的公司)");
throw new ServiceException("请填写所属公司 (主账号尚未注册公司, 请联系 admin 在 [支持单位管理/服务机构管理] 录入 orgType=" + entity.getUnitType() + " 的公司并关联 user_id)");
}
// 1. 创建 sys_user 子账号
@@ -1,17 +1,25 @@
package com.ruoyi.business.service.impl;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.business.domain.BizOrg;
import com.ruoyi.business.domain.BizProjectAssign;
import com.ruoyi.business.mapper.BizOrgMapper;
import com.ruoyi.business.mapper.BizProjectAssignMapper;
import com.ruoyi.business.service.IBizProjectAssignService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.id.SnowflakeId;
@Service
public class BizProjectAssignServiceImpl implements IBizProjectAssignService
{
@Autowired
private BizProjectAssignMapper bizProjectAssignMapper;
@Autowired
private BizOrgMapper bizOrgMapper;
@Override
public BizProjectAssign getById(String assignId) { return bizProjectAssignMapper.selectByPrimaryKey(assignId); }
@@ -22,19 +30,58 @@ public class BizProjectAssignServiceImpl implements IBizProjectAssignService
@Override
public List<BizProjectAssign> selectList(BizProjectAssign entity) { return bizProjectAssignMapper.selectList(entity); }
/**
* 插入项目执行方分配:
* 1. execUserId → biz_org(org_type='executor') 反查 org_id 写入 executionUnitId (NOT NULL, 找不到 throw)
* 2. 自动补 audit 字段 (createBy/createTime/updateBy/updateTime) 从 SecurityUtils
* 3. SnowflakeId 主键
* 4. status='0' 默认
*/
@Override
public int insert(BizProjectAssign entity) {
com.ruoyi.common.utils.id.SnowflakeId.injectIfEmpty(entity, "assignId");
if (entity.getStatus() == null) entity.setStatus("0");
// 0. 必须有 execUserId (前端必传)
if (entity.getExecUserId() == null) {
throw new ServiceException("执行方用户ID不能为空");
}
// 1. execUserId → biz_org(executor) → executionUnitId
BizOrg q = new BizOrg();
q.setUserId(entity.getExecUserId());
q.setOrgType("executor");
List<BizOrg> matched = bizOrgMapper.selectList(q);
if (matched == null || matched.isEmpty()) {
throw new ServiceException("执行方用户 " + entity.getExecUserId() + " 未关联执行单位 (请检查 biz_org.org_type='executor' + user_id 是否绑定)");
}
BizOrg org = matched.get(0);
entity.setExecutionUnitId(org.getOrgId());
// 2. audit 字段
Date now = new Date();
String operator = SecurityUtils.getUsername();
if (entity.getCreateBy() == null || entity.getCreateBy().isEmpty()) entity.setCreateBy(operator);
if (entity.getCreateTime() == null) entity.setCreateTime(now);
entity.setUpdateBy(operator);
entity.setUpdateTime(now);
// 3. 默认值
if (entity.getStatus() == null || entity.getStatus().isEmpty()) entity.setStatus("0");
// 4. SnowflakeId
SnowflakeId.injectIfEmpty(entity, "assignId");
return bizProjectAssignMapper.insert(entity);
}
@Override
public int updateByPrimaryKey(BizProjectAssign entity) { return bizProjectAssignMapper.updateByPrimaryKey(entity); }
public int updateByPrimaryKey(BizProjectAssign entity) {
entity.setUpdateBy(SecurityUtils.getUsername());
entity.setUpdateTime(new Date());
return bizProjectAssignMapper.updateByPrimaryKey(entity);
}
@Override
public int deleteByPrimaryKey(String assignId) { return bizProjectAssignMapper.deleteByPrimaryKey(assignId); }
@Override
public int deleteByProjectId(Long projectId) { return bizProjectAssignMapper.deleteByProjectId(projectId); }
}
}
@@ -14,7 +14,7 @@ public class BizProjectSponsorAssignServiceImpl implements IBizProjectSponsorAss
@Override
public int insertAssign(BizProjectSponsorAssign entity) {
// 赞助方分配策略: 一个项目只分配一个 sponsor, 先按 project_id 删, 再插
// 支持方分配策略: 一个项目只分配一个 sponsor, 先按 project_id 删, 再插
mapper.deleteByProjectId(entity.getProjectId());
return mapper.insertAssign(entity);
}
@@ -3,6 +3,7 @@
<mapper namespace="com.ruoyi.business.mapper.BizOrgMapper">
<resultMap type="BizOrg" id="BizOrgResult">
<id property="orgId" column="org_id" />
<result property="userId" column="user_id" />
<result property="orgName" column="org_name" />
<result property="orgType" column="org_type" />
<result property="businessNature" column="business_nature" />
@@ -19,7 +20,7 @@
</resultMap>
<sql id="selectFields">
select org_id, org_name, org_type, business_nature, address, tax_no, status, create_time,
select org_id, user_id, org_name, org_type, business_nature, address, tax_no, status, create_time,
contact_name, contact_phone, intent_count,
create_by, update_by, update_time
from biz_org
@@ -33,6 +34,7 @@
<select id="selectList" resultMap="BizOrgResult" parameterType="BizOrg">
<include refid="selectFields"/>
<where>
<if test="userId != null"> and user_id = #{userId}</if>
<if test="orgType != null and orgType != ''"> and org_type = #{orgType}</if>
<if test="orgName != null and orgName != ''"> and org_name like concat('%', #{orgName}, '%')</if>
<if test="taxNo != null and taxNo != ''"> and tax_no = #{taxNo}</if>
@@ -44,6 +46,7 @@
<insert id="insert" parameterType="BizOrg" useGeneratedKeys="true" keyProperty="orgId">
insert into biz_org
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">user_id,</if>
<if test="orgName != null and orgName != ''">org_name,</if>
<if test="orgType != null and orgType != ''">org_type,</if>
<if test="businessNature != null and businessNature != ''">business_nature,</if>
@@ -57,6 +60,7 @@
create_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userId != null">#{userId},</if>
<if test="orgName != null and orgName != ''">#{orgName},</if>
<if test="orgType != null and orgType != ''">#{orgType},</if>
<if test="businessNature != null and businessNature != ''">#{businessNature},</if>
@@ -74,6 +78,7 @@
<update id="updateByPrimaryKey" parameterType="BizOrg">
update biz_org
<trim prefix="SET" suffixOverrides=",">
<if test="userId != null">user_id = #{userId},</if>
<if test="orgName != null and orgName != ''">org_name = #{orgName},</if>
<if test="orgType != null and orgType != ''">org_type = #{orgType},</if>
<if test="businessNature != null and businessNature != ''">business_nature = #{businessNature},</if>
@@ -95,4 +100,46 @@
#{orgId}
</foreach>
</delete>
<!--
支持方下拉选项: JOIN sys_user 取主账号 user_name (供分配弹窗缓存 biz_project.sponsor_admin_user_name 用)
返回 Map: userId / orgName / userName
走 idx_org_user_type_name 索引 (user_id, org_type, org_name)
过滤条件: orgName 模糊匹配 (主用) / userId 精确匹配 (拉回已选项)
-->
<select id="selectSponsorOrgOptions" parameterType="BizOrg" resultType="java.util.LinkedHashMap">
select o.user_id as userId,
o.org_name as orgName,
u.user_name as userName
from biz_org o
join sys_user u on u.user_id = o.user_id
where o.org_type = 'sponsor'
and u.del_flag = '0'
<if test="userId != null">and o.user_id = #{userId}</if>
<if test="orgName != null and orgName != ''">and o.org_name like concat('%', #{orgName}, '%')</if>
order by o.org_id desc
</select>
<!--
执行方下拉选项: JOIN sys_user 取 MAIN 账号 user_name (parent_user_id IS NULL 限定主账号)
返回 Map: userId / orgName / userName
走 idx_org_user_type_name 索引 (user_id, org_type, org_name)
关键: 只查 MAIN 账号 (parent_user_id IS NULL), 过滤掉同一公司下的普通员工子账号
value=userId (MAIN 账号 sys_user.user_id, 直接写 biz_project_assign.exec_user_id)
label=orgName (执行单位名称, 不带 user_name 避免人名/昵称混淆)
过滤条件: orgName 模糊匹配 (主用) / userId 精确匹配 (拉回已选项)
-->
<select id="selectExecutorOrgOptions" parameterType="BizOrg" resultType="java.util.LinkedHashMap">
select o.user_id as userId,
o.org_name as orgName,
u.user_name as userName
from biz_org o
join sys_user u on u.user_id = o.user_id
where o.org_type = 'executor'
and u.del_flag = '0'
and u.parent_user_id is null
<if test="userId != null">and o.user_id = #{userId}</if>
<if test="orgName != null and orgName != ''">and o.org_name like concat('%', #{orgName}, '%')</if>
order by o.org_id desc
</select>
</mapper>
@@ -11,7 +11,6 @@
<result property="department" column="department" />
<result property="position" column="position" />
<result property="role" column="role" />
<result property="status" column="status" />
<result property="userId" column="user_id" />
<result property="unitType" column="unit_type" />
<result property="accountType" column="account_type" />
@@ -2,25 +2,24 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.business.mapper.BizProjectAssignMapper">
<resultMap type="BizProjectAssign" id="BizProjectAssignResult">
<id property="assignId" column="assign_id" />
<result property="projectId" column="project_id" />
<result property="execUserId" column="exec_user_id" />
<result property="execUserName" column="exec_user_name" />
<result property="execNickName" column="exec_nick_name" />
<result property="execOrg" column="exec_org" />
<result property="sessions" column="sessions" />
<result property="amount" column="amount" />
<result property="remark" column="remark" />
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<id property="assignId" column="assign_id" />
<result property="projectId" column="project_id" />
<result property="executionUnitId" column="execution_unit_id" />
<result property="execUserId" column="exec_user_id" />
<result property="sessions" column="sessions" />
<result property="amount" column="amount" />
<result property="remark" column="remark" />
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectFields">
select assign_id, project_id, exec_user_id, exec_user_name, exec_nick_name, exec_org,
sessions, amount, remark, status, create_by, create_time, update_by, update_time
select assign_id, project_id, execution_unit_id, exec_user_id,
sessions, amount, remark, status,
create_by, create_time, update_by, update_time
from biz_project_assign
</sql>
@@ -39,57 +38,60 @@
<include refid="selectFields"/>
<where>
<if test="projectId != null"> and project_id = #{projectId}</if>
<if test="executionUnitId != null"> and execution_unit_id = #{executionUnitId}</if>
<if test="execUserId != null"> and exec_user_id = #{execUserId}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
</where>
order by assign_id desc
</select>
<!--
INSERT: execution_unit_id 由 service 层从 exec_user_id 反查 biz_org 写入 (NOT NULL)
audit 字段 createBy/createTime/updateBy/updateTime 由 service 层从 SecurityUtils 写入
-->
<insert id="insert" parameterType="BizProjectAssign">
insert into biz_project_assign
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="assignId != null">assign_id,</if>
<if test="assignId != null and assignId != ''">assign_id,</if>
<if test="projectId != null">project_id,</if>
<if test="executionUnitId != null">execution_unit_id,</if>
<if test="execUserId != null">exec_user_id,</if>
<if test="execUserName != null and execUserName != ''">exec_user_name,</if>
<if test="execNickName != null and execNickName != ''">exec_nick_name,</if>
<if test="execOrg != null and execOrg != ''">exec_org,</if>
<if test="sessions != null">sessions,</if>
<if test="amount != null">amount,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="status != null and status != ''">status,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time,
<if test="createTime != null">create_time,</if>
<if test="updateBy != null and updateBy != ''">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="assignId != null">#{assignId},</if>
<if test="assignId != null and assignId != ''">#{assignId},</if>
<if test="projectId != null">#{projectId},</if>
<if test="executionUnitId != null">#{executionUnitId},</if>
<if test="execUserId != null">#{execUserId},</if>
<if test="execUserName != null and execUserName != ''">#{execUserName},</if>
<if test="execNickName != null and execNickName != ''">#{execNickName},</if>
<if test="execOrg != null and execOrg != ''">#{execOrg},</if>
<if test="sessions != null">#{sessions},</if>
<if test="amount != null">#{amount},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate(),
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateByPrimaryKey" parameterType="BizProjectAssign">
update biz_project_assign
<trim prefix="SET" suffixOverrides=",">
<if test="executionUnitId != null">execution_unit_id = #{executionUnitId},</if>
<if test="execUserId != null">exec_user_id = #{execUserId},</if>
<if test="execUserName != null and execUserName != ''">exec_user_name = #{execUserName},</if>
<if test="execNickName != null and execNickName != ''">exec_nick_name = #{execNickName},</if>
<if test="execOrg != null and execOrg != ''">exec_org = #{execOrg},</if>
<if test="sessions != null">sessions = #{sessions},</if>
<if test="amount != null">amount = #{amount},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate(),
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where assign_id = #{assignId}
</update>
@@ -101,4 +103,4 @@
<delete id="deleteByProjectId" parameterType="Long">
delete from biz_project_assign where project_id = #{projectId}
</delete>
</mapper>
</mapper>
@@ -13,16 +13,16 @@
<result property="paidLaborAmount" column="paid_labor_amount" />
<result property="paidMeetingAmount" column="paid_meeting_amount" />
<result property="ratingScore" column="rating_score" />
<result property="ratingQ1" column="rating_q1" />
<result property="ratingQ2" column="rating_q2" />
<result property="ratingQ3" column="rating_q3" />
<result property="ratingQ4" column="rating_q4" />
<result property="sponsorAdminUserName" column="sponsor_admin_user_name" />
<result property="sponsorAdminUserId" column="sponsor_admin_user_id" />
<result property="leadUserId" column="lead_user_id" />
<result property="leadUserName" column="lead_user_name" />
<result property="isBidProject" column="is_bid_project" />
<result property="sponsorOrgName" column="sponsor_org_name" />
<result property="execOrgNames" column="exec_org_names" />
<result property="projectForm" column="project_form" />
<result property="isFinished" column="is_finished" />
<result property="isSettled" column="is_settled" />
<result property="sponsorAdminUserId" column="sponsor_admin_user_id" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@@ -36,35 +36,39 @@
<result property="invitationUrl" column="invitation_url" />
<result property="supportLetterUrl" column="support_letter_url" />
<result property="publishUrl" column="publish_url" />
<result property="noticeUrl" column="notice_url" />
<result property="scheduleUrl" column="schedule_url" />
<result property="isPublished" column="is_published" />
<result property="publishTime" column="publish_time" />
<result property="announcementType" column="announcement_type" />
</resultMap>
<sql id="selectFields">
select project_id, project_no, project_name, total_sessions, done_sessions, todo_sessions, total_amount, available_amount, paid_labor_amount, paid_meeting_amount, rating_score, rating_q1, rating_q2, rating_q3, rating_q4, sponsor_admin_user_name, project_form, is_finished, is_settled, sponsor_admin_user_id, create_by, create_time, update_by, update_time, manage_fee, start_time, end_time, submit_deadline_days, support_contract_url, execute_contract_url, invitation_url, support_letter_url, publish_url, notice_url, schedule_url, is_published, publish_time, announcement_type
from biz_project
select p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions, p.todo_sessions, p.total_amount, p.available_amount, p.paid_labor_amount, p.paid_meeting_amount, p.rating_score, p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled, p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project, p.create_by, p.create_time, p.update_by, p.update_time, p.manage_fee, p.start_time, p.end_time, p.submit_deadline_days, p.support_contract_url, p.execute_contract_url, p.invitation_url, p.support_letter_url, p.publish_url,
o.org_name as sponsor_org_name,
lu.user_name as lead_user_name,
(select group_concat(distinct o2.org_name separator ',')
from biz_project_assign bpa
join biz_org o2 on o2.org_id = bpa.execution_unit_id and o2.org_type = 'executor'
where bpa.project_id = p.project_id) as exec_org_names
from biz_project p
left join biz_org o on o.user_id = p.sponsor_admin_user_id and o.org_type = 'sponsor'
left join sys_user lu on lu.user_id = p.lead_user_id
</sql>
<!-- sponsor 端专属查询: 字段 = selectFields + LEFT JOIN 当前 login 用户对该项目的评分 -->
<!-- sponsor 端专属查询: selectFields 等价 (sponsor 评分改走 biz_project_rating 子表, 这里只查项目本体) -->
<sql id="selectFieldsForSponsor">
select p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions, p.todo_sessions,
p.total_amount, p.available_amount, p.paid_labor_amount, p.paid_meeting_amount,
p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled,
p.sponsor_admin_user_id,
p.rating_score, p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled,
p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project,
p.create_by, p.create_time, p.update_by, p.update_time, p.manage_fee,
p.start_time, p.end_time, p.submit_deadline_days,
p.support_contract_url, p.execute_contract_url, p.invitation_url, p.support_letter_url,
p.publish_url, p.notice_url, p.schedule_url, p.is_published, p.publish_time, p.announcement_type,
bps.sponsor_rating as sponsor_rating,
bps.sponsor_q1 as sponsor_q1,
bps.sponsor_q2 as sponsor_q2,
bps.sponsor_q3 as sponsor_q3,
bps.sponsor_q4 as sponsor_q4,
bps.sponsor_remark as sponsor_remark
p.publish_url,
o.org_name as sponsor_org_name,
lu.user_name as lead_user_name,
(select group_concat(distinct o2.org_name separator ',')
from biz_project_assign bpa
join biz_org o2 on o2.org_id = bpa.execution_unit_id and o2.org_type = 'executor'
where bpa.project_id = p.project_id) as exec_org_names
from biz_project p
left join biz_project_sponsor bps on bps.project_id = p.project_id and bps.user_id = #{params.loginUid}
left join biz_org o on o.user_id = p.sponsor_admin_user_id and o.org_type = 'sponsor'
left join sys_user lu on lu.user_id = p.lead_user_id
</sql>
<select id="selectByPrimaryKey" resultMap="BizProjectResult" parameterType="String">
<include refid="selectFields"/>
@@ -107,29 +111,28 @@
<if test="projectNo != null and projectNo != ''">and project_no like concat('%', #{projectNo}, '%')</if>
<if test="projectName != null and projectName != ''">and project_name like concat('%', #{projectName}, '%')</if>
<if test="projectForm != null and projectForm != ''">and project_form = #{projectForm}</if>
<if test="sponsorAdminUserName != null and sponsorAdminUserName != ''">
<if test="sponsorOrgName != null and sponsorOrgName != ''">
and exists (
select 1 from sys_user su
where su.user_id = sponsor_admin_user_id
and su.role_type = 'sponsor'
and (su.user_name like concat('%', #{sponsorAdminUserName}, '%')
or su.nick_name like concat('%', #{sponsorAdminUserName}, '%'))
select 1 from biz_org o
where o.user_id = sponsor_admin_user_id
and o.org_type = 'sponsor'
and o.org_name like concat('%', #{sponsorOrgName}, '%')
)
</if>
<if test="execOrgName != null and execOrgName != ''">
and exists (
select 1 from biz_project_assign bpa
join biz_org o2 on o2.org_id = bpa.execution_unit_id and o2.org_type = 'executor'
join sys_user su on su.user_id = bpa.exec_user_id
where bpa.project_id = project_id
and su.role_type = 'executor'
and (su.user_name like concat('%', #{execOrgName}, '%')
or su.nick_name like concat('%', #{execOrgName}, '%')
or bpa.exec_org like concat('%', #{execOrgName}, '%'))
and (o2.org_name like concat('%', #{execOrgName}, '%')
or su.user_name like concat('%', #{execOrgName}, '%')
or su.nick_name like concat('%', #{execOrgName}, '%'))
)
</if>
<if test="isFinished != null and isFinished != ''">and is_finished = #{isFinished}</if>
<if test="isSettled != null and isSettled != ''">and is_settled = #{isSettled}</if>
<if test="isPublished != null and isPublished != ''">and is_published = #{isPublished}</if>
</where>
order by project_id desc
</select>
@@ -147,15 +150,13 @@
<if test="paidLaborAmount != null">paid_labor_amount,</if>
<if test="paidMeetingAmount != null">paid_meeting_amount,</if>
<if test="ratingScore != null">rating_score,</if>
<if test="ratingQ1 != null">rating_q1,</if>
<if test="ratingQ2 != null">rating_q2,</if>
<if test="ratingQ3 != null">rating_q3,</if>
<if test="ratingQ4 != null">rating_q4,</if>
<if test="sponsorAdminUserName != null and sponsorAdminUserName != ''">sponsor_admin_user_name,</if>
<if test="projectForm != null and projectForm != ''">project_form,</if>
<if test="isFinished != null and isFinished != ''">is_finished,</if>
<if test="isSettled != null and isSettled != ''">is_settled,</if>
<if test="sponsorAdminUserId != null">sponsor_admin_user_id,</if>
<if test="leadUserId != null">lead_user_id,</if>
<if test="isBidProject != null and isBidProject != ''">is_bid_project,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
@@ -169,11 +170,6 @@
<if test="invitationUrl != null and invitationUrl != ''">invitation_url,</if>
<if test="supportLetterUrl != null and supportLetterUrl != ''">support_letter_url,</if>
<if test="publishUrl != null and publishUrl != ''">publish_url,</if>
<if test="noticeUrl != null and noticeUrl != ''">notice_url,</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url,</if>
<if test="isPublished != null and isPublished != ''">is_published,</if>
<if test="publishTime != null">publish_time,</if>
<if test="announcementType != null and announcementType != ''">announcement_type,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="projectId != null and projectId != ''">#{projectId},</if>
@@ -187,15 +183,13 @@
<if test="paidLaborAmount != null">#{paidLaborAmount},</if>
<if test="paidMeetingAmount != null">#{paidMeetingAmount},</if>
<if test="ratingScore != null">#{ratingScore},</if>
<if test="ratingQ1 != null">#{ratingQ1},</if>
<if test="ratingQ2 != null">#{ratingQ2},</if>
<if test="ratingQ3 != null">#{ratingQ3},</if>
<if test="ratingQ4 != null">#{ratingQ4},</if>
<if test="sponsorAdminUserName != null and sponsorAdminUserName != ''">#{sponsorAdminUserName},</if>
<if test="projectForm != null and projectForm != ''">#{projectForm},</if>
<if test="isFinished != null and isFinished != ''">#{isFinished},</if>
<if test="isSettled != null and isSettled != ''">#{isSettled},</if>
<if test="sponsorAdminUserId != null">#{sponsorAdminUserId},</if>
<if test="leadUserId != null">#{leadUserId},</if>
<if test="isBidProject != null and isBidProject != ''">#{isBidProject},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
@@ -209,11 +203,6 @@
<if test="invitationUrl != null and invitationUrl != ''">#{invitationUrl},</if>
<if test="supportLetterUrl != null and supportLetterUrl != ''">#{supportLetterUrl},</if>
<if test="publishUrl != null and publishUrl != ''">#{publishUrl},</if>
<if test="noticeUrl != null and noticeUrl != ''">#{noticeUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">#{scheduleUrl},</if>
<if test="isPublished != null and isPublished != ''">#{isPublished},</if>
<if test="publishTime != null">#{publishTime},</if>
<if test="announcementType != null and announcementType != ''">#{announcementType},</if>
</trim>
</insert>
<update id="updateByPrimaryKey" parameterType="BizProject">
@@ -228,11 +217,6 @@
<if test="invitationUrl != null and invitationUrl != ''">invitation_url = #{invitationUrl},</if>
<if test="supportLetterUrl != null and supportLetterUrl != ''">support_letter_url = #{supportLetterUrl},</if>
<if test="publishUrl != null and publishUrl != ''">publish_url = #{publishUrl},</if>
<if test="noticeUrl != null and noticeUrl != ''">notice_url = #{noticeUrl},</if>
<if test="scheduleUrl != null and scheduleUrl != ''">schedule_url = #{scheduleUrl},</if>
<if test="isPublished != null and isPublished != ''">is_published = #{isPublished},</if>
<if test="publishTime != null">publish_time = #{publishTime},</if>
<if test="announcementType != null and announcementType != ''">announcement_type = #{announcementType},</if>
<if test="projectNo != null and projectNo != ''">project_no = #{projectNo},</if>
<if test="projectName != null and projectName != ''">project_name = #{projectName},</if>
<if test="totalSessions != null">total_sessions = #{totalSessions},</if>
@@ -243,15 +227,13 @@
<if test="paidLaborAmount != null">paid_labor_amount = #{paidLaborAmount},</if>
<if test="paidMeetingAmount != null">paid_meeting_amount = #{paidMeetingAmount},</if>
<if test="ratingScore != null">rating_score = #{ratingScore},</if>
<if test="ratingQ1 != null">rating_q1 = #{ratingQ1},</if>
<if test="ratingQ2 != null">rating_q2 = #{ratingQ2},</if>
<if test="ratingQ3 != null">rating_q3 = #{ratingQ3},</if>
<if test="ratingQ4 != null">rating_q4 = #{ratingQ4},</if>
<if test="sponsorAdminUserName != null and sponsorAdminUserName != ''">sponsor_admin_user_name = #{sponsorAdminUserName},</if>
<if test="projectForm != null and projectForm != ''">project_form = #{projectForm},</if>
<if test="isFinished != null and isFinished != ''">is_finished = #{isFinished},</if>
<if test="isSettled != null and isSettled != ''">is_settled = #{isSettled},</if>
<if test="sponsorAdminUserId != null">sponsor_admin_user_id = #{sponsorAdminUserId},</if>
<if test="leadUserId != null">lead_user_id = #{leadUserId},</if>
<if test="isBidProject != null and isBidProject != ''">is_bid_project = #{isBidProject},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
@@ -22,7 +22,7 @@
(#{projectId}, #{sponsorUserId}, #{monitorUserId}, #{assignDesc}, #{assignPoints}, #{createBy}, sysdate())
</insert>
<!-- 按 project_id 全删 (赞助方分配策略: 一个项目只分配一个 sponsor, 先删后插) -->
<!-- 按 project_id 全删 (支持方分配策略: 一个项目只分配一个 sponsor, 先删后插) -->
<delete id="deleteByProjectId" parameterType="String">
delete from biz_project_sponsor_assign where project_id = #{projectId}
</delete>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.business.mapper.BizSysUserQueryMapper">
<resultMap type="SysUserExtendVo" id="BizSysUserResult">
<id property="userId" column="user_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="accountType" column="account_type" />
<result property="roleType" column="role_type" />
<result property="phonenumber" column="phonenumber" />
<result property="email" column="email" />
<result property="status" column="status" />
</resultMap>
<!--
业务模块专用: 按 sys_user.role_type 查用户列表 (项目负责人 / 执行方 下拉)
不注入 ${params.dataScope}, 避免被当前用户权限过滤 (manager 没 system:user:list 会被清空)
限定 status='0' + del_flag='0', 排除停用/删除账号
-->
<select id="selectActiveByRole" parameterType="SysUserExtendVo" resultMap="BizSysUserResult">
select u.user_id, u.user_name, u.nick_name, u.account_type, u.role_type,
u.phonenumber, u.email, u.status
from sys_user u
where u.del_flag = '0'
and u.status = '0'
and u.role_type = #{roleType}
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
</if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
order by u.user_id asc
</select>
</mapper>
+1 -1
View File
@@ -24,7 +24,7 @@ export function sponsorAssignProject(data) {
}
/**
* 赞助方批量分配 (多个项目同一个监察员 + 同一份说明)
* 支持方批量分配 (多个项目同一个监察员 + 同一份说明)
* body: [{projectId, monitorUserId, assignDesc, assignPoints}, ...]
*/
export function sponsorAssignBatch(data) {
+20 -4
View File
@@ -11,12 +11,28 @@ export function listRole(query) {
return request({ url: '/system/role/list', method: 'get', params: query })
}
// 按角色查询用户列表 (绕开 sys_user:list 权限)
// 用法: listByRole({ roleType: 'executor', userName: 'exe' })
// 按 sys_user.role_type 查用户列表 (走 /business/project/listByRole, 不走 system:user:list 权限)
// 用法: listByRole({ roleType: 'manager', userName: 'm' })
export function listByRole(query) {
return request({ url: '/business/executor/list', method: 'get', params: query })
return request({ url: '/business/project/listByRole', method: 'get', params: query })
}
// 兼容旧名
// 兼容旧名 (项目分配弹窗已不再使用, 保留避免破坏其它 import)
export const listExecutor = (query) => listByRole({ roleType: 'executor', ...query })
export const listSupporters = (query) => listByRole({ roleType: 'sponsor', ...query })
// 合规管理员 (sys_user.role_type='manager', 选项目负责人用)
export const listManagers = (query) => listByRole({ roleType: 'manager', ...query })
// 支持方下拉选项 (JOIN biz_org + sys_user): 返回 [{ userId, orgName, userName }, ...]
// 用于 manager 项目分配弹窗按公司名搜索 + 选公司
export function listSponsorOrgs(query) {
return request({ url: '/business/org/sponsorOptions', method: 'get', params: query })
}
// 执行方下拉选项 (JOIN biz_org + sys_user, 仅 MAIN 账号): 返回 [{ userId, orgName, userName }, ...]
// 用于 manager 项目分配弹窗按公司名搜索 + 选执行单位
// 关键: 后端 SQL 已限定 parent_user_id IS NULL, 自动排除同公司的普通员工子账号
// 业务上 label=orgName / value=MAIN user_id, 直接写 biz_project_assign.exec_user_id
export function listExecutorOrgs(query) {
return request({ url: '/business/org/executorOptions', method: 'get', params: query })
}
+257
View File
@@ -0,0 +1,257 @@
<template>
<div class="portal-shell">
<!-- ========== 顶部导航 ========== -->
<header class="top-nav" :class="topNavClass">
<a class="logo" title="返回首页" @click.prevent="goHome">
<div class="logo-icon"></div>
<div class="logo-text">
<span class="logo-title">北京整合医学学会</span>
<span class="logo-subtitle">Beijing Association of Holistic Integrative Medicine</span>
</div>
</a>
<ul class="nav-list">
<li class="nav-item"><a class="nav-link" :class="{ active: activeNav === 'home' }" @click.prevent="goHome">年度项目规划</a></li>
<li class="nav-item"><a class="nav-link" :class="{ active: activeNav === 'publicity' }" @click.prevent="goPublicity">项目公示</a></li>
</ul>
<div class="top-tools">
<template v-if="!loggedIn">
<span class="login-btn" @click="goLogin">登录</span>
</template>
<template v-else>
<el-dropdown trigger="click" @command="onUserCmd">
<a class="user-link" @click.prevent>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
<span>{{ userName }}</span>
</a>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="account">我的主页</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</div>
</header>
<!-- ========== 主体 slot ========== -->
<main class="portal-main">
<slot />
</main>
<!-- ========== 底部 ========== -->
<footer class="footer">
<div class="container">
<div class="footer-main">
<div class="footer-brand">
<div class="brand-row">
<div class="footer-logo-icon"></div>
<div>
<div class="footer-brand-name">北京整合医学学会</div>
<div class="footer-brand-en">Beijing Association of Holistic Integrative Medicine</div>
</div>
</div>
<p class="footer-desc">依托"健康中国2030"战略,整合医学资源,推动健康科普与公益事业,助力全民健康素养提升,共建共享健康中国</p>
</div>
<div class="footer-col">
<h4>快速导航</h4>
<a @click.prevent="goHome">首页</a>
<a @click.prevent="goHome">年度项目规划</a>
<a @click.prevent="goPublicity">项目公示</a>
</div>
<div class="footer-col">
<h4>学会项目</h4>
<a>百姓巡常行</a>
<a>专病联盟暨全国专家智库</a>
<a>乡村幸福安康</a>
<a>临床科研资助计划</a>
</div>
<div class="footer-col">
<h4>联系方式</h4>
<p>电话: 010-82089470</p>
<p>邮箱: contactus@bahim.org.cn</p>
<p>地址: 北京市海淀区知春路1号学院国际大厦908-2</p>
<p>邮编: 100083</p>
</div>
<div class="footer-qr">
<div class="qr-image">
<svg width="52" height="52" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 11h8V3H3v8zm2-6h4v4H5V5zm8-2v8h8V3h-8zm6 6h-4V5h4v4zM3 21h8v-8H3v8zm2-6h4v4H5v-4zm13-2h-2v2h2v-2zm-2 2h-2v2h2v-2zm2 2h-2v2h2v-2zm2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm-2-2h-2v2h2v-2zm0 0h-2v2h2v-2z"/>
</svg>
</div>
<div class="qr-label">公众号二维码</div>
</div>
</div>
<div class="footer-bottom">
<span>© 北京整合医学学会 BAHIM</span>
<span>京ICP备2020035479号-1 &nbsp;&nbsp; 京公网安备11010802034820</span>
</div>
</div>
</footer>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
import { logout as logoutApi } from '@/api/auth'
defineProps({
activeNav: { type: String, default: '' } // 'home' | 'publicity' | ''
})
const router = useRouter()
const userStore = useUserStore()
const isScrolled = ref(false)
const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '')
const loggedIn = computed(() => !!userStore.token)
const userName = computed(() => userStore.user?.userName || '用户')
function handleScroll() { isScrolled.value = window.scrollY > 10 }
function goHome() { isScrolled.value = false; router.push('/portal/home') }
function goPublicity() { router.push('/publicity') }
function goLogin() { router.push('/login') }
async function goLogout() {
try { await logoutApi() } catch {}
userStore.logout()
router.replace('/login')
}
async function onUserCmd(cmd) {
if (cmd === 'logout') return goLogout()
if (cmd === 'account') {
const r = (userStore.user?.role || '')
const map = { admin: '/leader/home', leader: '/leader/home', manager: '/manager/workbench', doctor: '/doctor/home', executor: '/executor/meetings', sponsor: '/sponsor/home' }
router.push(map[r] || '/leader/home')
}
}
onMounted(() => window.addEventListener('scroll', handleScroll, { passive: true }))
onBeforeUnmount(() => window.removeEventListener('scroll', handleScroll))
</script>
<style scoped>
/* ========== 基础 ========== */
* { margin: 0; padding: 0; box-sizing: border-box; }
.portal-shell {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #1f2937;
background: #f5f6f8;
min-width: 1354px;
line-height: 1.6;
}
a { color: inherit; text-decoration: none; }
/* ========== 顶部导航 ========== */
.top-nav {
position: sticky; top: 0; z-index: 1000;
height: 72px; padding: 0 60px;
background: var(--brand-primary);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
display: flex; align-items: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
transition: box-shadow 0.3s;
}
.top-nav.is-scrolled { box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); }
.logo { display: flex; align-items: center; gap: 12px; }
.logo-icon {
width: 36px; height: 36px;
background: #fff; color: var(--brand-primary);
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 600;
}
.logo-text { display: flex; flex-direction: column; line-height: 1.2; }
.logo-title { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 0.5px; }
.logo-subtitle { font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 2px; letter-spacing: 0.3px; }
.nav-list {
flex: 1; display: flex; align-items: center; justify-content: center;
gap: 36px; list-style: none;
}
.nav-item { position: relative; height: 72px; display: flex; align-items: center; }
.nav-link {
font-size: 15px; font-weight: 500; color: rgba(255, 255, 255, 0.85);
cursor: pointer; transition: color 0.25s; position: relative;
height: 72px; display: flex; align-items: center;
}
.nav-link::after {
content: ''; position: absolute; left: 50%; bottom: 0;
width: 0; height: 2px; background: #fff;
transform: translateX(-50%); transition: width 0.3s ease;
}
.nav-item:hover .nav-link, .nav-link.active { color: #fff; }
.nav-item:hover .nav-link::after, .nav-link.active::after { width: 100%; }
.top-tools { display: flex; align-items: center; gap: 16px; }
.login-btn {
padding: 7px 20px; background: #fff; color: var(--brand-primary);
font-size: 13px; font-weight: 500;
cursor: pointer; transition: background 0.2s; letter-spacing: 1px;
}
.login-btn:hover { background: #f3f4f6; }
.user-link {
display: flex; align-items: center; gap: 6px;
padding: 6px 10px;
color: rgba(255, 255, 255, 0.85);
font-size: 13px; cursor: pointer;
transition: background 0.2s, color 0.2s;
}
.user-link:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
/* ========== 主体容器 ========== */
.portal-main { min-height: calc(100vh - 72px - 350px); } /* 给 footer 留位 */
.container { max-width: 1354px; margin: 0 auto; padding: 0 60px; }
/* ========== 底部 ========== */
.footer { background: var(--brand-primary-darker); color: rgba(255, 255, 255, 0.65); padding: 48px 0 0; }
.footer-main { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr auto; gap: 48px; padding-bottom: 36px; }
.footer-brand .brand-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.footer-logo-icon {
width: 36px; height: 36px;
background: #fff; color: var(--brand-primary);
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 600; flex-shrink: 0;
}
.footer-brand-name { font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 1px; }
.footer-brand-en { font-size: 10px; color: rgba(255, 255, 255, 0.4); letter-spacing: 0.5px; margin-top: 2px; }
.footer-desc { font-size: 13px; line-height: 1.9; color: rgba(255, 255, 255, 0.55); }
.footer-col h4 {
font-size: 14px; font-weight: 600; color: #fff;
letter-spacing: 1px; margin-bottom: 16px; padding-bottom: 10px;
position: relative;
}
.footer-col h4::after {
content: ''; position: absolute; left: 0; bottom: 0;
width: 24px; height: 2px; background: #93c5fd;
}
.footer-col a, .footer-col p {
display: block; font-size: 13px; color: rgba(255, 255, 255, 0.6);
line-height: 2.1; transition: color 0.2s;
}
.footer-col a { cursor: pointer; }
.footer-col a:hover { color: #fff; }
.footer-qr { text-align: center; }
.qr-image {
width: 100px; height: 100px;
background: #fff;
display: flex; align-items: center; justify-content: center;
color: #1f2937;
}
.qr-label { font-size: 12px; color: rgba(255, 255, 255, 0.5); margin-top: 10px; }
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.12);
padding: 18px 0;
display: flex; justify-content: space-between;
font-size: 12px; color: rgba(255, 255, 255, 0.4);
}
@media (max-width: 900px) {
.top-nav { padding: 0 24px; }
.container { padding: 0 24px; }
}
</style>
+1 -1
View File
@@ -62,7 +62,7 @@ const router = useRouter()
const store = useUserStore()
const role = computed(() => route.meta?.role || store.role)
const ROLE_LABEL = { leader: '项目负责人', manager: '合规人员', admin: '后台管理员', doctor: '评审专家', executor: '执行人', sponsor: '赞助方' }
const ROLE_LABEL = { leader: '项目负责人', manager: '合规人员', admin: '后台管理员', doctor: '评审专家', executor: '执行人', sponsor: '支持方' }
const roleLabel = computed(() => ROLE_LABEL[role.value] || '工作台')
const MENU = {
+1 -1
View File
@@ -12,7 +12,7 @@ const routes = [
{ path: 'invitation/:annId', name: 'invitation', component: () => import('@/views/portal/Invitation.vue'), meta: { title: '邀请函详情' } },
{ path: 'register-expert', name: 'register-expert', component: () => import('@/views/auth/RegisterExpert.vue'), meta: { title: '专家注册' } },
{ path: 'register-executor', name: 'register-executor', component: () => import('@/views/auth/RegisterExecutor.vue'), meta: { title: '执行单位(供应商)注册' } },
{ path: 'register-sponsor', name: 'register-sponsor', component: () => import('@/views/auth/RegisterSponsor.vue'), meta: { title: '赞助方注册' } },
{ path: 'register-sponsor', name: 'register-sponsor', component: () => import('@/views/auth/RegisterSponsor.vue'), meta: { title: '支持方注册' } },
{ path: 'login', name: 'login', component: () => import('@/views/auth/Login.vue'), meta: { title: '登录' } },
{ path: 'article/:type', name: 'portal-article', component: () => import('@/views/portal/ArticleView.vue'), meta: { title: '协议' } },
{ path: 'special-plan/:id', name: 'portal-special-plan', component: () => import('@/views/portal/SpecialPlanDetail.vue'), meta: { title: '专项计划详情' } }
+1 -1
View File
@@ -50,7 +50,7 @@ const store = useUserStore()
const tab = ref('profile')
const loading = ref(false)
const profile = ref({})
const ROLE_LABEL = { admin: '后台管理员', leader: '项目负责人', manager: '合规人员', doctor: '评审专家', executor: '执行人', sponsor: '赞助方' }
const ROLE_LABEL = { admin: '后台管理员', leader: '项目负责人', manager: '合规人员', doctor: '评审专家', executor: '执行人', sponsor: '支持方' }
const roleLabel = computed(() => ROLE_LABEL[store.role] || store.role || '后台管理员')
const form = reactive({ nickName: '', phonenumber: '', email: '' })
+5 -2
View File
@@ -137,7 +137,10 @@ function reset() {
async function onToggleStatus(row) {
const disabled = isDisabled(row)
const newStatus = disabled ? '正常' : '禁用'
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
const newStatus = disabled ? '0' : '1'
const label = disabled ? '启用' : '禁用'
try {
await ElMessageBox.confirm(
@@ -147,7 +150,7 @@ async function onToggleStatus(row) {
)
} catch { return }
try {
await bizUpdate('person', { personId: row.personId, status: newStatus })
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success('已' + label)
load()
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
+5 -5
View File
@@ -3,7 +3,7 @@
角色: 🟦 后台 公司管理 (admin/orgs)
重构说明 (2026-08-15):
biz_support_unit / biz_execution_unit / biz_service_org 三表合并为 biz_org
通过 org_type 区分 sponsor(赞助) / execution(执行方)
通过 org_type 区分 sponsor(支持) / execution(执行方)
biz_service_org 整体删除
接口: bizList('org', q) / bizUpdate
-->
@@ -14,7 +14,7 @@
<el-form inline :model="q" class="filter-form">
<el-form-item label="公司类型">
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
<el-option label="赞助方" value="sponsor" />
<el-option label="支持方" value="sponsor" />
<el-option label="执行方" value="executor" />
</el-select>
</el-form-item>
@@ -50,7 +50,7 @@
<el-table-column label="公司类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
{{ row.orgType === 'sponsor' ? '支持方' : '执行方' }}
</el-tag>
</template>
</el-table-column>
@@ -95,7 +95,7 @@
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef">
<el-form-item label="公司类型" prop="orgType">
<el-radio-group v-model="form.orgType">
<el-radio value="sponsor">赞助</el-radio>
<el-radio value="sponsor">支持</el-radio>
<el-radio value="executor">执行方</el-radio>
</el-radio-group>
</el-form-item>
@@ -219,7 +219,7 @@ function onEdit(row) {
function onView(row) {
// 只读查看用 ElMessageBox 弹窗即可, 这里简单 alert 一下
ElMessageBox.alert(
`类型: ${row.orgType === 'sponsor' ? '赞助方' : '执行方'}\n` +
`类型: ${row.orgType === 'sponsor' ? '支持方' : '执行方'}\n` +
`名称: ${row.orgName}\n` +
`地址: ${row.address || '-'}\n` +
`税号: ${row.taxNo || '-'}\n` +
+1 -1
View File
@@ -47,7 +47,7 @@ import { ElMessage } from 'element-plus'
const ROLE_LABEL = {
admin: '后台管理员', leader: '项目负责人', manager: '合规人员',
doctor: '评审专家', executor: '执行人', sponsor: '赞助方'
doctor: '评审专家', executor: '执行人', sponsor: '支持方'
}
// 卡片展示的角色 (隐藏 admin / leader)
const VISIBLE_ROLES = ['manager', 'doctor', 'executor', 'sponsor']
+1 -1
View File
@@ -198,7 +198,7 @@ function onPeople(row) {
}
function onView(row) {
ElMessageBox.alert(
`类型: 赞助\n` +
`类型: 支持\n` +
`名称: ${row.orgName}\n` +
`地址: ${row.address || '-'}\n` +
`税号: ${row.taxNo || '-'}\n` +
+5 -2
View File
@@ -138,7 +138,10 @@ function reset() {
async function onToggleStatus(row) {
const disabled = isDisabled(row)
const newStatus = disabled ? '正常' : '禁用'
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
const newStatus = disabled ? '0' : '1'
const label = disabled ? '启用' : '禁用'
try {
await ElMessageBox.confirm(
@@ -148,7 +151,7 @@ async function onToggleStatus(row) {
)
} catch { return }
try {
await bizUpdate('person', { personId: row.personId, status: newStatus })
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success('已' + label)
load()
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
+1 -1
View File
@@ -30,7 +30,7 @@
<el-input v-model="form.position" placeholder="职务" maxlength="50" />
</el-form-item>
<el-form-item label="角色">
<span style="color:#303133">监察员 <span style="color:#909399;font-size:12px">(赞助方人员固定)</span></span>
<span style="color:#303133">监察员 <span style="color:#909399;font-size:12px">(支持方人员固定)</span></span>
</el-form-item>
</el-form>
</el-card>
+1 -1
View File
@@ -98,7 +98,7 @@ import { ElMessage } from 'element-plus'
const ROLE_LABEL = {
admin: '后台管理员', leader: '项目负责人', manager: '合规人员',
doctor: '评审专家', executor: '执行人', sponsor: '赞助方'
doctor: '评审专家', executor: '执行人', sponsor: '支持方'
}
const roleOptions = Object.entries(ROLE_LABEL).map(([value, label]) => ({ value, label }))
+2 -2
View File
@@ -53,7 +53,7 @@ const roleStats = ref([])
const ROLE_LABEL = {
admin: '后台管理员', leader: '项目负责人', manager: '合规人员',
doctor: '评审专家', executor: '执行人', sponsor: '赞助方'
doctor: '评审专家', executor: '执行人', sponsor: '支持方'
}
async function load() {
@@ -125,7 +125,7 @@ onMounted(load)
background-clip: text;
}
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
.hint-text { font-size: 12px; color: #8c8c8c; margin: 12px 0 20px; text-align: center; }
.section {
background: #fff;
+1 -1
View File
@@ -213,7 +213,7 @@ const userTypes = [
{ value: 'manager', name: '项目经理', desc: '项目管理、方案审核、过程监督' },
{ value: 'doctor', name: '评审专家', desc: '项目评审、评分反馈、查看任务' },
{ value: 'executor', name: '执行单位', desc: '会议组织、人员安排、劳务结算' },
{ value: 'sponsor', name: '支持单位 / 赞助方', desc: '支持函管理、人员管理、合作记录' },
{ value: 'sponsor', name: '支持单位', desc: '支持函管理、人员管理、合作记录' },
]
async function loadCaptcha() {
+11 -7
View File
@@ -1,7 +1,7 @@
<template>
<div class="page-wrap">
<div class="page-card">
<div class="page-title">新执行单位(供应商)注册</div>
<PortalShell>
<div class="page-wrap">
<div class="page-card">
<el-steps :active="step" finish-status="success" simple class="steps">
<el-step title="基本信息" />
@@ -12,7 +12,7 @@
<!-- ============ Step 0: 基本信息 ( proto 供应商注册.html) ============ -->
<!-- TODO: 后续如要做 sponsor 也用同样模板, 拆出 BizOrgRegisterVO 公共组件 -->
<template v-if="step === 0">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" label-position="right">
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px" label-position="right">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入登录用户名(4-20位字母数字)" maxlength="20" />
</el-form-item>
@@ -81,8 +81,9 @@
<div class="login-link">
已有账号? <a href="javascript:void(0)" @click="$router.push('/login')">立即登录</a>
</div>
</div>
</div>
</div>
</PortalShell>
</template>
<script setup>
@@ -90,6 +91,7 @@ import { reactive, ref, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import { registerExecutor } from '@/api/public'
import PortalShell from '@/components/PortalShell.vue'
import { useAsyncLock } from '@/utils/useAsyncLock'
const { locked: smsLocked, run: sendSmsOnce } = useAsyncLock()
@@ -182,7 +184,8 @@ async function onSubmit() {
ElMessage.success('注册成功')
step.value = 1
} catch (e) {
ElMessage.error(e?.msg || '注册失败')
// 错误提示已由 utils/request.js 拦截器统一弹 (ElMessage.error), 这里只 log
console.warn('registerExecutor failed', e?.message || e)
} finally {
submitting.value = false
}
@@ -192,6 +195,7 @@ onUnmounted(() => { if (smsTimer) clearInterval(smsTimer) })
</script>
<style scoped>
.page-card { max-width: 1200px; margin: 0 auto; }
.steps { max-width: 720px; margin: 24px auto 0; }
.step-body { max-width: 640px; margin: 32px auto 0; }
.sms-row { display: flex; gap: 12px; width: 100%; }
@@ -200,6 +204,6 @@ onUnmounted(() => { if (smsTimer) clearInterval(smsTimer) })
.agreement-content { line-height: 1.8; color: #606266; }
.agreement-content p { margin-bottom: 12px; }
.login-link { text-align: center; margin-top: 16px; font-size: 14px; color: #606266; }
.login-link a { color: #1890ff; text-decoration: none; margin-left: 4px; }
.login-link a { color: var(--brand-primary); text-decoration: none; margin-left: 4px; }
.login-link a:hover { text-decoration: underline; }
</style>
+84 -84
View File
@@ -1,86 +1,85 @@
<template>
<div class="page-wrap">
<div class="page-card">
<div class="page-title">专家注册</div>
<PortalShell>
<div class="page-wrap">
<div class="page-card">
<!-- Stepper (原型: 1 基本信息 2 注册成功) -->
<div class="stepper">
<div class="step">
<div class="step-circle" :class="{ active: step === 0, done: step > 0 }">1</div>
<div class="step-label" :class="{ active: step === 0, done: step > 0 }">基本信息</div>
</div>
<div class="step-line" :class="{ done: step > 0 }"></div>
<div class="step">
<div class="step-circle" :class="{ active: step === 1 }">2</div>
<div class="step-label" :class="{ active: step === 1 }">注册成功</div>
<el-steps :active="step" finish-status="success" simple class="steps">
<el-step title="基本信息" />
<el-step title="注册成功" />
</el-steps>
<div class="step-body">
<!-- 表单 (原型 1:1 : 11 个字段) -->
<el-form v-if="step === 0" :model="form" :rules="rules" ref="formRef" label-width="130px" label-position="right">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入您的真实姓名" />
</el-form-item>
<el-form-item label="工作单位" prop="workUnit">
<el-input v-model="form.workUnit" placeholder="请输入工作单位(医院全称)" />
</el-form-item>
<el-form-item label="科室" prop="department">
<DoctorDeptSelect v-model="form.department" placeholder="请输入所在科室" />
</el-form-item>
<el-form-item label="职称" prop="doctorTitle">
<DoctorTitleSelect v-model="form.doctorTitle" placeholder="请输入职称" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
<el-form-item label="短信验证码" prop="code">
<div class="sms-row">
<el-input v-model="form.code" placeholder="请输入收到的验证码" maxlength="6" />
<el-button class="sms-btn" :disabled="smsLocked || smsCountdown > 0 || !form.phone" @click="sendCode">
{{ smsLocked ? '发送中...' : smsCountdown > 0 ? `${smsCountdown}s 后重新获取` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<el-input v-model="form.password" type="password" show-password placeholder="请输入登录密码(6-20位)" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入登录密码" />
</el-form-item>
<el-form-item label="执业证书/证明">
<OssImageUploader v-model="form.licenseCertUrl" dir="ry8080/cert/license/" placeholder="+" />
</el-form-item>
<el-form-item label="职称证明">
<OssImageUploader v-model="form.titleCertUrl" dir="ry8080/cert/title/" placeholder="+" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="agreed">我已阅读并同意
<a href="#/article/agreement" target="_blank">用户协议</a>
<a href="#/article/privacy" target="_blank">隐私政策</a>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="!agreed" @click="onSubmit" :loading="submitting" style="width: 100%;">同意协议并注册</el-button>
</el-form-item>
</el-form>
<!-- 注册成功 -->
<div v-else class="success-page">
<el-result icon="success" title="注册成功" sub-title="请等待管理员审核,审核通过后即可登录">
<template #extra>
<el-button type="primary" @click="$router.push('/login')">前往登录</el-button>
</template>
</el-result>
</div>
</div>
<!-- 表单 (原型 1:1 : 11 个字段) -->
<el-form v-if="step === 0" :model="form" :rules="rules" ref="formRef" label-width="100px" style="max-width:760px;margin:24px auto;">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入您的真实姓名" />
</el-form-item>
<el-form-item label="工作单位" prop="workUnit">
<el-input v-model="form.workUnit" placeholder="请输入工作单位(医院全称)" />
</el-form-item>
<el-form-item label="科室" prop="department">
<DoctorDeptSelect v-model="form.department" placeholder="请输入所在科室" />
</el-form-item>
<el-form-item label="职称" prop="doctorTitle">
<DoctorTitleSelect v-model="form.doctorTitle" placeholder="请输入职称" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
<el-form-item label="短信验证码" prop="code">
<div class="sms-row">
<el-input v-model="form.code" placeholder="请输入收到的验证码" maxlength="6" />
<el-button :disabled="smsLocked || smsCountdown > 0 || !form.phone" @click="sendCode">
{{ smsLocked ? '发送中...' : smsCountdown > 0 ? `${smsCountdown}s 后重新获取` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<el-input v-model="form.password" type="password" show-password placeholder="请输入登录密码(6-20位)" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入登录密码" />
</el-form-item>
<el-form-item label="执业证书/证明">
<OssImageUploader v-model="form.licenseCertUrl" dir="ry8080/cert/license/" placeholder="+" />
</el-form-item>
<el-form-item label="职称证明">
<OssImageUploader v-model="form.titleCertUrl" dir="ry8080/cert/title/" placeholder="+" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="agreed">我已阅读并同意
<a href="#/article/agreement" target="_blank">用户协议</a>
<a href="#/article/privacy" target="_blank">隐私政策</a>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="!agreed" @click="onSubmit" :loading="submitting" style="width: 100%;">同意协议并注册</el-button>
</el-form-item>
</el-form>
<!-- 注册成功 -->
<div v-else class="success-page">
<el-result icon="success" title="注册成功" sub-title="请等待管理员审核,审核通过后即可登录">
<template #extra>
<el-button type="primary" @click="$router.push('/login')">前往登录</el-button>
</template>
</el-result>
<div class="login-link">
已有账号? <a href="javascript:void(0)" @click="$router.push('/login')">立即登录</a>
</div>
</div>
</div>
</div>
</PortalShell>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { reactive, ref, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { registerExpert } from '@/api/public'
@@ -88,6 +87,7 @@ import request from '@/utils/request'
import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue'
import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue'
import OssImageUploader from '@/components/OssImageUploader.vue'
import PortalShell from '@/components/PortalShell.vue'
import { useAsyncLock } from '@/utils/useAsyncLock'
const { locked: smsLocked, run: sendSmsOnce } = useAsyncLock()
@@ -171,27 +171,27 @@ async function onSubmit() {
ElMessage.success('注册成功')
step.value = 1
} catch (e) {
ElMessage.error(e?.msg || '注册失败')
// 错误提示已由 utils/request.js 拦截器统一弹 (ElMessage.error), 这里只 log
console.warn('registerExpert failed', e?.message || e)
} finally {
submitting.value = false
}
}
onUnmounted(() => { if (smsTimer) clearInterval(smsTimer) })
</script>
<style scoped>
.stepper { display: flex; align-items: center; justify-content: center; margin: 24px 0 8px; }
.step { display: flex; align-items: center; gap: 8px; }
.step-circle { width: 28px; height: 28px; border-radius: 50%; background: #fff; border: 1px solid #d9d9d9; color: #909399; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 500; }
.step-circle.active { background: #1890ff; border-color: #1890ff; color: #fff; }
.step-circle.done { background: #52c41a; border-color: #52c41a; color: #fff; }
.step-label { font-size: 14px; color: #909399; }
.step-label.active { color: #1890ff; font-weight: 500; }
.step-label.done { color: #52c41a; }
.step-line { width: 80px; height: 1px; background: #d9d9d9; margin: 0 12px; }
.step-line.done { background: #52c41a; }
.page-card { max-width: 1200px; margin: 0 auto; }
.steps { max-width: 720px; margin: 24px auto 0; }
.step-body { max-width: 640px; margin: 32px auto 0; }
.sms-row { display: flex; gap: 12px; width: 100%; }
.sms-row .el-input { flex: 1; }
.sms-btn { flex-shrink: 0; }
.agreement-content { line-height: 1.8; color: #606266; }
.agreement-content p { margin-bottom: 12px; }
.login-link { text-align: center; margin-top: 16px; font-size: 14px; color: #606266; }
.login-link a { color: var(--brand-primary); text-decoration: none; margin-left: 4px; }
.login-link a:hover { text-decoration: underline; }
.success-page { padding: 60px 0; text-align: center; }
</style>
+188 -22
View File
@@ -1,42 +1,208 @@
<template>
<div class="page-wrap">
<div class="page-card">
<div class="page-title">赞助方注册</div>
<PortalShell>
<div class="page-wrap">
<div class="page-card">
<el-steps :active="step" finish-status="success" simple class="steps">
<el-step title="账号信息" /><el-step title="单位信息" />
<el-step title="基本信息" />
<el-step title="注册成功" />
</el-steps>
<div class="step-body">
<!-- ============ Step 0: 基本信息 (字段与 register-executor 完全一致, orgType 不同) ============ -->
<template v-if="step === 0">
<el-form :model="form" label-width="100px"><el-form-item label="用户名"><el-input v-model="form.username" /></el-form-item><el-form-item label="密码"><el-input v-model="form.password" type="password" /></el-form-item></el-form>
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px" label-position="right">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入登录用户名(4-20位字母数字)" maxlength="20" />
</el-form-item>
<el-form-item label="企业名称" prop="unitName">
<el-input v-model="form.unitName" placeholder="请输入企业全称" maxlength="200" />
</el-form-item>
<el-form-item label="企业性质" prop="businessNature">
<el-select v-model="form.businessNature" placeholder="请选择企业性质" style="width:100%">
<el-option label="私营" value="私营" />
<el-option label="国营" value="国营" />
<el-option label="中外合资" value="中外合资" />
<el-option label="外资" value="外资" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
<el-form-item label="短信验证码" prop="smsCode">
<div class="sms-row">
<el-input v-model="form.smsCode" placeholder="请输入收到的验证码" maxlength="6" />
<el-button class="sms-btn" :disabled="smsLocked || smsCountdown > 0 || !form.phone" @click="sendSms">
{{ smsLocked ? '发送中...' : smsCountdown > 0 ? `${smsCountdown}s 后重试` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
<el-form-item label="登录密码" prop="password">
<el-input v-model="form.password" type="password" show-password placeholder="请输入登录密码(6-20位)" />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入登录密码" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="agreed">我已阅读并同意
<a href="#/article/agreement" target="_blank">用户协议</a>
<a href="#/article/privacy" target="_blank">隐私政策</a>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="!agreed" :loading="submitting" @click="onSubmit" style="width:100%;">
同意协议并注册
</el-button>
</el-form-item>
</el-form>
</template>
<!-- ============ Step 1: 注册成功 ============ -->
<template v-else>
<el-form :model="form" label-width="100px"><el-form-item label="单位名称"><el-input v-model="form.unitName" /></el-form-item><el-form-item label="联系人"><el-input v-model="form.contact" /></el-form-item><el-form-item label="联系电话"><el-input v-model="form.phone" /></el-form-item><el-form-item label="赞助意向"><el-input v-model="form.intent" type="textarea" /></el-form-item><el-form-item><el-checkbox v-model="agreed">我已阅读并同意 <a href="#/article/agreement" target="_blank">《用户协议》</a> 和 <a href="#/article/privacy" target="_blank">隐私政策</a></el-checkbox></el-form-item></el-form>
<el-result icon="success" title="注册成功" sub-title="我们会在 3 个工作日内完成审核">
<template #extra>
<el-button type="primary" @click="$router.push('/login')">前往登录</el-button>
</template>
</el-result>
</template>
</div>
<div style="text-align:center;margin-top:16px">
<el-button @click="prev" :disabled="step === 0">上一步</el-button>
<el-button type="primary" @click="next" v-if="step < 2">下一步</el-button>
<el-button @click="$router.push('/login')" v-else>去登录</el-button>
<div class="login-link">
已有账号? <a href="javascript:void(0)" @click="$router.push('/login')">立即登录</a>
</div>
</div>
</div>
</div>
</PortalShell>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { registerSponsor } from '@/api/public'
import { reactive, ref, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import { registerSponsor } from '@/api/public'
import PortalShell from '@/components/PortalShell.vue'
import { useAsyncLock } from '@/utils/useAsyncLock'
const { locked: smsLocked, run: sendSmsOnce } = useAsyncLock()
const step = ref(0)
const form = reactive({ username: '', password: '', unitName: '', contact: '', phone: '', intent: '' })
const formRef = ref()
const submitting = ref(false)
const agreed = ref(false)
const prev = () => step.value = Math.max(0, step.value - 1)
const next = async () => {
if (step.value === 1) {
if (!agreed.value) return ElMessage.warning('请先阅读并同意用户协议和隐私政策')
try { await registerSponsor(form); ElMessage.success('已提交') } catch { /* demo */ }
step.value = 2
const form = reactive({
username: '',
unitName: '',
businessNature: '',
phone: '',
smsCode: '',
password: '',
confirmPassword: ''
})
const rules = {
username: [
{ required: true, message: '请输入登录用户名', trigger: 'blur' },
{ min: 4, max: 20, message: '用户名长度 4-20 位', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9_]+$/, message: '只能包含字母/数字/下划线', trigger: 'blur' }
],
unitName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
businessNature: [{ required: true, message: '请选择企业性质', trigger: 'change' }],
phone: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ pattern: /^1\d{10}$/, message: '手机号格式错误', trigger: 'blur' }
],
smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
password: [
{ required: true, message: '请输入登录密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度 6-20 位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入登录密码', trigger: 'blur' },
{
validator: (rule, value, cb) => {
if (value !== form.password) cb(new Error('两次密码输入不一致'))
else cb()
},
trigger: 'blur'
}
]
}
// ===== 短信验证码 =====
const smsCountdown = ref(0)
let smsTimer = null
const smsUuid = ref('')
async function sendSms() {
if (!/^1\d{10}$/.test(form.phone)) {
return ElMessage.warning('请先输入正确的手机号')
}
await sendSmsOnce(async () => {
try {
const resp = await request({
url: '/business/auth/registerSendSms',
method: 'post',
data: { phone: form.phone }
})
smsUuid.value = resp?.data || ''
ElMessage.success('验证码已发送')
smsCountdown.value = 60
smsTimer = setInterval(() => {
smsCountdown.value--
if (smsCountdown.value <= 0) {
clearInterval(smsTimer)
smsTimer = null
}
}, 1000)
} catch (e) {
ElMessage.warning(e?.msg || '验证码发送失败')
}
})
}
// ===== 提交 =====
async function onSubmit() {
try {
await formRef.value.validate()
} catch {
return
}
step.value++
submitting.value = true
try {
await registerSponsor({ ...form, uuid: smsUuid.value })
ElMessage.success('注册成功')
step.value = 1
} catch (e) {
// 错误提示已由 utils/request.js 拦截器统一弹 (ElMessage.error), 这里只 log
console.warn('registerSponsor failed', e?.message || e)
} finally {
submitting.value = false
}
}
onUnmounted(() => { if (smsTimer) clearInterval(smsTimer) })
</script>
<style scoped>.steps { max-width: 560px; margin: 16px auto; } .step-body { max-width: 560px; margin: 16px auto; }</style>
<style scoped>
.page-card { max-width: 1200px; margin: 0 auto; }
.steps { max-width: 720px; margin: 24px auto 0; }
.step-body { max-width: 640px; margin: 32px auto 0; }
.sms-row { display: flex; gap: 12px; width: 100%; }
.sms-row .el-input { flex: 1; }
.sms-btn { flex-shrink: 0; }
.agreement-content { line-height: 1.8; color: #606266; }
.agreement-content p { margin-bottom: 12px; }
.login-link { text-align: center; margin-top: 16px; font-size: 14px; color: #606266; }
.login-link a { color: var(--brand-primary); text-decoration: none; margin-left: 4px; }
.login-link a:hover { text-decoration: underline; }
</style>
+1 -1
View File
@@ -45,7 +45,7 @@
<el-descriptions-item label="项目编号">{{ detail.projectNo }}</el-descriptions-item>
<el-descriptions-item label="项目形式">{{ detail.projectForm || '-' }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ detail.projectName }}</el-descriptions-item>
<el-descriptions-item label="赞助方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="支持方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="总场次">{{ detail.totalSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="已执行">{{ detail.doneSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="未执行">{{ detail.todoSessions || 0 }}</el-descriptions-item>
+38 -2
View File
@@ -34,7 +34,13 @@
</el-form-item>
<el-form-item label="所属公司" prop="orgName">
<el-input v-model="form.orgName" placeholder="请输入所属公司" maxlength="200" />
<el-input v-model="form.orgName" :readonly="!!myOrg" placeholder="请输入所属公司" maxlength="200">
<template #append v-if="myOrg">
<el-tooltip content="主账号公司, 注册时已自动关联" placement="top">
<el-icon><Lock /></el-icon>
</el-tooltip>
</template>
</el-input>
</el-form-item>
<el-form-item label="部门" prop="department">
@@ -72,15 +78,21 @@ import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { bizGet, bizAdd, bizUpdate } from '@/api/public'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import { useUserStore } from '@/store/user'
import { Lock } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const isEdit = computed(() => !!route.params.id)
const personId = computed(() => route.params.id || null)
const formRef = ref()
const saving = ref(false)
// 主账号关联的公司 (注册时建, 按 user_id 反查)
const myOrg = ref(null)
const form = reactive({
userName: '',
name: '',
@@ -146,7 +158,31 @@ function goHome() {
router.push('/executor/overview')
}
onMounted(loadDetail)
// 加载主账号关联的公司 (注册时建, 按 user_id + orgType 反查, 一用户一类型一公司)
async function loadMyOrg() {
const uid = userStore.user?.userId
if (!uid) return
try {
const res = await request({
url: '/business/org/list',
method: 'get',
params: { userId: uid, orgType: 'executor', pageNum: 1, pageSize: 1 }
})
const rows = (res?.data?.rows) || res?.rows || []
if (rows.length) {
myOrg.value = rows[0]
// 默认带出, 提交时也直接发这个 orgId (不再依赖 orgName 字符串匹配)
form.orgName = rows[0].orgName || ''
}
} catch (e) {
console.warn('loadMyOrg:', e?.msg)
}
}
onMounted(() => {
if (isEdit.value) loadDetail()
else loadMyOrg() // 新建时默认带出主账号公司
})
</script>
<style scoped>
+5 -2
View File
@@ -131,8 +131,11 @@ function onView(row) { view.value = row; viewOpen.value = true }
async function onToggleStatus(row, action) {
try {
await ElMessageBox.confirm(`确定${action}${row.name}」吗?`, action, { type: 'warning' })
const newStatus = action === '禁用' ? '禁用' : '正常'
await bizUpdate('person', { personId: row.personId, status: newStatus })
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
const newStatus = action === '禁用' ? '1' : '0'
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success(`${action}成功`)
loadList()
} catch (e) { if (e !== 'cancel') ElMessage.error(e?.msg || `${action}失败`) }
+1 -1
View File
@@ -124,7 +124,7 @@
<el-descriptions-item label="项目编号">{{ detail.projectNo }}</el-descriptions-item>
<el-descriptions-item label="项目形式">{{ detail.projectForm }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ detail.projectName }}</el-descriptions-item>
<el-descriptions-item label="赞助方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="支持方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="总场次/总期数">{{ detail.totalSessions }}/{{ detail.totalPeriods }}</el-descriptions-item>
<el-descriptions-item label="已执行">{{ detail.doneSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="未执行">{{ detail.todoSessions || 0 }}</el-descriptions-item>
+1 -1
View File
@@ -8,7 +8,7 @@
是否结题 / 项目开始时间 / 项目结束时间 / 操作
操作列 (top 352, left): 查看 / 项目评价 / 分配
提示:
- 支持方管理员分配时只能分配监察员赞助领导方可分配执行人员
- 支持方管理员分配时只能分配监察员支持领导方可分配执行人员
- 项目评价: 4 维度评分标准与供应商系统一致
-->
<div class="leader-projects">
+5 -2
View File
@@ -137,7 +137,10 @@ function reset() {
async function onToggleStatus(row) {
const disabled = isDisabled(row)
const newStatus = disabled ? '正常' : '禁用'
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
const newStatus = disabled ? '0' : '1'
const label = disabled ? '启用' : '禁用'
try {
await ElMessageBox.confirm(
@@ -147,7 +150,7 @@ async function onToggleStatus(row) {
)
} catch { return }
try {
await bizUpdate('person', { personId: row.personId, status: newStatus })
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success('已' + label)
load()
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
@@ -5,54 +5,61 @@
首页 / 项目管理 / <span class="current">项目详情</span>
</div>
<!-- ================= 1. 项目信息 (11 字段, 一列竖排) ================= -->
<el-form v-if="row && row.projectId" label-position="left" class="info-form">
<el-form-item label="项目编号">
<span class="info-value">{{ row.projectNo || '-' }}</span>
</el-form-item>
<el-form-item label="项目名称">
<span class="info-value">{{ row.projectName || '-' }}</span>
</el-form-item>
<el-form-item label="总场次/总期数">
<span class="info-value">{{ row.totalSessions || 0 }}</span>
</el-form-item>
<el-form-item label="总金额">
<span class="info-value money">¥ {{ fmtMoney(row.totalAmount) }}</span>
</el-form-item>
<el-form-item label="管理费及税金">
<span class="info-value money">¥ {{ fmtMoney(row.manageFee) }}</span>
</el-form-item>
<el-form-item label="项目开始时间">
<span class="info-value">{{ fmtDate(row.startTime) }}</span>
</el-form-item>
<el-form-item label="项目结束时间">
<span class="info-value">{{ fmtDate(row.endTime) }}</span>
</el-form-item>
<el-form-item label="赞助公司">
<span class="info-value">{{ row.sponsorAdminUserName || '-' }}</span>
</el-form-item>
<el-form-item label="项目形式">
<span class="info-value">{{ row.projectForm || '-' }}</span>
</el-form-item>
<el-form-item label="项目评价">
<a class="op-link" href="javascript:void(0)" @click="openScoreDialog">{{ fmtScore(row.ratingScore) }}</a>
</el-form-item>
<!-- ================= 1. 项目信息 (11 字段, 两列) ================= -->
<div class="new-card-title">项目信息</div>
<el-form v-if="row && row.projectId" label-width="120px" class="info-form">
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="项目编号"><span class="info-value">{{ row.projectNo || '-' }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目名称"><span class="info-value">{{ row.projectName || '-' }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="总场次/总期数"><span class="info-value">{{ row.totalSessions || 0 }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="总金额"><span class="info-value money">¥ {{ fmtMoney(row.totalAmount) }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="管理费及税金"><span class="info-value money">¥ {{ fmtMoney(row.manageFee) }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目形式"><span class="info-value">{{ row.projectForm || '-' }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="项目开始时间"><span class="info-value">{{ fmtDate(row.startTime) }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目结束时间"><span class="info-value">{{ fmtDate(row.endTime) }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="是否招标项目"><span class="info-value">{{ row.isBidProject === 'Y' ? '是' : '否' }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目负责人"><span class="info-value">{{ row.leadUserName || row.sponsorAdminUserName || '-' }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="支持公司"><span class="info-value">{{ row.sponsorAdminUserName || '-' }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目评价"><a class="op-link" href="javascript:void(0)" @click="openScoreDialog">{{ fmtScore(row.ratingScore) }}</a></el-form-item></el-col>
</el-row>
</el-form>
<!-- ================= 2. 角色劳务设置 ================= -->
<div class="section-title">角色劳务设置</div>
<p class="hint-red">*角色劳务设置针对该项目所有会议生效</p>
<el-table :data="form.roleRows" border>
<el-table-column prop="role" label="角色" width="120" />
<el-table-column label="劳务金额" align="right">
<template #default="{ row }">¥ {{ fmtMoney(row.amount) }}</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="{ $index }">
<el-button link type="danger" :disabled="form.roleRows.length<=1" @click="removeRoleRowAt($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<table class="info-table role-labor-table">
<colgroup>
<col style="width:120px">
<col style="width:180px">
<col style="width:80px">
</colgroup>
<thead>
<tr>
<th>角色</th>
<th style="text-align:right">劳务金额</th>
<th style="text-align:center">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(r, idx) in form.roleRows" :key="idx">
<td>{{ r.role || '-' }}</td>
<td style="text-align:right">¥ {{ fmtMoney(r.amount) }}</td>
<td style="text-align:center">
<el-button link type="danger" :disabled="form.roleRows.length<=1" @click="removeRoleRowAt(idx)">删除</el-button>
</td>
</tr>
</tbody>
</table>
<div style="margin-top:8px">
<el-button link type="primary" @click="addRoleRow">+ 增加角色</el-button>
</div>
@@ -402,6 +409,13 @@ onMounted(async () => {
.info-form :deep(.el-form-item__label) {
font-size: 14px; color: #595959; width: 130px; padding-right: 16px;
}
/* 章节标题 (与编辑页 ProjectsNew.vue 保持一致) */
.new-card-title {
font-size: 14px; font-weight: 600; color: #262626;
margin: 16px 0 12px; padding-left: 8px;
border-left: 3px solid var(--brand-primary); line-height: 1;
}
.new-card-title:first-child { margin-top: 0; }
.info-value { font-size: 15px; color: #1a1a1a; font-weight: 500; }
.info-value.money {
font-family: ui-monospace, "Courier New", monospace;
@@ -425,6 +439,11 @@ onMounted(async () => {
/* 角色劳务表格 */
.info-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.role-labor-table { width: auto; border: 1px solid #ebeef5; } /* 角色劳务表格按内容紧凑显示, 不撑满卡片 */
.role-labor-table th,
.role-labor-table td { border-right: 1px solid #ebeef5; }
.role-labor-table th:last-child,
.role-labor-table td:last-child { border-right: none; }
.info-table th { background: #fafafa; padding: 10px 12px; text-align: left; color: #1a1a1a; font-weight: 600; border-bottom: 1px solid #f0f0f0; white-space: nowrap; }
.info-table td { padding: 10px 12px; border-bottom: 1px solid #f5f5f5; color: #595959; vertical-align: middle; }
+3 -3
View File
@@ -11,7 +11,7 @@
<el-form inline :model="q" class="filter-form">
<el-form-item label="公司类型">
<el-select v-model="q.orgType" placeholder="全部" clearable style="width:140px">
<el-option label="赞助方" value="sponsor" />
<el-option label="支持方" value="sponsor" />
<el-option label="执行方" value="executor" />
</el-select>
</el-form-item>
@@ -40,7 +40,7 @@
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.orgType === 'sponsor' ? 'success' : 'primary'" disable-transitions>
{{ row.orgType === 'sponsor' ? '赞助方' : '执行方' }}
{{ row.orgType === 'sponsor' ? '支持方' : '执行方' }}
</el-tag>
</template>
</el-table-column>
@@ -71,7 +71,7 @@
<el-descriptions :column="1" border>
<el-descriptions-item label="公司类型">
<el-tag :type="currentRow.orgType === 'sponsor' ? 'success' : 'primary'">
{{ currentRow.orgType === 'sponsor' ? '赞助方' : '执行方' }}
{{ currentRow.orgType === 'sponsor' ? '支持方' : '执行方' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
+73 -47
View File
@@ -3,7 +3,7 @@
<div class="breadcrumb">首页 / 项目管理</div>
<el-form inline :model="q" class="filter-form">
<el-form-item label="赞助方负责人:"><el-input v-model="q.sponsorAdminUserName" placeholder="输入账号/昵称" clearable /></el-form-item>
<el-form-item label="支持单位:"><el-input v-model="q.sponsorOrgName" placeholder="输入支持单位名称" clearable /></el-form-item>
<el-form-item label="服务机构:"><el-input v-model="q.execOrgName" placeholder="输入服务机构" clearable /></el-form-item>
<el-form-item label="项目编号:"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
<el-form-item label="项目名称:"><el-input v-model="q.projectName" placeholder="输入项目名称" clearable /></el-form-item>
@@ -64,7 +64,8 @@
<span v-else style="color:#c0c4cc">-</span>
</template>
</el-table-column>
<el-table-column prop="sponsorAdminUserName" label="赞助方负责人" width="160" show-overflow-tooltip />
<el-table-column prop="sponsorOrgName" label="支持单位" width="160" show-overflow-tooltip />
<el-table-column prop="execOrgNames" label="服务机构" min-width="180" show-overflow-tooltip />
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
<el-table-column prop="isFinished" label="是否结题" width="100" align="center">
<template #default="{ row }">
@@ -89,6 +90,7 @@
<el-dropdown-item v-if="row.isFinished !== '1' && row.isFinished !== 1" command="close">结题</el-dropdown-item>
<el-dropdown-item command="assign">项目分配</el-dropdown-item>
<el-dropdown-item command="rate">执行单位评分</el-dropdown-item>
<el-dropdown-item command="exportExperts" divided>导出报名专家</el-dropdown-item>
<el-dropdown-item v-if="row.isPublished === '1' || row.publishUrl" command="activate" divided>开通</el-dropdown-item>
<el-dropdown-item v-if="row.isPublished === '1' || row.publishUrl" command="delAnn" divided>删除公告</el-dropdown-item>
</el-dropdown-menu>
@@ -121,8 +123,8 @@
<div style="color:#606266;font-size:13px"> <b>{{ assignBatchProjects.length }}</b> 个项目</div>
</el-form-item>
<el-form-item label="支持方">
<el-select v-model="assignForm.sponsorAdminUserId" placeholder="请选择赞助方负责人" filterable :filter-method="searchSupporters" clearable style="width:100%" @change="onSupporterPick">
<el-option v-for="u in supporterOptions" :key="u.userId" :label="`${u.userName} (${u.nickName || ''})`" :value="u.userId" />
<el-select v-model="assignForm.sponsorAdminUserId" placeholder="请选择支持单位" filterable :filter-method="searchSponsorOrgs" clearable style="width:100%" @change="onSponsorOrgPick">
<el-option v-for="u in sponsorOrgOptions" :key="u.userId" :label="u.orgName" :value="u.userId" />
</el-select>
</el-form-item>
<el-divider>执行方分配</el-divider>
@@ -152,7 +154,7 @@
style="width:100%"
@change="v => onExecUserPick(row, v)">
<el-option v-for="u in (row._options || [])" :key="u.userId"
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`" :value="u.userId" />
:label="u.orgName" :value="u.userId" />
</el-select>
</template>
</el-table-column>
@@ -270,7 +272,7 @@
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { bizList, bizAdd, bizUpdate, bizDelete } from '@/api/public'
import { listExecutor, listSupporters } from '@/api/system'
import { listExecutorOrgs, listSponsorOrgs } from '@/api/system'
import request from '@/utils/request'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowDown } from '@element-plus/icons-vue'
@@ -278,7 +280,7 @@ import Preview from '@/components/Preview.vue'
const router = useRouter()
const route = useRoute()
const q = ref({ sponsorAdminUserName: '', execOrgName: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', ratingScore: '' })
const q = ref({ sponsorOrgName: '', execOrgName: '', projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '', ratingScore: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
@@ -303,6 +305,7 @@ function onAction(cmd, row) {
else if (cmd === 'close') openClose(row)
else if (cmd === 'assign') doAssign(row)
else if (cmd === 'rate') openSingleScore(row)
else if (cmd === 'exportExperts') exportExperts(row)
else if (cmd === 'activate') openActivate(row)
else if (cmd === 'delAnn') openDeleteAnnouncement(row)
}
@@ -314,7 +317,7 @@ function doAssign(row) {
assignOpen.value = true
onAssignProjectChange(row.projectId)
// 预加载 sponsor 候选
setTimeout(() => loadSupporters(''), 100)
setTimeout(() => loadSponsorOrgs(''), 100)
}
function doEdit(row) { router.push(`/manager/projects/edit/${row.projectId}`) }
@@ -330,12 +333,38 @@ async function doDelete(row) {
}
}
function reset() { q.value = { sponsorAdminUserName:'', execOrgName:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', ratingScore:'' }; page.pageNum = 1; load() }
function reset() { q.value = { sponsorOrgName:'', execOrgName:'', projectNo:'', projectName:'', projectForm:'', startTime:'', endTime:'', isFinished:'', isSettled:'', ratingScore:'' }; page.pageNum = 1; load() }
// 导出
function exportProjects() { ElMessage.info('导出项目功能开发中') }
function exportEval() { ElMessage.info('项目评价导出功能开发中') }
function exportExperts() { ElMessage.info('已报名专家信息导出功能开发中') }
// 导出某项目的报名专家 (biz_execution_intent)
// 字段: 专家姓名 科室 医院 职称 报名时间 手机号
async function exportExperts(row) {
if (!row || !row.projectNo) { ElMessage.warning('缺少项目编号'); return }
try {
// 后端 BizExecutionIntent 参数没 @RequestBody, 必须走 URL query string
const res = await request({
url: '/business/executionIntent/export',
method: 'post',
params: { projectNo: row.projectNo },
responseType: 'blob'
})
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `报名专家_${row.projectNo}.xlsx`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} catch (e) {
const msg = e?.msg || e?.message || '导出失败'
ElMessage.error(msg)
}
}
// ========== 单行操作按钮 handlers(弹 5 个独立 dialog ==========
function openClose(row) {
@@ -481,7 +510,7 @@ function openAssign() {
assignOpen.value = true
setTimeout(() => {
assignForm.execRows.forEach(r => searchExecutors('', r))
loadSupporters('')
loadSponsorOrgs('')
}, 100)
}
function openBatch(kind) {
@@ -573,7 +602,7 @@ const assignForm = reactive({
execRows: [], // 动态加
deadlineDays: 30
})
const supporterOptions = ref([])
const sponsorOrgOptions = ref([])
let _supporterTimer = null
// 已分配场次合计 (实时)
@@ -593,12 +622,12 @@ const singleAmountOver = computed(() =>
)
function makeEmptyExecRow() {
return { _loading: false, _timer: null, _options: [], execUserId: null, execUserName: '', execNickName: '', execOrg: '', sessions: 0, amount: 0, remark: '' }
return { _loading: false, _timer: null, _options: [], execUserId: null, sessions: 0, amount: 0, remark: '' }
}
function resetAssignForm() {
Object.assign(assignForm, { projectId:'', sponsorAdminUserId:null, sponsorAdminUserName:'', totalSessions:0, totalAmount:0, execRows:[makeEmptyExecRow()], deadlineDays:30 })
supporterOptions.value = []
sponsorOrgOptions.value = []
assignBatchMode.value = false
assignBatchProjects.value = []
}
@@ -621,36 +650,37 @@ function onAssignProjectChange(v) {
// 切换项目时重新拉已分配记录 (回显)
loadAssigns(v)
// 重新拉支持方候选
loadSupporters('')
loadSponsorOrgs('')
}
// 加载支持方 (sponsor 角色)
async function loadSupporters(query) {
// 加载支持方 (sponsor 角色) - 按公司名查, JOIN biz_org + sys_user
async function loadSponsorOrgs(query) {
if (_supporterTimer) clearTimeout(_supporterTimer)
_supporterTimer = setTimeout(async () => {
try {
const params = { pageNum: 1, pageSize: 50 }
if (query) params.userName = query
const r = await listSupporters(params)
supporterOptions.value = r.data || []
// 已选项不在结果里时单独拉取并加首位
if (assignForm.sponsorAdminUserId && !supporterOptions.value.find(u => u.userId === assignForm.sponsorAdminUserId)) {
const r2 = await listSupporters({ userId: assignForm.sponsorAdminUserId })
// 主搜索: 按 org_name 模糊匹配
const params = {}
if (query) params.orgName = query
const r = await listSponsorOrgs(params)
sponsorOrgOptions.value = r.data || []
// 已选项不在结果里时按 userId 单独拉取并加首位
if (assignForm.sponsorAdminUserId && !sponsorOrgOptions.value.find(u => u.userId === assignForm.sponsorAdminUserId)) {
const r2 = await listSponsorOrgs({ userId: assignForm.sponsorAdminUserId })
const sel = (r2.data || []).find(u => u.userId === assignForm.sponsorAdminUserId)
if (sel) supporterOptions.value = [sel, ...supporterOptions.value]
if (sel) sponsorOrgOptions.value = [sel, ...sponsorOrgOptions.value]
}
} catch (e) {
supporterOptions.value = []
sponsorOrgOptions.value = []
}
}, 200)
}
function searchSupporters(q) { loadSupporters(q) }
function searchSponsorOrgs(q) { loadSponsorOrgs(q) }
// 选中支持方后回填名称
function onSupporterPick(userId) {
const u = supporterOptions.value.find(x => x.userId === userId)
if (u) assignForm.sponsorAdminUserName = u.nickName || u.userName
// 选中支持方后回填 user_name (缓存到 biz_project.sponsor_admin_user_name)
function onSponsorOrgPick(userId) {
const u = sponsorOrgOptions.value.find(x => x.userId === userId)
if (u) assignForm.sponsorAdminUserName = u.userName
}
// 拉取项目已分配的执行方
@@ -663,9 +693,6 @@ async function loadAssigns(projectId) {
assignForm.execRows = list.map(a => ({
...makeEmptyExecRow(),
execUserId: a.execUserId,
execUserName: a.execUserName,
execNickName: a.execNickName,
execOrg: a.execOrg,
sessions: a.sessions || 0,
amount: Number(a.amount || 0),
remark: a.remark || ''
@@ -682,19 +709,22 @@ async function loadAssigns(projectId) {
}
}
// 加载执行方 (row 级 debounce, 每个 row 独立 timer)
// 加载执行方 (按执行单位名 org_name 模糊匹配)
// 关键: 数据源是 biz_org (org_type='executor') JOIN sys_user (MAIN 账号 only)
// label=orgName (执行单位名称), value=MAIN user_id (直接写 biz_project_assign.exec_user_id)
// 走 /business/org/executorOptions (不在走 sys_user.role_type='executor' 查询, 避免子账号混入)
async function searchExecutors(query, row) {
if (row._timer) clearTimeout(row._timer)
row._loading = true
row._timer = setTimeout(async () => {
try {
const params = { pageNum: 1, pageSize: 20 }
if (query) params.userName = query
const r = await listExecutor(params)
const params = {}
if (query) params.orgName = query
const r = await listExecutorOrgs(params)
row._options = r.data || []
// 选中的项如果不在结果里, 加到首位
// 选中的项如果不在结果里, 按 userId 单独拉取并加到首位 (保证已选项在 el-option 中能找到 label)
if (row.execUserId && !row._options.find(u => u.userId === row.execUserId)) {
const r2 = await listExecutor({ userId: row.execUserId })
const r2 = await listExecutorOrgs({ userId: row.execUserId })
const sel = (r2.data || []).find(u => u.userId === row.execUserId)
if (sel) row._options = [sel, ...row._options]
}
@@ -706,13 +736,12 @@ async function searchExecutors(query, row) {
}, 300)
}
// 选中执行方后回填
// 选中执行方后只保留 userId (后端 service 层会反查 biz_org 写 execution_unit_id;
// 名称/nickName/deptName 不再缓存, 由前端从 _options 里实时取)
function onExecUserPick(row, userId) {
const u = (row._options || []).find(x => x.userId === userId)
if (u) {
row.execUserName = u.userName
row.execNickName = u.nickName || ''
row.execOrg = u.dept?.deptName || ''
// 仅清掉旧的非持久化字段 (兼容旧 _options 残留), 业务字段不写
}
}
@@ -751,9 +780,6 @@ async function submitAssign() {
const assignBody = valid.map(r => ({
execUserId: r.execUserId,
execUserName: r.execUserName,
execNickName: r.execNickName,
execOrg: r.execOrg,
sessions: r.sessions,
amount: r.amount,
remark: r.remark
+49 -47
View File
@@ -20,6 +20,24 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="项目负责人">
<el-select v-model="form.leadUserId" placeholder="请选择合规管理员" filterable clearable :filter-method="searchManagers" style="width:100%">
<el-option v-for="u in managerOptions" :key="u.userId"
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`" :value="u.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否招标项目">
<el-select v-model="form.isBidProject" placeholder="请选择" style="width:100%" clearable>
<el-option label="是" value="Y" />
<el-option label="否" value="N" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="项目形式" prop="projectForm">
@@ -61,22 +79,6 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="提交截止(天)">
<el-input-number v-model="form.submitDeadlineDays" :min="0" controls-position="right" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="支持单位">
<el-select v-model="form.sponsorAdminUserId" placeholder="请选择赞助方负责人" filterable clearable style="width:100%" @change="onSupporterPick">
<el-option v-for="u in supporterOptions" :key="u.userId"
:label="`${u.userName}${u.nickName ? ' (' + u.nickName + ')' : ''}`"
:value="u.userId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- ========== 第二区块角色劳务设置 ========== -->
<div class="new-card-title">角色劳务设置</div>
@@ -134,7 +136,7 @@
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
import { listExecutor, listSupporters } from '@/api/system'
import { listExecutor, listManagers } from '@/api/system'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import OssFileUploader from '@/components/OssFileUploader.vue'
@@ -150,18 +152,18 @@ const fileInputRef = ref(null)
function defaultRoleRow() { return { role: '', customName: '', amount: 0 } }
function defaultNotice(key, label) { return { key, label, url: '', name: '' } }
// 支持方候选 (sponsor 角色)
const supporterOptions = ref([])
// 项目负责人候选 (合规管理员, sys_user.role_type='manager')
const managerOptions = ref([])
let _managerTimer = null
const form = reactive({
projectName: '', projectNo: '', projectForm: '',
// 项目负责人 (sys_user.role_type='manager' 合规管理员, 名称由后端 JOIN 查, 不缓存)
leadUserId: null,
// 是否招标项目 Y/N (默认 N)
isBidProject: 'N',
totalSessions: 0, totalAmount: 0, manageFee: 0,
startTime: '', endTime: '',
// 赞助方负责人 (sys_user.role_type='sponsor', 原 org_id/org_name 字段已重命名)
sponsorAdminUserId: null,
sponsorAdminUserName: '',
// 提交截止(天)
submitDeadlineDays: 30,
roleRows: [
{ role: '主席', customName: '', amount: 0 },
{ role: '主持', customName: '', amount: 0 },
@@ -222,9 +224,8 @@ async function loadProject() {
form.manageFee = p.manageFee || 0
form.startTime = p.startTime || ''
form.endTime = p.endTime || ''
form.sponsorAdminUserId = p.sponsorAdminUserId || null
form.sponsorAdminUserName = p.sponsorAdminUserName || ''
form.submitDeadlineDays = p.submitDeadlineDays || 30
form.leadUserId = p.leadUserId || null
form.isBidProject = p.isBidProject || 'N'
// 公告文件反显: 优先 publicityFiles JSON 数组, 否则兼容旧字段
const files = []
if (Array.isArray(p.publicityFiles)) files.push(...p.publicityFiles)
@@ -258,27 +259,29 @@ async function loadProject() {
}
}
async function loadSupporters(query = '') {
try {
const params = { roleType: 'sponsor', pageNum: 1, pageSize: 50 }
if (query) params.userName = query
const r = await listSupporters(params)
supporterOptions.value = (r.data && r.data.rows) || r.rows || []
} catch (e) { supporterOptions.value = [] }
// 加载项目负责人 (合规管理员, sys_user.role_type='manager')
async function loadManagers(query) {
if (_managerTimer) clearTimeout(_managerTimer)
_managerTimer = setTimeout(async () => {
try {
const params = { pageNum: 1, pageSize: 50 }
if (query) params.userName = query
const r = await listManagers(params)
managerOptions.value = (r.data && r.data.rows) || r.rows || []
// 已选项不在结果里时按 userId 单独拉取并加首位, 避免 el-select 选中后 label 空白
if (form.leadUserId && !managerOptions.value.find(u => u.userId === form.leadUserId)) {
const r2 = await listManagers({ userId: form.leadUserId })
const sel = (r2.data && r2.data.rows) || r2.rows || []
if (sel[0]) managerOptions.value = [sel[0], ...managerOptions.value]
}
} catch (e) { managerOptions.value = [] }
}, 200)
}
function onSupporterPick(userId) {
const u = supporterOptions.value.find(x => x.userId === userId)
if (u) form.sponsorAdminUserName = u.userName || ''
}
function searchManagers(q) { loadManagers(q) }
onMounted(() => {
loadProject()
loadSupporters('')
loadManagers('')
})
function goBack() {
// 带 _t 强制 Projects.vue watch 触发 reload, 即便路由一样也重载
@@ -301,9 +304,8 @@ async function submit(mode = 'save') {
manageFee: form.manageFee,
startTime: form.startTime,
endTime: form.endTime,
sponsorAdminUserId: form.sponsorAdminUserId,
sponsorAdminUserName: form.sponsorAdminUserName,
submitDeadlineDays: form.submitDeadlineDays
leadUserId: form.leadUserId,
isBidProject: form.isBidProject
}
// 公告文件以 publicityFiles JSON 数组存储 (替代旧 invitationUrl/supportLetterUrl/noticeUrl/scheduleUrl)
payload.publicityFiles = JSON.stringify(uploaded.map(n => ({
+1 -1
View File
@@ -56,7 +56,7 @@
<el-dialog v-model="detailOpen" title="支持单位详情" width="640px" v-if="currentRow">
<el-descriptions :column="1" border>
<el-descriptions-item label="公司类型">
<el-tag type="success">赞助</el-tag>
<el-tag type="success">支持</el-tag>
</el-descriptions-item>
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
<el-descriptions-item label="公司地址">{{ currentRow.address }}</el-descriptions-item>
+5 -2
View File
@@ -137,7 +137,10 @@ function reset() {
async function onToggleStatus(row) {
const disabled = isDisabled(row)
const newStatus = disabled ? '正常' : '禁用'
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
const newStatus = disabled ? '0' : '1'
const label = disabled ? '启用' : '禁用'
try {
await ElMessageBox.confirm(
@@ -147,7 +150,7 @@ async function onToggleStatus(row) {
)
} catch { return }
try {
await bizUpdate('person', { personId: row.personId, status: newStatus })
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success('已' + label)
load()
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
@@ -30,7 +30,7 @@
<el-input v-model="form.position" placeholder="职务" maxlength="50" />
</el-form-item>
<el-form-item label="角色">
<span style="color:#303133">监察员 <span style="color:#909399;font-size:12px">(赞助方人员固定)</span></span>
<span style="color:#303133">监察员 <span style="color:#909399;font-size:12px">(支持方人员固定)</span></span>
</el-form-item>
</el-form>
</el-card>
+8 -15
View File
@@ -26,8 +26,6 @@
</router-link>
</div>
<p class="hint-text">*点击数字跳转到相应的列表页</p>
<!-- 消息通知 -->
<section class="section">
<h2 class="section-title">消息通知<a class="more" href="javascript:void(0)">更多 </a></h2>
@@ -102,37 +100,32 @@ onMounted(() => { loadStats(); loadNotices() })
.manager-workbench { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
/* 5 个 KPI 卡 (跟 executor/Overview 一致) */
/* 5 个 KPI 卡 (深色底白字) */
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
.stat-card {
background: #fff;
border: 1px solid #f0f0f0;
background: #1e3a8a;
border: 1px solid #1e3a8a;
border-radius: 8px;
padding: 24px;
cursor: pointer;
text-decoration: none;
color: inherit;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: opacity .15s, border-color .15s;
transition: opacity .15s, border-color .15s, transform .15s;
min-height: 120px;
}
.stat-card:hover { border-color: var(--brand-primary); opacity: .9; }
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
.stat-card:hover { opacity: .9; transform: translateY(-2px); }
.stat-label { font-size: 14px; color: rgba(255, 255, 255, 0.85); margin-bottom: 16px; }
.stat-value {
font-size: 32px;
font-weight: 600;
line-height: 1.2;
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: #fff;
}
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
/* 消息通知 */
.section {
background: #fff;
+9 -11
View File
@@ -111,29 +111,27 @@ onMounted(() => {
.stats-grid-2 { grid-template-columns: repeat(2, 1fr); }
.stat-card {
background: #fff;
border: 1px solid #f0f0f0;
background: var(--brand-primary);
border: 1px solid var(--brand-primary);
border-radius: 8px;
padding: 24px;
cursor: pointer;
text-decoration: none;
color: inherit;
color: #fff;
display: block;
transition: border-color 0.2s, box-shadow 0.2s;
transition: opacity .15s, transform .15s, box-shadow .2s;
box-shadow: 0 2px 8px rgba(30, 58, 138, 0.15);
}
.stat-card:hover { border-color: var(--brand-primary); box-shadow: 0 2px 8px rgba(30,58,138,0.15); }
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
.stat-card:hover { opacity: .92; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(30, 58, 138, 0.25); }
.stat-label { font-size: 14px; color: rgba(255, 255, 255, 0.85); margin-bottom: 16px; }
.stat-value {
font-size: 32px;
font-weight: 700;
line-height: 1;
margin-bottom: 8px;
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: #fff;
}
.stat-desc { font-size: 12px; color: #bfbfbf; }
.stat-desc { font-size: 12px; color: rgba(255, 255, 255, 0.7); }
.section {
background: #fff;
+51 -16
View File
@@ -4,7 +4,7 @@
表单布局: el-card shadow="never" form-card + el-row :gutter="12" el-col :span="12" 两列
编辑/新建复用: 通过 route.params.id 区分
-->
<div class="page-card">
<div class="page-card new-person">
<!-- 面包屑 -->
<div class="breadcrumb">
<a @click="goBack">人员管理</a> &gt; {{ isEdit ? '编辑人员' : '新建人员' }}
@@ -15,13 +15,13 @@
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-row v-if="!isEdit" :gutter="12">
<el-col :span="12">
<el-form-item label="账号" prop="loginUsername">
<el-input v-model="form.loginUsername" placeholder="请输入子账号登录账号" maxlength="30" />
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="请输入子账号登录用户名" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="初始密码" prop="loginPassword">
<el-input v-model="form.loginPassword" placeholder="请输入初始密码 (≥6 位)" type="password" show-password maxlength="30" />
<el-form-item label="初始密码" prop="password">
<el-input v-model="form.password" placeholder="请输入初始密码 (≥6 位)" type="password" show-password maxlength="30" />
</el-form-item>
</el-col>
</el-row>
@@ -42,7 +42,13 @@
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="所属公司" prop="orgName">
<el-input v-model="form.orgName" placeholder="请输入所属公司" maxlength="200" />
<el-input v-model="form.orgName" :readonly="!!myOrg" placeholder="请输入所属公司" maxlength="200">
<template #append v-if="myOrg">
<el-tooltip content="主账号公司, 注册时已自动关联" placement="top">
<el-icon><Lock /></el-icon>
</el-tooltip>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -94,15 +100,21 @@ import { reactive, ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
import request from '@/utils/request'
import { useUserStore } from '@/store/user'
import { Lock } from '@element-plus/icons-vue'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const formRef = ref(null)
const saving = ref(false)
const loadingDetail = ref(false)
const isEdit = !!route.params.id
const personId = route.params.id || null
// 主账号关联的公司 (注册时建, 按 user_id 反查)
const myOrg = ref(null)
const form = reactive({
personId: null,
@@ -114,9 +126,9 @@ const form = reactive({
role: 'supervisor',
unitType: 'sponsor',
status: '0',
// 子账号登录信息 (后端会创建 sys_user SUB,parent_user_id=主账号)
loginUsername: '',
loginPassword: '',
// 子账号登录信息 (后端会创建 sys_user SUB,parent_user_id=主账号, 字段名跟后端 @JsonProperty 对齐)
userName: '',
password: '',
})
// 7 字段全必填 + 手机号正则
@@ -131,11 +143,11 @@ const rules = {
position: [{ required: true, message: '请输入职务', trigger: 'blur' }],
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
loginUsername: [
{ required: true, message: '请输入子账号登录账号', trigger: 'blur' },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]{2,29}$/, message: '账号以字母开头,3-30 位字母数字下划线', trigger: 'blur' },
userName: [
{ required: true, message: '请输入子账号登录用户名', trigger: 'blur' },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]{2,29}$/, message: '用户名以字母开头,3-30 位字母数字下划线', trigger: 'blur' },
],
loginPassword: [
password: [
{ required: true, message: '请输入初始密码', trigger: 'blur' },
{ min: 6, max: 30, message: '密码长度 6-30 位', trigger: 'blur' },
],
@@ -145,6 +157,27 @@ function goBack() {
router.push({ path: '/sponsor/people', query: { _t: Date.now() } })
}
// 加载主账号关联的公司 (注册时建, 按 user_id + orgType 反查, 一用户一类型一公司)
async function loadMyOrg() {
const uid = userStore.user?.userId
if (!uid) return
try {
const res = await request({
url: '/business/org/list',
method: 'get',
params: { userId: uid, orgType: 'sponsor', pageNum: 1, pageSize: 1 }
})
const rows = (res?.data?.rows) || res?.rows || []
if (rows.length) {
myOrg.value = rows[0]
// 默认带出, 提交时也直接发这个 orgId (不再依赖 orgName 字符串匹配)
form.orgName = rows[0].orgName || ''
}
} catch (e) {
console.warn('loadMyOrg:', e?.msg)
}
}
async function loadDetail() {
if (!personId) return
loadingDetail.value = true
@@ -201,8 +234,8 @@ async function onSave() {
let payload = { ...form }
let res
if (isEdit) {
delete payload.loginUsername // 编辑时不改登录账号
delete payload.loginPassword // 编辑时不改密码 (password 走独立接口)
delete payload.userName // 编辑时不改登录账号
delete payload.password // 编辑时不改密码 (password 走独立接口)
res = await bizUpdate('person', payload)
} else {
payload.unitType = 'sponsor'
@@ -213,7 +246,7 @@ async function onSave() {
// 新建成功: 提示主账号刚创建的子账号信息
const u = res.data
ElMessageBox.alert(
`子账号已创建\n账号: ${u.userName || form.loginUsername}\n初始密码: ${u.password || form.loginPassword}\n请记录后告知使用人 (密码仅本次显示)`,
`子账号已创建\n账号: ${u.userName || form.userName}\n初始密码: ${u.password || form.password}\n请记录后告知使用人 (密码仅本次显示)`,
'创建成功',
{ type: 'success', confirmButtonText: '我已记录, 返回' }
).then(() => goBack())
@@ -238,11 +271,13 @@ async function onSave() {
onMounted(() => {
if (isEdit) loadDetail()
else loadMyOrg() // 新建时默认带出主账号公司
})
</script>
<style scoped>
/* 1:1 抄 doctor/SubmissionNew.vue */
.new-person { max-width: 1200px; }
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
+2 -1
View File
@@ -45,7 +45,8 @@
</el-col>
<el-col :span="12">
<el-form-item label="角色">
<el-input :model-value="display.role" readonly />
<el-tag v-if="detail.role === 'supervisor'" type="primary" disable-transitions>监察员</el-tag>
<span v-else style="color:#909399">{{ display.role }}</span>
</el-form-item>
</el-col>
</el-row>
+5 -5
View File
@@ -66,7 +66,7 @@
<el-table-column label="已支付会务费" width="120" align="right"><template #default="{ row }">¥{{ formatMoney(row.paidMeetingAmount) }}</template></el-table-column>
<el-table-column prop="complianceScore" label="执行单位得分(合规)" width="150" align="center" />
<el-table-column prop="ratingScore" label="执行单位评价(支持方)" width="160" align="center" />
<el-table-column prop="orgName" label="赞助公司" min-width="140" show-overflow-tooltip />
<el-table-column prop="orgName" label="支持公司" min-width="140" show-overflow-tooltip />
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
<el-table-column label="是否结题" width="90" align="center">
<template #default="{ row }">
@@ -172,8 +172,8 @@
<el-descriptions-item label="项目编号">{{ detail.projectNo }}</el-descriptions-item>
<el-descriptions-item label="项目形式">{{ detail.projectForm }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ detail.projectName }}</el-descriptions-item>
<el-descriptions-item label="赞助方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="公司类型" :span="2">赞助</el-descriptions-item>
<el-descriptions-item label="支持方负责人" :span="2">{{ detail.sponsorAdminUserName || '-' }}</el-descriptions-item>
<el-descriptions-item label="公司类型" :span="2">支持</el-descriptions-item>
<el-descriptions-item label="总场次/总期数">{{ detail.totalSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="已执行/未执行">{{ detail.doneSessions || 0 }} / {{ detail.todoSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="总金额">¥{{ formatMoney(detail.totalAmount) }}</el-descriptions-item>
@@ -276,8 +276,8 @@ function onCreateMeeting(row) {
}
function onSupportUnit(row) {
if (!row.sponsorAdminUserId) { ElMessage.warning('该项目未关联赞助方负责人'); return }
ElMessage.info(`查看赞助方负责人: ${row.sponsorAdminUserName || row.sponsorAdminUserId}`)
if (!row.sponsorAdminUserId) { ElMessage.warning('该项目未关联支持方负责人'); return }
ElMessage.info(`查看支持方负责人: ${row.sponsorAdminUserName || row.sponsorAdminUserId}`)
}
async function onDeleteNotice(row) {
try { await ElMessageBox.confirm(`确定删除「${row.projectName}」的所有公告吗?删除后无法恢复`, '删除公告', { type: 'warning' }) } catch { return }
+9 -2
View File
@@ -49,7 +49,12 @@
<el-table-column prop="orgName" label="所属公司" min-width="180" show-overflow-tooltip />
<el-table-column prop="department" label="部门" width="120" />
<el-table-column prop="position" label="职务" width="120" />
<el-table-column prop="role" label="角色" width="100" />
<el-table-column label="角色" width="100">
<template #default="{ row }">
<el-tag v-if="row.role === 'supervisor'" type="primary" disable-transitions>监察员</el-tag>
<span v-else style="color:#909399"></span>
</template>
</el-table-column>
<el-table-column label="账号类型" width="90">
<template #default="{ row }">
<el-tag v-if="row.accountType" :type="row.accountType === 'MAIN' ? 'success' : 'warning'" disable-transitions>
@@ -192,7 +197,9 @@ async function onToggleStatus(row) {
await ElMessageBox.confirm(`确定要${label}${row.name}」吗?`, '确认', { type: 'warning' })
} catch { return }
try {
await bizUpdate('person', { personId: row.personId, status: newStatus })
// 必须带上 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
// 才同步 status 到 sys_user, 否则只是白更新 biz_person (而 biz_person.status 已删除)
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
ElMessage.success('已' + label)
load()
} catch (e) {
-232
View File
@@ -1,232 +0,0 @@
-- ============================================================================
-- 业务表种子数据(基于原型 /home/john/ry8080/proto/html/components/ 数据复刻)
-- ============================================================================
-- 项目
-- 2026-08-16: org_id/org_name/org_type 重命名为 sponsor_admin_user_id/sponsor_admin_user_name (存 sys_user.user_id, 不是公司)
-- sponsor01=钱七, user_id=104
INSERT INTO biz_project(project_no, project_name, project_form, total_sessions, done_sessions, todo_sessions,
total_amount, available_amount, paid_labor_amount, paid_meeting_amount, manage_fee,
is_finished, is_settled, rating_score, sponsor_admin_user_id, sponsor_admin_user_name,
start_time, end_time, create_by, create_time)
VALUES
('ZH-2026-658', '小牛血清创新应用研讨会', '线上', 26, 16, 10, 3112000.00, 1112000.00, 1000000.00, 1000000.00, 1112000.00, '0', '0', 5.0, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-659', '整合医学学会项目评审会', '线下', 18, 12, 6, 2500000.00, 980000.00, 900000.00, 620000.00, 980000.00, '0', '0', 4.5, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-650', '智慧医院建设项目方案论证会', '线上+线下', 12, 7, 5, 1800000.00, 760000.00, 700000.00, 340000.00, 760000.00, '0', '0', 4.0, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-645', '基层医疗改革试点方案中期评估会', '线下', 20, 14, 6, 2800000.00, 1050000.00, 950000.00, 800000.00, 1050000.00, '0', '0', 4.8, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-640', '数字化医疗转型方案评审会', '线上', 22, 15, 7, 3000000.00, 1200000.00, 1100000.00, 700000.00, 1200000.00, '0', '0', 4.2, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-635', '临床医学研究方案评议会', '线下', 14, 9, 5, 2200000.00, 820000.00, 740000.00, 640000.00, 820000.00, '1', '1', 4.6, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-628', '公共卫生应急体系建设研讨会', '线下', 16, 11, 5, 2400000.00, 900000.00, 820000.00, 680000.00, 900000.00, '0', '0', 4.4, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-622', '中医诊疗标准化研究论坛', '线下', 10, 6, 4, 1500000.00, 560000.00, 500000.00, 440000.00, 560000.00, '0', '0', 4.7, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-618', '医疗 AI 辅助诊断应用论坛', '线上+线下', 8, 4, 4, 1200000.00, 480000.00, 420000.00, 300000.00, 480000.00, '0', '0', 4.9, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW());
-- 项目角色劳务设置
INSERT INTO biz_project_labor_role(project_id, role_name, labor_amount)
SELECT project_id, '主席', 5000.00 FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT project_id, '主持', 3000.00 FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT project_id, '主席', 5000.00 FROM biz_project WHERE project_no='ZH-2026-659' UNION ALL
SELECT project_id, '主持', 3000.00 FROM biz_project WHERE project_no='ZH-2026-659';
-- 项目策划方案
INSERT INTO biz_project_plan(plan_name, plan_direction, plan_category, project_form, status, is_settled, project_no, remark, create_by, create_time)
VALUES
('小牛血清创新应用研讨会', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '会议项目', '线上', '待审核', '1', 'ZH-2026-658', 'XXXXXX', 'admin', NOW()),
('整合医学学会项目设计', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '科研项目', '线上', '待审核', '0', 'ZH-2026-659', 'XXXXXX', 'admin', NOW()),
('智慧医院建设项目方案', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '学术项目', '线下', '审核通过', '0', 'ZH-2026-650', 'XXXXXX', 'admin', NOW()),
('基层医疗改革试点方案', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '指南', '线下', '待审核', '0', 'ZH-2026-645', 'XXXXXX', 'admin', NOW()),
('公共卫生应急体系建设方案', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '对外交流', '线上', '审核通过', '0', 'ZH-2026-640', 'XXXXXX', 'admin', NOW()),
('数字化医疗转型方案', '专项计划一:《规范化诊疗及医疗质量提升专项计划(2026-2030年)》', '共识', '线下', '已退回', '0', 'ZH-2026-635', 'XXXXXX', 'admin', NOW());
-- 会议
INSERT INTO biz_meeting(business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, current_stage, create_by, create_time)
SELECT '5643145673', project_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '小牛血清创新应用研讨会', 3, 26, '线上', '2026-05-11 11:30:00', '2026-05-11 14:30:00', '北京XXXX有限公司', '待监管', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT '5643145674', project_id, 'ZH-2026-659', '整合医学学会项目评审会', '整合医学学会项目评审会', 18, 18, '线下', '2026-04-01 09:00:00', '2026-04-01 17:00:00', '北京XXXX有限公司', '监管通过', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-659' UNION ALL
SELECT '5643145675', project_id, 'ZH-2026-650', '智慧医院建设项目方案论证会', '智慧医院建设项目方案论证会', 7, 12, '线上+线下', '2026-03-15 14:00:00', '2026-03-15 16:30:00', '北京XXXX有限公司', '待整改', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-650' UNION ALL
SELECT '5643145676', project_id, 'ZH-2026-645', '基层医疗改革试点中期评估会', '基层医疗改革试点中期评估会', 14, 20, '线上', '2026-02-01 09:30:00', '2026-02-01 12:00:00', '北京XXXX有限公司', '已结算', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-645' UNION ALL
SELECT '5643145677', project_id, 'ZH-2026-640', '数字化医疗转型方案评审会', '数字化医疗转型方案评审会', 15, 22, '线下', '2026-01-10 10:00:00', '2026-01-10 17:00:00', '北京XXXX有限公司', '已结题', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-640';
-- 会议结算明细(会务)
INSERT INTO biz_meeting_settlement(meeting_id, project_no, project_name, exec_unit, fee_type, unit_price, qty, subtotal, remark, audit_status, settlement_type)
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '场地租赁', 5000.00, 1, 5000.00, '会议室 A', '已审核', 'meeting' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '设备租赁', 3000.00, 1, 3000.00, '投影+音响', '已审核', 'meeting' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '餐饮服务', 200.00, 30, 6000.00, '工作午餐', '已审核', 'meeting' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '资料印刷', 50.00, 40, 2000.00, '会议手册', '待审核', 'meeting' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '其他杂费', 1000.00, 1, 1000.00, '茶水等', '已审核', 'meeting' FROM biz_meeting WHERE business_id='5643145673';
-- 会议结算明细(劳务)
INSERT INTO biz_meeting_settlement(meeting_id, project_no, project_name, exec_unit, fee_type, unit_price, qty, subtotal, remark, audit_status, settlement_type)
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '专家劳务费', 5000.00, 1, 5000.00, '会议室 A', '已审核', 'labor' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '评审费', 3000.00, 1, 3000.00, '投影+音响', '已审核', 'labor' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '差旅费', 200.00, 30, 6000.00, '工作午餐', '已审核', 'labor' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '材料费', 50.00, 40, 2000.00, '会议手册', '待审核', 'labor' FROM biz_meeting WHERE business_id='5643145673' UNION ALL
SELECT meeting_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '北京会务服务公司', '其他费用', 1000.00, 1, 1000.00, '茶水等', '已审核', 'labor' FROM biz_meeting WHERE business_id='5643145673';
-- 专家
-- audit_status: 0=未提交 1=待审核 2=通过 3=拒绝 (BizAuditStatusEnum)
-- status: Y=正常 N=禁用
INSERT INTO biz_expert(name, phone, work_unit, department, title, region, id_card, audit_status, audit_by, audit_time, status, create_by, create_time)
VALUES
('张三', '13534621147', '北京XXXXXXX医院', '呼吸科', '主任医师', '北京', '110101198501011234', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
('李四', '13534621148', '北京XXXXXXX医院', '血液科', '副主任医师', '北京', '110101198801012345', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('王五', '13534621149', '北京XXXXXXX医院', '心血管科', '主治医师', '上海', '310101198901013456', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
('赵六', '13534621150', '北京XXXXXXX医院', '神经科', '副主任医师', '广东', '440101198701014567', '3', 'admin', NOW(), 'Y', 'admin', NOW()),
('钱七', '13534621151', '北京XXXXXXX医院', '内分泌科', '主任医师', '北京', '110101199001015678', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('陈教授', '13534621152', '北京XXXXXXX医院', '消化科', '副主任医师', '北京', '110101198601016789', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('王教授', '13534621153', '北京XXXXXXX医院', '心血管科', '主任医师', '上海', '310101198401017890', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('李教授', '13534621154', '北京XXXXXXX医院', '神经科', '主治医师', '北京', '110101199201018901', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('张教授', '13534621155', '北京XXXXXXX医院', '骨科', '主任医师', '北京', '110101198501019012', '3', 'admin', NOW(), 'Y', 'admin', NOW());
-- 公司表 (赞助方/执行方共用)
INSERT INTO biz_org(org_name, org_type, address, tax_no, contact_name, contact_phone, intent_count, status, create_by, create_time)
VALUES
-- 赞助方 (原 biz_support_unit 数据)
('北京XXXXXXX公司', 'sponsor', '北京市海淀区XXX路XXXX号', '91330101MA2GPR961K', '王五', '13800138001', 8, '合作中', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市朝阳区XXX路XXXX号', '91330101MA2GPR962K', '李六', '13800138002', 0, '合作中', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市西城区XXX路XXXX号', '91330101MA2GPR963K', '钱七', '13800138003', 0, '禁用', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市丰台区XXX路XXXX号', '91330101MA2GPR964K', '孙八', '13800138004', 0, '正常', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市海淀区XXX路XXXX号', '91330101MA2GPR965K', '周九', '13800138005', 0, '正常', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市朝阳区XXX路XXXX号', '91330101MA2GPR966K', '吴十', '13800138006', 0, '正常', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市西城区XXX路XXXX号', '91330101MA2GPR967K', '郑十一', '13800138007', 0, '正常', 'admin', NOW()),
('北京XXXXXXX公司', 'sponsor', '北京市海淀区XXX路XXXX号', '91330101MA2GPR968K', '王十二', '13800138008', 0, '正常', 'admin', NOW()),
-- 执行方 (原 biz_execution_unit 数据)
('北京XXXXXXX公司', 'executor', '北京市海淀区XXX路XXXX号', '91330101MA2GPR961K', NULL, NULL, 0, '0', 'admin', NOW()),
('上海XXXXXXX公司', 'executor', '上海市浦东新区XXX路XXXX号', '91330101MA2GPR962K', NULL, NULL, 0, '0', 'admin', NOW()),
('广州XXXXXXX公司', 'executor', '广州市天河区XXX路XXXX号', '91330101MA2GPR963K', NULL, NULL, 0, '0', 'admin', NOW()),
('深圳XXXXXXX公司', 'executor', '深圳市南山区XXX路XXXX号', '91330101MA2GPR964K', NULL, NULL, 0, '1', 'admin', NOW()),
('北京XXXXXXX公司', 'executor', '北京市朝阳区XXX路XXXX号', '91330101MA2GPR965K', NULL, NULL, 0, '0', 'admin', NOW()),
('北京XXXXXXX公司', 'executor', '北京市西城区XXX路XXXX号', '91330101MA2GPR966K', NULL, NULL, 0, '0', 'admin', NOW()),
('北京XXXXXXX公司', 'executor', '北京市丰台区XXX路XXXX号', '91330101MA2GPR967K', NULL, NULL, 0, '0', 'admin', NOW()),
('北京XXXXXXX公司', 'executor', '北京市昌平区XXX路XXXX号', '91330101MA2GPR968K', NULL, NULL, 0, '1', 'admin', NOW());
-- 人员(执行方会务执行, org_id 对应 biz_execution_unit 那 8 条, 取前 5 个 org_id)
INSERT INTO biz_person(name, phone, org_id, department, position, role, unit_type, status, create_by, create_time)
VALUES
('张三', '13534621147', 1, '会务部', '经理', '会务执行', 'executor', '0', 'admin', NOW()),
('李四', '13534621148', 1, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
('王五', '13534621149', 2, '会务部', '主管', '会务执行', 'executor', '0', 'admin', NOW()),
('赵六', '13534621150', 2, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
('钱七', '13534621151', 3, '会务部', '经理', '会务执行', 'executor', '0', 'admin', NOW()),
('孙八', '13534621152', 3, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW()),
('周九', '13534621153', 4, '会务部', '主管', '会务执行', 'executor', '1', 'admin', NOW()),
('吴十', '13534621154', 4, '会务部', '专员', '会务执行', 'executor', '0', 'admin', NOW());
-- 人员(合规方项目负责人/会议执行/监察员, org_id 对应 sponsor 类型 biz_org 前 5 个)
INSERT INTO biz_person(name, phone, org_id, department, position, role, unit_type, status, create_by, create_time)
VALUES
('张三', '13800138001', 1, '合规部', '经理', '项目负责人', 'executor', '0', 'admin', NOW()),
('李四', '13800138002', 2, '合规部', '专员', '会议执行', 'executor', '0', 'admin', NOW()),
('王五', '13800138003', 3, '监察组', '组长', '监察员', 'executor', '0', 'admin', NOW()),
('赵六', '13800138004', 4, '监察组', '专员', '监察员', 'executor', '0', 'admin', NOW()),
('钱七', '13800138005', 5, '合规部', '主管', '项目负责人', 'executor', '0', 'admin', NOW());
-- 支持意向
INSERT INTO biz_support_intent(project_no, project_name, name, work_unit, department, position, phone, account_status, create_time)
VALUES
('ZH-2026-658', '小牛血清创新应用研讨会', '张三', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621147', '存在', NOW()),
('ZH-2026-659', '整合医学学会项目评审', '李四', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621148', '存在', NOW()),
('ZH-2026-650', '智慧医院建设项目', '张三', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621149', '不存在', NOW()),
('ZH-2026-645', '基层医疗改革试点项目', '李四', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621150', '存在', NOW()),
('ZH-2026-640', '数字化医疗转型方案', '张三', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621151', '不存在', NOW()),
('ZH-2026-635', '临床医学研究方案评议', '李四', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621152', '存在', NOW()),
('ZH-2026-628', '公共卫生应急体系建设', '张三', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621153', '存在', NOW()),
('ZH-2026-622', '中医诊疗标准化研究', '李四', '北京XXXXXXX公司', '华北大区', '项目负责人', '13534621154', '存在', NOW());
-- 执行意向
INSERT INTO biz_execution_intent(project_no, project_name, name, work_unit, department, position, phone, onboard_status, create_time)
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '张三', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321597', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '李四', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321598', '未入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '王五', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321599', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '赵六', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321600', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '钱七', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321601', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '孙八', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321602', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '周九', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321603', '已入库', NOW() UNION ALL
SELECT 'ZH-2026-658', '小牛血清创新应用研讨会', '吴十', '北京XXXXXX公司', '华北大区', '项目负责人', '13512321604', '已入库', NOW();
-- 公示公告 (2026-08-16: biz_announcement → biz_publicity, ann_type → announce_type)
INSERT INTO biz_publicity(title, announce_type, file_url, publish_time, project_id, project_no, project_name, status, create_by, create_time)
SELECT a.title, a.announce_type, NULL, a.publish_time,
(SELECT project_id FROM biz_project WHERE project_no=a.project_no LIMIT 1),
a.project_no,
(SELECT project_name FROM biz_project WHERE project_no=a.project_no LIMIT 1),
'1', a.create_by, a.create_time
FROM (
SELECT '邀请函 2026第2323号 "恺启新生—胃癌 CAR-T 细胞治疗系列会"' AS title, 'invitation' AS announce_type, '2026-05-06 10:00:00' AS publish_time, 'ZH-2026-658' AS project_no, 'admin' AS create_by, NOW() AS create_time UNION ALL
SELECT '会议通知 2026第2277号 北京同仁医院建院140周年学术会议', 'notice', '2026-05-06 10:00:00', 'ZH-2026-659', 'admin', NOW() UNION ALL
SELECT '支持函 2026第2278号 项目启动支持', 'support', '2026-05-06 10:00:00', 'ZH-2026-650', 'admin', NOW() UNION ALL
SELECT '会议日程 2026第2279号 整合医学学会项目评审会', 'agenda', '2026-05-06 10:00:00', 'ZH-2026-659', 'admin', NOW()
) a;
-- 邀请函
INSERT INTO biz_invitation(project_id, project_no, title, publish_time)
SELECT project_id, 'ZH-2026-658', '小牛血清创新应用研讨会邀请函', NOW() FROM biz_project WHERE project_no='ZH-2026-658';
-- 支持函
INSERT INTO biz_support_letter(project_id, project_no, title, file_url, publish_time)
SELECT project_id, 'ZH-2026-658', '小牛血清创新应用研讨会支持函', '/upload/support/u635.jpg', NOW() FROM biz_project WHERE project_no='ZH-2026-658';
-- 投稿
INSERT INTO biz_submission(submitter_name, title, direction, project_form, project_category, design_file_url, status, remark, create_by, create_time)
VALUES
('张三', '小牛血清创新应用研讨会投稿', '临床医学', '线上', '学术会议类', '/upload/submission/001.pdf', '未结题', NULL, 'admin', NOW()),
('李四', '小牛血清临床应用二期', '临床医学', '线下', '学术会议类', '/upload/submission/002.pdf', '已结题', NULL, 'admin', NOW()),
('王五', '整合医学学会项目设计', '临床医学', '线上+线下', '学术会议类', '/upload/submission/003.pdf', '已退回', NULL, 'admin', NOW()),
('赵六', '基层医疗改革试点方案投稿', '临床医学', '线下', '调研征集类', '/upload/submission/004.pdf', '未结题', NULL, 'admin', NOW()),
('钱七', '智慧医院建设项目设计', '临床医学', '线上', '标准制定类', '/upload/submission/005.pdf', '已结题', NULL, 'admin', NOW()),
('孙八', '数字化医疗转型方案 V2', '临床医学', '线上', '标准制定类', '/upload/submission/006.pdf', '未结题', NULL, 'admin', NOW()),
('周九', '公共卫生应急体系设计稿', '临床医学', '线下', '调研征集类', '/upload/submission/007.pdf', '已结题', NULL, 'admin', NOW()),
('吴十', '中医诊疗标准化方案', '临床医学', '线下', '专业培训类', '/upload/submission/008.pdf', '已退回', NULL, 'admin', NOW());
-- 项目评分
INSERT INTO biz_project_rating(project_id, project_no, project_name, rater_name, rater_role, quality_score, response_score, cooperation_score, compliance_score, total_score, rating_time)
SELECT project_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '张合规', 'compliance', 5, 5, 5, 5, 5.0, NOW() FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT project_id, 'ZH-2026-659', '整合医学学会项目评审', '李合规', 'compliance', 4, 5, 4, 5, 4.5, NOW() FROM biz_project WHERE project_no='ZH-2026-659' UNION ALL
SELECT project_id, 'ZH-2026-650', '智慧医院建设项目', '王合规', 'compliance', 4, 4, 4, 4, 4.0, NOW() FROM biz_project WHERE project_no='ZH-2026-650' UNION ALL
SELECT project_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '张赞助', 'sponsor', 4, 3, 4, 3, 3.5, NOW() FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT project_id, 'ZH-2026-659', '整合医学学会项目评审', '李赞助', 'sponsor', 4, 3, 3, 4, 3.5, NOW() FROM biz_project WHERE project_no='ZH-2026-659';
-- 资料库
INSERT INTO biz_resource(name, category, file_size, file_url, uploader_name, upload_time, status)
VALUES
('项目策划模板 V2.0.docx', '项目模板', '2.3 MB', '/upload/resource/001.docx', '张三', '2026-07-15 10:30:00', '已发布'),
('会议材料模板 V1.5.docx', '会议材料', '1.8 MB', '/upload/resource/002.docx', '李四', '2026-07-12 14:20:00', '已发布'),
('合规审核要点手册.pdf', '合规文档', '5.6 MB', '/upload/resource/003.pdf', '王五', '2026-07-08 09:15:00', '已发布'),
('专家评审标准 V3.0.pdf', '专家库', '3.2 MB', '/upload/resource/004.pdf', '赵六', '2026-06-25 16:45:00', '已发布'),
('项目操作培训视频.mp4', '培训资料', '256 MB', '/upload/resource/005.mp4', '钱七', '2026-06-20 11:00:00', '已发布'),
('劳务凭证模板.xlsx', '表单模板', '0.5 MB', '/upload/resource/006.xlsx', '孙八', '2026-06-15 14:30:00', '已发布');
-- 劳务凭证
INSERT INTO biz_labor_voucher(voucher_no, meeting_id, meeting_name, expert_name, id_card, bank_account, amount, submit_date, audit_status)
VALUES
('LV-2026-001', 1, '小牛血清创新应用研讨会', '陈教授', '110101********1234', '6222****5678', 3000.00, '2026-05-12 10:00:00', '待审核'),
('LV-2026-002', 2, '整合医学学会项目评审会', '王教授', '310101********5678', '6222****1234', 2500.00, '2026-04-02 10:00:00', '已审核'),
('LV-2026-003', 3, '智慧医院建设项目方案论证', '李教授', '440101********9012', '6222****3456', 4000.00, '2026-03-16 10:00:00', '已审核'),
('LV-2026-004', 4, '基层医疗改革试点中期评估', '张教授', '320101********3456', '6222****7890', 3500.00, '2026-02-02 10:00:00', '已驳回');
-- 业务角色绑定 (2026-08-16 重构: 统一存 sys_user.role_type, 不再使用 biz_user_role_bind)
-- admin: 1=后台管理员
UPDATE sys_user SET role_type='admin' WHERE user_name='admin';
-- 创建 5 个业务角色用户 (密码 123456, RuoYi 加密后)
INSERT INTO sys_user(user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, create_by, create_time, remark, role_type)
VALUES
('leader01', '项目负责人张三', '00', 'leader01@bahim.org.cn', '13900139001', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '项目负责人', 'leader'),
('manager01', '合规员李四', '00', 'manager01@bahim.org.cn', '13900139002', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '合规人员', 'manager'),
('doctor01', '专家王五', '00', 'doctor01@bahim.org.cn', '13900139003', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '专家', 'doctor'),
('executor01', '执行负责人赵六', '00', 'executor01@bahim.org.cn', '13900139004', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '执行方', 'executor'),
('sponsor01', '赞助方钱七', '00', 'sponsor01@bahim.org.cn', '13900139005', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', 'admin', NOW(), '赞助方', 'sponsor');
-- 给所有业务用户配项目负责人角色(ruoyi role_id=2)
INSERT INTO sys_user_role(user_id, role_id)
SELECT user_id, 2 FROM sys_user WHERE user_name IN ('leader01','manager01','doctor01','executor01','sponsor01');
-- admin 已在原 RuoYi 脚本中,保留
-- ============================================================================
-- 平台协议/隐私政策 初始数据 (2026-08-16)
-- ============================================================================
INSERT INTO biz_article(title, type, content, status, create_by, create_time, remark) VALUES
('用户服务协议', 'agreement',
'<h2>用户服务协议</h2><p>欢迎使用 BAHIM 项目管理平台。请仔细阅读本协议, 注册即视为同意全部条款。</p><p>1. 用户应保证所提供资料真实有效。</p><p>2. 用户应妥善保管账号密码。</p><p>3. 平台保留最终解释权。</p>',
'0', 'admin', NOW(), '注册页底部 [我已阅读并同意《用户协议》] 链接指向此处'),
('隐私政策', 'privacy',
'<h2>隐私政策</h2><p>BAHIM 平台高度重视用户隐私, 严格按照法律法规要求保护您的个人信息。</p><p>1. 收集信息范围: 注册手机号、姓名、单位名称等业务必要字段。</p><p>2. 信息用途: 仅用于项目协作, 不会用于商业推广或对外披露。</p><p>3. 您的权利: 可随时在账号信息页查看和更正个人信息。</p>',
'0', 'admin', NOW(), '注册页底部 [《隐私政策》] 链接指向此处');
-73
View File
@@ -1,73 +0,0 @@
-- ============================================================================
-- 重新灌 5 张被清空的表 (2026-08-16, biz_schema.sql 误执行后)
-- 仅插入被清空的 5 张表: biz_project / biz_meeting / biz_expert / biz_project_assign / biz_publicity
-- 其他表已有数据, 不动
-- ============================================================================
-- 1. biz_project (9 条, sponsor_admin_user_id=104=sponsor01, sponsor_admin_user_name='赞助方钱七')
INSERT INTO biz_project(project_no, project_name, project_form, total_sessions, done_sessions, todo_sessions,
total_amount, available_amount, paid_labor_amount, paid_meeting_amount, manage_fee,
is_finished, is_settled, rating_score, sponsor_admin_user_id, sponsor_admin_user_name,
start_time, end_time, create_by, create_time)
VALUES
('ZH-2026-658', '小牛血清创新应用研讨会', '线上', 26, 16, 10, 3112000.00, 1112000.00, 1000000.00, 1000000.00, 1112000.00, '0', '0', 5.0, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-659', '整合医学学会项目评审会', '线下', 18, 12, 6, 2500000.00, 980000.00, 900000.00, 620000.00, 980000.00, '0', '0', 4.5, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-650', '智慧医院建设项目方案论证会', '线上+线下', 12, 7, 5, 1800000.00, 760000.00, 700000.00, 340000.00, 760000.00, '0', '0', 4.0, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-645', '基层医疗改革试点方案中期评估会', '线下', 20, 14, 6, 2800000.00, 1050000.00, 950000.00, 800000.00, 1050000.00, '0', '0', 4.8, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-640', '数字化医疗转型方案评审会', '线上', 22, 15, 7, 3000000.00, 1200000.00, 1100000.00, 700000.00, 1200000.00, '0', '0', 4.2, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-635', '临床医学研究方案评议会', '线下', 14, 9, 5, 2200000.00, 820000.00, 740000.00, 640000.00, 820000.00, '1', '1', 4.6, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-628', '公共卫生应急体系建设研讨会', '线下', 16, 11, 5, 2400000.00, 900000.00, 820000.00, 680000.00, 900000.00, '0', '0', 4.4, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-622', '中医诊疗标准化研究论坛', '线下', 10, 6, 4, 1500000.00, 560000.00, 500000.00, 440000.00, 560000.00, '0', '0', 4.7, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW()),
('ZH-2026-618', '医疗 AI 辅助诊断应用论坛', '线上+线下', 8, 4, 4, 1200000.00, 480000.00, 420000.00, 300000.00, 480000.00, '0', '0', 4.9, 104, '赞助方钱七', '2026-05-11 00:00:00', '2026-06-30 00:00:00', 'admin', NOW());
-- 2. biz_meeting (5 条, 关联 biz_project)
INSERT INTO biz_meeting(business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, current_stage, create_by, create_time)
SELECT '5643145673', project_id, 'ZH-2026-658', '小牛血清创新应用研讨会', '小牛血清创新应用研讨会', 3, 26, '线上', '2026-05-11 11:30:00', '2026-05-11 14:30:00', '北京XXXX有限公司', '待监管', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-658' UNION ALL
SELECT '5643145674', project_id, 'ZH-2026-659', '整合医学学会项目评审会', '整合医学学会项目评审会', 18, 18, '线下', '2026-04-01 09:00:00', '2026-04-01 17:00:00', '北京XXXX有限公司', '监管通过', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-659' UNION ALL
SELECT '5643145675', project_id, 'ZH-2026-650', '智慧医院建设项目方案论证会', '智慧医院建设项目方案论证会', 7, 12, '线上+线下', '2026-03-15 14:00:00', '2026-03-15 16:30:00', '北京XXXX有限公司', '待整改', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-650' UNION ALL
SELECT '5643145676', project_id, 'ZH-2026-645', '基层医疗改革试点中期评估会', '基层医疗改革试点中期评估会', 14, 20, '线上', '2026-02-01 09:30:00', '2026-02-01 12:00:00', '北京XXXX有限公司', '已结算', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-645' UNION ALL
SELECT '5643145677', project_id, 'ZH-2026-640', '数字化医疗转型方案评审会', '数字化医疗转型方案评审会', 15, 22, '线下', '2026-01-10 10:00:00', '2026-01-10 17:00:00', '北京XXXX有限公司', '已结题', 'admin', NOW() FROM biz_project WHERE project_no='ZH-2026-640';
-- 3. biz_expert (9 条)
INSERT INTO biz_expert(name, phone, work_unit, department, title, region, id_card, audit_status, audit_by, audit_time, status, create_by, create_time)
VALUES
('张三', '13534621147', '北京XXXXXXX医院', '呼吸科', '主任医师', '北京', '110101198501011234', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
('李四', '13534621148', '北京XXXXXXX医院', '血液科', '副主任医师', '北京', '110101198801012345', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('王五', '13534621149', '北京XXXXXXX医院', '心血管科', '主治医师', '上海', '310101198901013456', '1', 'admin', NOW(), 'Y', 'admin', NOW()),
('赵六', '13534621150', '北京XXXXXXX医院', '神经科', '副主任医师', '广东', '440101198701014567', '3', 'admin', NOW(), 'Y', 'admin', NOW()),
('钱七', '13534621151', '北京XXXXXXX医院', '内分泌科', '主任医师', '北京', '110101199001015678', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('陈教授', '13534621152', '北京XXXXXXX医院', '消化科', '副主任医师', '北京', '110101198601016789', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('王教授', '13534621153', '北京XXXXXXX医院', '心血管科', '主任医师', '上海', '310101198401017890', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('李教授', '13534621154', '北京XXXXXXX医院', '神经科', '主治医师', '北京', '110101199201018901', '2', 'admin', NOW(), 'Y', 'admin', NOW()),
('张教授', '13534621155', '北京XXXXXXX医院', '骨科', '主任医师', '北京', '110101198501019012', '3', 'admin', NOW(), 'Y', 'admin', NOW());
-- 4. biz_project_assign (10 条, 关联前 5 个项目和前 5 个执行方单位, 每个项目 2 个执行单位)
INSERT INTO biz_project_assign(project_id, execution_unit_id, execution_unit_name, sessions, amount)
SELECT p.project_id, e.unit_id, e.unit_name, e.sessions, e.amount
FROM biz_project p
JOIN (
SELECT 'ZH-2026-658' AS project_no, 9 AS unit_id, '北京XXXXXXX公司' AS unit_name, 26 AS sessions, 800000.00 AS amount UNION ALL
SELECT 'ZH-2026-658', 10, '上海XXXXXXX公司', 26, 800000.00 UNION ALL
SELECT 'ZH-2026-659', 9, '北京XXXXXXX公司', 18, 700000.00 UNION ALL
SELECT 'ZH-2026-659', 11, '广州XXXXXXX公司', 18, 600000.00 UNION ALL
SELECT 'ZH-2026-650', 10, '上海XXXXXXX公司', 12, 500000.00 UNION ALL
SELECT 'ZH-2026-650', 12, '深圳XXXXXXX公司', 12, 500000.00 UNION ALL
SELECT 'ZH-2026-645', 9, '北京XXXXXXX公司', 20, 900000.00 UNION ALL
SELECT 'ZH-2026-645', 13, '北京XXXXXXX公司', 20, 700000.00 UNION ALL
SELECT 'ZH-2026-640', 10, '上海XXXXXXX公司', 22, 1100000.00 UNION ALL
SELECT 'ZH-2026-640', 14, '北京XXXXXXX公司', 22, 800000.00
) e ON e.project_no = p.project_no;
-- 5. biz_publicity (4 条, 原 biz_announcement, 列名也改了: ann_type→announce_type)
INSERT INTO biz_publicity(title, announce_type, file_url, publish_time, project_id, project_no, project_name, status, create_by, create_time)
SELECT a.title, a.announce_type, NULL, a.publish_time,
(SELECT project_id FROM biz_project WHERE project_no=a.project_no LIMIT 1),
a.project_no,
(SELECT project_name FROM biz_project WHERE project_no=a.project_no LIMIT 1),
'1', a.create_by, a.create_time
FROM (
SELECT '邀请函 2026第2323号 "恺启新生—胃癌 CAR-T 细胞治疗系列会"' AS title, 'invitation' AS announce_type, '2026-05-06 10:00:00' AS publish_time, 'ZH-2026-658' AS project_no, 'admin' AS create_by, NOW() AS create_time UNION ALL
SELECT '会议通知 2026第2277号 北京同仁医院建院140周年学术会议', 'notice', '2026-05-06 10:00:00', 'ZH-2026-659', 'admin', NOW() UNION ALL
SELECT '支持函 2026第2278号 项目启动支持', 'support', '2026-05-06 10:00:00', 'ZH-2026-650', 'admin', NOW() UNION ALL
SELECT '会议日程 2026第2279号 整合医学学会项目评审会', 'agenda', '2026-05-06 10:00:00', 'ZH-2026-659', 'admin', NOW()
) a;
-526
View File
@@ -1,526 +0,0 @@
-- ============================================================================
-- 业务表结构 (基于原型 /home/john/ry8080/proto/html/components/ 字段提炼)
-- 目标库: ry0808
-- 引擎: InnoDB / utf8mb4
-- ============================================================================
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS biz_project;
CREATE TABLE biz_project (
project_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '项目ID',
project_no VARCHAR(50) NOT NULL COMMENT '项目编号 ZH-2026-658',
project_name VARCHAR(200) NOT NULL COMMENT '项目名称',
project_form VARCHAR(20) DEFAULT NULL COMMENT '项目形式 线上/线下/线上+线下/其他',
total_sessions INT DEFAULT 0 COMMENT '总场次/总期数',
done_sessions INT DEFAULT 0 COMMENT '已执行',
todo_sessions INT DEFAULT 0 COMMENT '未执行',
total_amount DECIMAL(15,2) DEFAULT 0 COMMENT '总金额',
available_amount DECIMAL(15,2) DEFAULT 0 COMMENT '可用金额',
paid_labor_amount DECIMAL(15,2) DEFAULT 0 COMMENT '已支付劳务费',
paid_meeting_amount DECIMAL(15,2) DEFAULT 0 COMMENT '已支付会务费',
manage_fee DECIMAL(15,2) DEFAULT 0 COMMENT '管理费及税金',
is_finished CHAR(1) DEFAULT '0' COMMENT '是否结题 0否 1是',
is_settled CHAR(1) DEFAULT '0' COMMENT '是否结算 0否 1是',
rating_score DECIMAL(3,1) DEFAULT NULL COMMENT '项目评价分数',
sponsor_admin_user_id BIGINT DEFAULT NULL COMMENT '赞助方负责人用户ID (sys_user.user_id, role_type=sponsor)',
sponsor_admin_user_name VARCHAR(200) DEFAULT NULL COMMENT '赞助方负责人用户名(冗余)',
start_time DATETIME DEFAULT NULL COMMENT '项目开始时间',
end_time DATETIME DEFAULT NULL COMMENT '项目结束时间',
submit_deadline_days INT DEFAULT 0 COMMENT '提交材料截止天数',
support_contract_url VARCHAR(500) DEFAULT NULL COMMENT '支持合同文件URL',
execute_contract_url VARCHAR(500) DEFAULT NULL COMMENT '执行合同文件URL',
invitation_url VARCHAR(500) DEFAULT NULL COMMENT '邀请函文件URL',
support_letter_url VARCHAR(500) DEFAULT NULL COMMENT '支持函文件URL',
publish_url VARCHAR(500) DEFAULT NULL COMMENT '已发布公告URL',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (project_id),
UNIQUE KEY uk_project_no (project_no),
KEY idx_project_form (project_form),
KEY idx_is_finished (is_finished),
KEY idx_is_settled (is_settled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目表';
DROP TABLE IF EXISTS biz_project_plan;
CREATE TABLE biz_project_plan (
plan_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '策划方案ID',
plan_name VARCHAR(200) NOT NULL COMMENT '策划方案名称',
plan_direction VARCHAR(500) DEFAULT NULL COMMENT '项目方向',
plan_category VARCHAR(50) DEFAULT NULL COMMENT '项目类别 会议项目/科研项目/学术项目/指南/对外交流/共识',
project_form VARCHAR(20) DEFAULT NULL COMMENT '项目形式',
design_file_url VARCHAR(500) DEFAULT NULL COMMENT '设计文件URL',
status VARCHAR(20) DEFAULT '0' COMMENT '状态 待审核/审核通过/已退回',
is_settled CHAR(1) DEFAULT '0' COMMENT '是否结算 0否 1是',
project_no VARCHAR(50) DEFAULT NULL COMMENT '关联项目编号',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
audit_opinion VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(64) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (plan_id),
KEY idx_plan_status (status),
KEY idx_plan_project_no (project_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目策划方案表';
DROP TABLE IF EXISTS biz_project_assign;
CREATE TABLE biz_project_assign (
assign_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '分配ID',
project_id BIGINT NOT NULL COMMENT '项目ID',
execution_unit_id BIGINT NOT NULL COMMENT '执行单位ID',
execution_unit_name VARCHAR(200) DEFAULT NULL COMMENT '执行单位名称(冗余)',
sessions INT DEFAULT 0 COMMENT '分配场数',
amount DECIMAL(15,2) DEFAULT 0 COMMENT '总金额',
PRIMARY KEY (assign_id),
KEY idx_assign_project (project_id),
KEY idx_assign_unit (execution_unit_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目-执行单位分配表';
DROP TABLE IF EXISTS biz_project_labor_role;
CREATE TABLE biz_project_labor_role (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
project_id BIGINT NOT NULL COMMENT '项目ID',
role_name VARCHAR(50) NOT NULL COMMENT '角色 主席/主持',
labor_amount DECIMAL(12,2) DEFAULT 0 COMMENT '劳务金额',
PRIMARY KEY (id),
KEY idx_plr_project (project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目角色劳务设置表';
DROP TABLE IF EXISTS biz_meeting;
CREATE TABLE biz_meeting (
meeting_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '会议ID',
business_id VARCHAR(50) NOT NULL COMMENT '业务会议ID 5643145673',
project_id BIGINT DEFAULT NULL COMMENT '所属项目ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '项目编号',
project_name VARCHAR(200) DEFAULT NULL COMMENT '项目名称',
meeting_name VARCHAR(200) DEFAULT NULL COMMENT '会议名称',
period_no INT DEFAULT 1 COMMENT '期数',
total_periods INT DEFAULT 1 COMMENT '总期数',
project_form VARCHAR(20) DEFAULT NULL COMMENT '项目形式',
start_time DATETIME DEFAULT NULL COMMENT '会议开始时间',
end_time DATETIME DEFAULT NULL COMMENT '会议结束时间',
org_name VARCHAR(200) DEFAULT NULL COMMENT '公司名称(冗余)',
current_stage VARCHAR(20) DEFAULT '0' COMMENT '当前阶段 未执行/待监管/监管通过/待整改/待结算/已结算/已结题',
supervision_opinion VARCHAR(500) DEFAULT NULL COMMENT '监察意见',
supervision_by VARCHAR(64) DEFAULT NULL COMMENT '监察人',
supervision_time DATETIME DEFAULT NULL COMMENT '监察时间',
invitation_url VARCHAR(500) DEFAULT NULL COMMENT '邀请函URL',
schedule_url VARCHAR(500) DEFAULT NULL COMMENT '日程海报URL',
labor_signed CHAR(1) DEFAULT '0' COMMENT '签署劳务 0未签 1已签',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建人',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新人',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (meeting_id),
UNIQUE KEY uk_business_id (business_id),
KEY idx_meeting_project (project_id),
KEY idx_meeting_stage (current_stage)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会议表';
DROP TABLE IF EXISTS biz_meeting_settlement;
CREATE TABLE biz_meeting_settlement (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
meeting_id BIGINT DEFAULT NULL COMMENT '会议ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '项目编号',
project_name VARCHAR(200) DEFAULT NULL COMMENT '会议名称',
exec_unit VARCHAR(200) DEFAULT NULL COMMENT '执行单位',
fee_type VARCHAR(50) NOT NULL COMMENT '费用项 场地租赁/设备租赁/餐饮服务/资料印刷/其他杂费/专家劳务费/评审费/差旅费/材料费/其他费用',
unit_price DECIMAL(12,2) DEFAULT 0 COMMENT '单价',
qty INT DEFAULT 0 COMMENT '数量',
subtotal DECIMAL(12,2) DEFAULT 0 COMMENT '小计',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
audit_status VARCHAR(20) DEFAULT '0' COMMENT '审核状态 已审核/待审核',
settlement_type VARCHAR(20) DEFAULT 'meeting' COMMENT '结算类型 meeting会务/labor劳务',
PRIMARY KEY (id),
KEY idx_st_meeting (meeting_id),
KEY idx_st_type (settlement_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会议结算明细';
DROP TABLE IF EXISTS biz_expert;
CREATE TABLE biz_expert (
expert_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '专家ID',
user_id BIGINT DEFAULT NULL COMMENT '关联系统用户ID',
name VARCHAR(50) NOT NULL COMMENT '姓名',
phone VARCHAR(20) NOT NULL COMMENT '手机号',
work_unit VARCHAR(200) DEFAULT NULL COMMENT '工作单位',
department VARCHAR(100) DEFAULT NULL COMMENT '科室',
title VARCHAR(50) DEFAULT NULL COMMENT '职称 主任医师/副主任医师/主治医师/住院医师',
region VARCHAR(50) DEFAULT NULL COMMENT '地区',
id_card VARCHAR(50) DEFAULT NULL COMMENT '证件号码',
bank_card VARCHAR(50) DEFAULT NULL COMMENT '银行卡号',
bank_name VARCHAR(100) DEFAULT NULL COMMENT '银行名称',
bank_province VARCHAR(50) DEFAULT NULL COMMENT '开户行省份',
bank_city VARCHAR(50) DEFAULT NULL COMMENT '开户行城市',
bank_address VARCHAR(200) DEFAULT NULL COMMENT '开户行地址',
id_card_front_url VARCHAR(500) DEFAULT NULL COMMENT '身份证正面URL',
id_card_back_url VARCHAR(500) DEFAULT NULL COMMENT '身份证反面URL',
practice_cert_url VARCHAR(500) DEFAULT NULL COMMENT '执业证书URL',
title_cert_url VARCHAR(500) DEFAULT NULL COMMENT '职称证明URL',
audit_status VARCHAR(20) DEFAULT '1' COMMENT '审核状态 0未提交 1待审核 2通过 3拒绝',
audit_opinion VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(64) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
status CHAR(1) DEFAULT 'Y' COMMENT '状态 Y正常 N禁用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (expert_id),
UNIQUE KEY uk_expert_phone (phone),
KEY idx_expert_audit (audit_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='专家表';
DROP TABLE IF EXISTS biz_execution_unit;
DROP TABLE IF EXISTS biz_support_unit;
DROP TABLE IF EXISTS biz_service_org;
CREATE TABLE biz_org (
org_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '公司ID',
org_name VARCHAR(200) NOT NULL COMMENT '公司名称',
org_type VARCHAR(20) NOT NULL COMMENT '公司类型 sponsor赞助方/executor执行方',
business_nature VARCHAR(20) DEFAULT NULL COMMENT '企业性质 私营/国营/中外合资/外资/其他',
address VARCHAR(500) DEFAULT NULL COMMENT '公司地址',
tax_no VARCHAR(50) DEFAULT NULL COMMENT '税号/统一社会信用代码',
contact_name VARCHAR(50) DEFAULT NULL COMMENT '联系人',
contact_phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
intent_count INT DEFAULT 0 COMMENT '意向项目数 (仅赞助方用)',
status VARCHAR(20) DEFAULT '0' COMMENT '合作状态 sponsor: 合作中/待签约/已停用; executor: 0正常 1禁用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (org_id),
KEY idx_org_type_status (org_type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司表 (赞助方/执行方共用)';
DROP TABLE IF EXISTS biz_person;
CREATE TABLE biz_person (
person_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '人员ID',
user_id BIGINT DEFAULT NULL COMMENT '关联系统用户ID',
name VARCHAR(50) NOT NULL COMMENT '姓名',
phone VARCHAR(20) NOT NULL COMMENT '手机号',
org_id BIGINT DEFAULT NULL COMMENT '所属公司ID (FK: biz_org.org_id)',
department VARCHAR(100) DEFAULT NULL COMMENT '部门',
position VARCHAR(50) DEFAULT NULL COMMENT '职务',
role VARCHAR(20) DEFAULT NULL COMMENT '角色 项目负责人/会议执行/监察员/会务执行',
unit_type VARCHAR(20) DEFAULT 'executor' COMMENT '所属单位类型 executor执行/sponsor支持',
status CHAR(1) DEFAULT '0' COMMENT '状态 0正常 1禁用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (person_id),
UNIQUE KEY uk_person_phone (phone),
KEY idx_person_role (role),
KEY idx_person_unit_type (unit_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人员表';
DROP TABLE IF EXISTS biz_support_intent;
CREATE TABLE biz_support_intent (
intent_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '意向项目编号',
project_name VARCHAR(200) DEFAULT NULL COMMENT '意向项目名称',
name VARCHAR(50) NOT NULL COMMENT '姓名',
work_unit VARCHAR(200) DEFAULT NULL COMMENT '工作单位名称',
department VARCHAR(100) DEFAULT NULL COMMENT '部门',
position VARCHAR(50) DEFAULT NULL COMMENT '职务',
phone VARCHAR(20) NOT NULL COMMENT '手机号',
account_status VARCHAR(20) DEFAULT '不存在' COMMENT '账号状态 存在/不存在',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (intent_id),
KEY idx_si_project (project_no),
KEY idx_si_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支持意向表';
DROP TABLE IF EXISTS biz_execution_intent;
CREATE TABLE biz_execution_intent (
intent_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '意向项目编号',
project_name VARCHAR(200) DEFAULT NULL COMMENT '意向项目名称',
name VARCHAR(50) NOT NULL COMMENT '姓名',
work_unit VARCHAR(200) DEFAULT NULL COMMENT '工作单位名称',
department VARCHAR(100) DEFAULT NULL COMMENT '部门',
position VARCHAR(50) DEFAULT NULL COMMENT '职务',
phone VARCHAR(20) NOT NULL COMMENT '手机号',
onboard_status VARCHAR(20) DEFAULT '未入库' COMMENT '是否入库 已入库/未入库',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (intent_id),
KEY idx_ei_project (project_no),
KEY idx_ei_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='执行意向表';
DROP TABLE IF EXISTS biz_announcement;
CREATE TABLE biz_announcement (
ann_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '公示ID',
title VARCHAR(500) NOT NULL COMMENT '标题',
ann_type VARCHAR(20) NOT NULL COMMENT '类型 邀请函/支持函/通知/日程',
publish_time DATETIME DEFAULT NULL COMMENT '发布时间',
file_url VARCHAR(500) DEFAULT NULL COMMENT '附件URL',
project_id BIGINT DEFAULT NULL COMMENT '关联项目ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '关联项目编号',
status CHAR(1) DEFAULT '0' COMMENT '状态 0正常 1删除',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (ann_id),
KEY idx_ann_type (ann_type),
KEY idx_ann_project (project_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目公示公告表';
DROP TABLE IF EXISTS biz_invitation;
CREATE TABLE biz_invitation (
invite_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '邀请函ID',
project_id BIGINT DEFAULT NULL COMMENT '项目ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '项目编号',
title VARCHAR(200) DEFAULT NULL COMMENT '标题',
template_url VARCHAR(500) DEFAULT NULL COMMENT '邀请函模板URL',
qr_code_url VARCHAR(500) DEFAULT NULL COMMENT '二维码URL',
share_url VARCHAR(500) DEFAULT NULL COMMENT '分享URL',
publish_time DATETIME DEFAULT NULL COMMENT '发布时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (invite_id),
KEY idx_iv_project (project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请函表';
DROP TABLE IF EXISTS biz_support_letter;
CREATE TABLE biz_support_letter (
letter_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '支持函ID',
project_id BIGINT DEFAULT NULL COMMENT '项目ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '项目编号',
title VARCHAR(200) DEFAULT NULL COMMENT '标题',
file_url VARCHAR(500) DEFAULT NULL COMMENT '支持函图片URL',
publish_time DATETIME DEFAULT NULL COMMENT '发布时间',
support_count INT DEFAULT 0 COMMENT '支持次数',
PRIMARY KEY (letter_id),
KEY idx_sl_project (project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支持函表';
DROP TABLE IF EXISTS biz_submission;
CREATE TABLE biz_submission (
sub_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '投稿ID',
submitter_id BIGINT DEFAULT NULL COMMENT '投稿人用户ID',
submitter_name VARCHAR(50) DEFAULT NULL COMMENT '投稿人姓名',
title VARCHAR(200) NOT NULL COMMENT '投稿名称',
direction VARCHAR(100) DEFAULT NULL COMMENT '学科方向',
project_form VARCHAR(20) DEFAULT NULL COMMENT '形式 线上/线下/线上+线下',
project_category VARCHAR(50) DEFAULT NULL COMMENT '项目类别 学术会议类/专项科研类/调研征集类/慈善帮扶类/标准制定类/患者援助类/专业培训类',
design_file_url VARCHAR(500) DEFAULT NULL COMMENT '设计文件URL',
status VARCHAR(20) DEFAULT '0' COMMENT '状态 待审核/审核通过/已退回/未结题/已结题',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
audit_opinion VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(64) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (sub_id),
KEY idx_sub_status (status),
KEY idx_sub_submitter (submitter_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='投稿表';
DROP TABLE IF EXISTS biz_project_rating;
CREATE TABLE biz_project_rating (
rating_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '评分ID',
project_id BIGINT NOT NULL COMMENT '项目ID',
project_no VARCHAR(50) DEFAULT NULL COMMENT '项目编号',
project_name VARCHAR(200) DEFAULT NULL COMMENT '项目名称',
rater_id BIGINT DEFAULT NULL COMMENT '评分人ID',
rater_name VARCHAR(50) DEFAULT NULL COMMENT '评分人姓名',
rater_role VARCHAR(20) DEFAULT NULL COMMENT '评分人角色 compliance/executor/sponsor',
quality_score INT DEFAULT 0 COMMENT '履约质量 1-5',
response_score INT DEFAULT 0 COMMENT '时效响应 1-5',
cooperation_score INT DEFAULT 0 COMMENT '配合度 1-5',
compliance_score INT DEFAULT 0 COMMENT '合规安全 1-5',
total_score DECIMAL(3,1) DEFAULT 0 COMMENT '总分',
remark VARCHAR(500) DEFAULT NULL COMMENT '评价备注',
rating_time DATETIME DEFAULT NULL COMMENT '评分时间',
PRIMARY KEY (rating_id),
KEY idx_pr_project (project_id),
KEY idx_pr_rater (rater_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目评分表';
DROP TABLE IF EXISTS biz_resource;
CREATE TABLE biz_resource (
resource_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '资料ID',
name VARCHAR(200) NOT NULL COMMENT '资料名称',
category VARCHAR(50) DEFAULT NULL COMMENT '分类 项目模板/会议材料/合规文档/专家库/培训资料/表单模板',
file_size VARCHAR(20) DEFAULT NULL COMMENT '文件大小',
file_url VARCHAR(500) DEFAULT NULL COMMENT '文件URL',
uploader_id BIGINT DEFAULT NULL COMMENT '上传人ID',
uploader_name VARCHAR(50) DEFAULT NULL COMMENT '上传人',
upload_time DATETIME DEFAULT NULL COMMENT '上传时间',
status VARCHAR(20) DEFAULT '0' COMMENT '状态 已发布/草稿',
PRIMARY KEY (resource_id),
KEY idx_res_category (category)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资料库表';
DROP TABLE IF EXISTS biz_labor_voucher;
CREATE TABLE biz_labor_voucher (
voucher_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '凭证ID',
voucher_no VARCHAR(50) NOT NULL COMMENT '凭证编号 LV-2026-001',
meeting_id BIGINT DEFAULT NULL COMMENT '会议ID',
meeting_name VARCHAR(200) DEFAULT NULL COMMENT '会议名称',
expert_name VARCHAR(50) DEFAULT NULL COMMENT '专家姓名',
id_card VARCHAR(50) DEFAULT NULL COMMENT '身份证号',
bank_account VARCHAR(50) DEFAULT NULL COMMENT '银行账号',
amount DECIMAL(12,2) DEFAULT 0 COMMENT '金额',
submit_date DATETIME DEFAULT NULL COMMENT '提交日期',
audit_status VARCHAR(20) DEFAULT '0' COMMENT '审核状态 待审核/已审核/已驳回',
audit_opinion VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(64) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
PRIMARY KEY (voucher_id),
UNIQUE KEY uk_voucher_no (voucher_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='劳务凭证表';
-- 注: 2026-08-16 重构, 删 biz_user_role_bind 表, 业务角色统一存 sys_user.role_type
-- sys_user 表在 RuoYi 标准 schema 里已含 role_type 列 (admin/leader/manager/doctor/executor/sponsor)
-- ============================================================================
-- 平台协议/隐私政策文章表 (2026-08-16 新增)
-- type: agreement=用户协议, privacy=隐私政策
-- ============================================================================
CREATE TABLE biz_article (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '文章ID',
title VARCHAR(200) NOT NULL COMMENT '标题',
type VARCHAR(20) NOT NULL COMMENT '类型 agreement=用户协议 privacy=隐私政策',
content MEDIUMTEXT NOT NULL COMMENT '正文 (HTML/富文本)',
status CHAR(1) DEFAULT '0' COMMENT '状态 0启用 1停用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
KEY idx_biz_article_type (type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台协议及隐私政策文章表';
-- ============================================================================
-- 七大专项计划表 (2026-08-16 新增)
-- 首页 "七大专项计划" 区块改由 admin 后台维护
-- content_type: rich=富文本 / file=上传文件 (PDF/PNG)
-- ============================================================================
CREATE TABLE biz_special_plan (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '专项计划ID',
title VARCHAR(200) NOT NULL COMMENT '专项计划名称',
content_type VARCHAR(20) NOT NULL DEFAULT 'rich' COMMENT '内容类型 rich=富文本 file=上传文件',
content MEDIUMTEXT DEFAULT NULL COMMENT '富文本内容',
file_url VARCHAR(500) DEFAULT NULL COMMENT '上传文件URL (PDF/PNG)',
sort_order INT DEFAULT 0 COMMENT '排序, 从小到大',
status CHAR(1) DEFAULT '0' COMMENT '状态 0启用 1停用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
KEY idx_special_plan_status (status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='七大专项计划表';
SET FOREIGN_KEY_CHECKS = 1;
-- ============================================================================
-- 初始化业务字典 (sys_dict_type / sys_dict_data 来自 RuoYi)
-- 这里只补业务特有字典
-- ============================================================================
INSERT INTO sys_dict_type(dict_id, dict_name, dict_type, status, create_by, create_time, remark)
VALUES
(100, '项目形式', 'biz_project_form', '0', 'admin', NOW(), '线上/线下/线上+线下/其他'),
(101, '项目状态', 'biz_project_status', '0', 'admin', NOW(), '已结题/未结题'),
(102, '结算状态', 'biz_settle_status', '0', 'admin', NOW(), '已结算/未结算'),
(103, '专家审核', 'biz_expert_audit', '0', 'admin', NOW(), '待审核/审核通过/已退回'),
(104, '合作状态', 'biz_coop_status', '0', 'admin', NOW(), '合作中/待签约/已停用'),
(105, '当前阶段', 'biz_meeting_stage', '0', 'admin', NOW(), '未执行/待监管/监管通过/待整改/待结算/已结算/已结题'),
(106, '人员角色', 'biz_person_role', '0', 'admin', NOW(), '项目负责人/会议执行/监察员/会务执行'),
(107, '企业性质', 'biz_business_nature', '0', 'admin', NOW(), '私营/国营/中外合资/外资/其他'),
(108, '项目类别', 'biz_project_category', '0', 'admin', NOW(), '会议项目/科研项目/学术项目/指南/对外交流/共识'),
(109, '学术类别', 'biz_submission_category', '0', 'admin', NOW(), '学术会议类/专项科研类/调研征集类/慈善帮扶类/标准制定类/患者援助类/专业培训类'),
(110, '审核状态', 'biz_audit_status', '0', 'admin', NOW(), '待审核/已审核/已驳回'),
(113, '公告类型', 'biz_ann_type', '0', 'admin', NOW(), '邀请函/支持函/通知/日程'),
(114, '医生职称', 'biz_doctor_title', '0', 'admin', NOW(), '主任医师/副主任医师/主治医师/住院医师'),
(115, '科室', 'biz_department', '0', 'admin', NOW(), '内科/外科/儿科/妇产科/其他'),
(116, '地区', 'biz_region', '0', 'admin', NOW(), '北京/上海/广东/其他'),
(117, '结算类型', 'biz_settlement_type', '0', 'admin', NOW(), 'meeting/labor');
INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, dict_label_en, css_class, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, 1, '线上', '线上', 'biz_project_form', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 2, 2, '线下', '线下', 'biz_project_form', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 3, 3, '线上+线下', '线上+线下', 'biz_project_form', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 4, 4, '其他', '其他', 'biz_project_form', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 5, 1, '已结题', '1', 'biz_project_status', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 6, 2, '未结题', '0', 'biz_project_status', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 7, 1, '已结算', '1', 'biz_settle_status', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 8, 2, '未结算', '0', 'biz_settle_status', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 9, 1, '待审核', '待审核', 'biz_expert_audit', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 10, 2, '审核通过', '审核通过', 'biz_expert_audit', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 11, 3, '已退回', '已退回', 'biz_expert_audit', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 12, 1, '合作中', '合作中', 'biz_coop_status', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 13, 2, '待签约', '待签约', 'biz_coop_status', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 14, 3, '已停用', '已停用', 'biz_coop_status', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 15, 1, '未执行', '未执行', 'biz_meeting_stage', '', '', 'info', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 16, 2, '待监管', '待监管', 'biz_meeting_stage', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 17, 3, '监管通过', '监管通过', 'biz_meeting_stage', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 18, 4, '待整改', '待整改', 'biz_meeting_stage', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 19, 5, '待结算', '待结算', 'biz_meeting_stage', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 20, 6, '已结算', '已结算', 'biz_meeting_stage', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 21, 7, '已结题', '已结题', 'biz_meeting_stage', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 22, 1, '项目负责人', '项目负责人', 'biz_person_role', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 23, 2, '会议执行', '会议执行', 'biz_person_role', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 24, 3, '监察员', '监察员', 'biz_person_role', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 25, 4, '会务执行', '会务执行', 'biz_person_role', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 26, 1, '私营', '私营', 'biz_business_nature', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 27, 2, '国营', '国营', 'biz_business_nature', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 28, 3, '中外合资', '中外合资', 'biz_business_nature', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 29, 4, '外资', '外资', 'biz_business_nature', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 30, 5, '其他', '其他', 'biz_business_nature', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 31, 1, '会议项目', '会议项目', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 32, 2, '科研项目', '科研项目', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 33, 3, '学术项目', '学术项目', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 34, 4, '指南', '指南', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 35, 5, '对外交流', '对外交流', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 36, 6, '共识', '共识', 'biz_project_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 37, 1, '学术会议类', '学术会议类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 38, 2, '专项科研类', '专项科研类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 39, 3, '调研征集类', '调研征集类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 40, 4, '慈善帮扶类', '慈善帮扶类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 41, 5, '标准制定类', '标准制定类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 42, 6, '患者援助类', '患者援助类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 43, 7, '专业培训类', '专业培训类', 'biz_submission_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 44, 1, '待审核', '待审核', 'biz_audit_status', '', '', 'warning', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 45, 2, '已审核', '已审核', 'biz_audit_status', '', '', 'success', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 46, 3, '已驳回', '已驳回', 'biz_audit_status', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 47, 1, '正常', '0', 'sys_normal_status', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 48, 2, '禁用', '1', 'sys_normal_status', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 49, 1, '', 'Y', 'sys_yes_no', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 50, 2, '', 'N', 'sys_yes_no', '', '', 'danger', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 51, 1, '邀请函', '邀请函', 'biz_ann_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 52, 2, '支持函', '支持函', 'biz_ann_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 53, 3, '通知', '通知', 'biz_ann_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 54, 4, '日程', '日程', 'biz_ann_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 55, 1, '主任医师', '主任医师', 'biz_doctor_title', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 56, 2, '副主任医师', '副主任医师', 'biz_doctor_title', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 57, 3, '主治医师', '主治医师', 'biz_doctor_title', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 58, 4, '住院医师', '住院医师', 'biz_doctor_title', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 59, 1, '内科', '内科', 'biz_department', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 60, 2, '外科', '外科', 'biz_department', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 61, 3, '儿科', '儿科', 'biz_department', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 62, 4, '妇产科', '妇产科', 'biz_department', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 63, 5, '其他', '其他', 'biz_department', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 64, 1, '北京', '北京', 'biz_region', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 65, 2, '上海', '上海', 'biz_region', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 66, 3, '广东', '广东', 'biz_region', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 67, 4, '其他', '其他', 'biz_region', '', '', 'default', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 68, 1, '会务', 'meeting', 'biz_settlement_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 69, 2, '劳务', 'labor', 'biz_settlement_type', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 70, 1, '项目模板', '项目模板', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 71, 2, '会议材料', '会议材料', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 72, 3, '合规文档', '合规文档', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 73, 4, '专家库', '专家库', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 74, 5, '培训资料', '培训资料', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '' UNION ALL
SELECT 75, 6, '表单模板', '表单模板', 'sys_resource_category', '', '', 'primary', 'N', '0', 'admin', NOW(), '';
-27
View File
@@ -1,27 +0,0 @@
-- ============================================================================
-- 平台协议/隐私政策文章表 (2026-08-16 新增)
-- type: agreement=用户协议, privacy=隐私政策
-- ============================================================================
CREATE TABLE IF NOT EXISTS biz_article (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '文章ID',
title VARCHAR(200) NOT NULL COMMENT '标题',
type VARCHAR(20) NOT NULL COMMENT '类型 agreement=用户协议 privacy=隐私政策',
content MEDIUMTEXT NOT NULL COMMENT '正文 (HTML/富文本)',
status CHAR(1) DEFAULT '0' COMMENT '状态 0启用 1停用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
KEY idx_biz_article_type (type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台协议及隐私政策文章表';
-- 初始数据
INSERT INTO biz_article(title, type, content, status, create_by, create_time, remark) VALUES
('用户服务协议', 'agreement',
'<h2>用户服务协议</h2><p>欢迎使用 BAHIM 项目管理平台。请仔细阅读本协议, 注册即视为同意全部条款。</p><p>1. 用户应保证所提供资料真实有效。</p><p>2. 用户应妥善保管账号密码。</p><p>3. 平台保留最终解释权。</p>',
'0', 'admin', NOW(), '注册页底部 [我已阅读并同意《用户协议》] 链接指向此处'),
('隐私政策', 'privacy',
'<h2>隐私政策</h2><p>BAHIM 平台高度重视用户隐私, 严格按照法律法规要求保护您的个人信息。</p><p>1. 收集信息范围: 注册手机号、姓名、单位名称等业务必要字段。</p><p>2. 信息用途: 仅用于项目协作, 不会用于商业推广或对外披露。</p><p>3. 您的权利: 可随时在账号信息页查看和更正个人信息。</p>',
'0', 'admin', NOW(), '注册页底部 [《隐私政策》] 链接指向此处');
-45
View File
@@ -1,45 +0,0 @@
-- ============================================================================
-- 七大专项计划表 (2026-08-16 新增)
-- 首页 "七大专项计划" 区块改由 admin 后台维护, 内容可选 富文本/上传文件
-- content_type: rich=富文本 / file=上传文件 (PDF/PNG)
-- ============================================================================
CREATE TABLE IF NOT EXISTS biz_special_plan (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '专项计划ID',
title VARCHAR(200) NOT NULL COMMENT '专项计划名称',
content_type VARCHAR(20) NOT NULL DEFAULT 'rich' COMMENT '内容类型 rich=富文本 file=上传文件',
content MEDIUMTEXT DEFAULT NULL COMMENT '富文本内容',
file_url VARCHAR(500) DEFAULT NULL COMMENT '上传文件URL (PDF/PNG)',
sort_order INT DEFAULT 0 COMMENT '排序, 从小到大',
status CHAR(1) DEFAULT '0' COMMENT '状态 0启用 1停用',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
KEY idx_special_plan_status (status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='七大专项计划表';
-- 初始化 7 个专项 (标题参考首页原硬编码, sort_order 与首页 01-07 对齐)
INSERT INTO biz_special_plan(title, content_type, content, sort_order, status, create_by, create_time) VALUES
('规范化诊疗及医疗质量提升专项计划', 'rich',
'<h3>《规范化诊疗及医疗质量提升专项计划(2026年)》</h3><p>推进临床路径标准化, 加强诊疗规范培训与质量评估, 建立多学科协作机制, 持续提升医疗服务水平。</p>',
1, '0', 'admin', NOW()),
('科研创新专项行动计划', 'rich',
'<h3>《科研创新专项行动计划(2026-2030年)》</h3><p>支持医学科研创新,推动整合医学研究成果转化与产业化应用,构建协同创新生态。</p>',
2, '0', 'admin', NOW()),
('医疗卫生人才培育专项行动计划', 'rich',
'<h3>《医疗卫生人才培育专项行动计划(2026-2030年)》</h3><p>建立多层次医学人才培养体系,重点加强基层医疗人才与跨学科复合型人才建设。</p>',
3, '0', 'admin', NOW()),
('医院管理及高质量发展促进专项计划', 'rich',
'<h3>《医院管理及高质量发展促进专项计划(2026-2030年)》</h3><p>聚焦医院管理创新与运营效率, 推广现代化管理工具, 促进医疗机构高质量发展。</p>',
4, '0', 'admin', NOW()),
('社会公益与可及性提升专项计划', 'rich',
'<h3>《社会公益与可及性提升专项计划(2026-2030年)》</h3><p>扩大优质医疗资源覆盖, 推动健康知识普及, 提升基层医疗可及性与公平性。</p>',
5, '0', 'admin', NOW()),
('政学协作综合项目专项计划', 'rich',
'<h3>《政学协作综合项目专项计划(2026-2030年)》</h3><p>深化政府、学会、医疗机构三方协作, 推动政策落地与学术成果转化, 打造协同创新示范。</p>',
6, '0', 'admin', NOW()),
('组织建设与内部治理专项计划', 'rich',
'<h3>《组织建设与内部治理专项计划(2026-2030年)》</h3><p>完善学会组织架构与制度体系, 加强内部治理与人才培养, 提升学会综合服务能力。</p>',
7, '0', 'admin', NOW());
@@ -1,45 +0,0 @@
-- ============================================================
-- Migration: biz_expert.audit_status 0/1/2/中文 → 0/1/2/3 (语义扩展)
-- Date: 2026-08-16
-- Author: Claude
-- 影响范围: biz_expert 表的 audit_status 字段
-- 旧语义: '0' = 待审核, '1' = 通过, '2' = 退回 (还有中文脏数据: '审核通过'/'待审核'/'已退回'/'已驳回')
-- 新语义: '0' = 未提交, '1' = 待审核, '2' = 通过, '3' = 拒绝
-- 注意: status 字段已改成 Y/N (见 migration_status_YN_2026_08_16.sql)
-- ============================================================
-- 0. 备份 (必须做, 用于回滚)
CREATE TABLE biz_expert_audit_status_bak_20260816 AS
SELECT expert_id, name, audit_status FROM biz_expert;
-- 1. 单条 CASE UPDATE, 一次搞定 (避免 cascading: '0'→'1' 后被 '1'→'2' 误改)
UPDATE biz_expert
SET audit_status = CASE audit_status
WHEN '0' THEN '1' -- 待审核 (旧) → 待审核 (新)
WHEN '1' THEN '2' -- 通过 (旧) → 通过 (新)
WHEN '2' THEN '3' -- 退回 (旧) → 拒绝 (新)
WHEN '审核通过' THEN '2'
WHEN '待审核' THEN '1'
WHEN '已退回' THEN '3'
WHEN '已驳回' THEN '3'
ELSE audit_status -- 未识别的值不动, 人工排查
END
WHERE audit_status IN ('0','1','2','审核通过','待审核','已退回','已驳回');
-- 2. ALTER 默认值 + comment
ALTER TABLE biz_expert
MODIFY COLUMN audit_status VARCHAR(20) DEFAULT '1' COMMENT '审核状态 0未提交 1待审核 2通过 3拒绝';
-- 3. 验证 (执行后应全部为 1/2/3)
-- SELECT audit_status, COUNT(*) FROM biz_expert GROUP BY audit_status ORDER BY audit_status;
-- 预期结果 (取决于生产数据):
-- 1 | 待审核
-- 2 | 通过
-- 3 | 拒绝
-- 4. 若有数据未转换 (上面 ELSE 分支), 用 SELECT 排查:
-- SELECT expert_id, name, audit_status FROM biz_expert
-- WHERE audit_status NOT IN ('0','1','2','3') OR audit_status IS NULL;
-- 5. 确认无误后, 可删除备份表:
-- DROP TABLE biz_expert_audit_status_bak_20260816;
-27
View File
@@ -1,27 +0,0 @@
-- ============================================================
-- Migration: biz_expert.status 0/1 → Y/N
-- Date: 2026-08-16
-- Author: Claude
-- 影响范围: biz_expert 表的 status 字段
-- 旧语义: '0' = 正常, '1' = 禁用
-- 新语义: 'Y' = 正常, 'N' = 禁用
-- 注意: audit_status (0/1/2) 不变
-- ============================================================
-- 0. 备份 (可选, 已上生产前一定要做)
-- CREATE TABLE biz_expert_status_bak_20260816 AS
-- SELECT expert_id, status FROM biz_expert;
-- 1. 数据迁移 (先转换旧值, 避免默认值变化后插入冲突)
UPDATE biz_expert SET status = 'Y' WHERE status = '0';
UPDATE biz_expert SET status = 'N' WHERE status = '1';
-- 2. 表结构变更 (默认值 + 注释)
ALTER TABLE biz_expert
MODIFY COLUMN status CHAR(1) DEFAULT 'Y' COMMENT '状态 Y正常 N禁用';
-- 3. 验证 (执行后应全部为 Y/N)
-- SELECT status, COUNT(*) FROM biz_expert GROUP BY status;
-- 预期结果:
-- Y | 10
-- N | 0
@@ -1,45 +0,0 @@
-- ============================================================================
-- 2026-08-16 重构: 删除 biz_user_role_bind 表, 业务角色统一存 sys_user.role_type
--
-- 背景:
-- 原架构在两个地方存储业务角色 (admin/leader/manager/doctor/executor/sponsor):
-- 1. sys_user.role_type 列 (RuoYi 标准列, 只被 updateRoleType 写, 从未读)
-- 2. biz_user_role_bind 表 (自建, sys_user 的 1:N 关系设计, 但 replaceUserRoleType 实际是 delete+insert 强制单绑定)
-- - 三个注册入口不一致: 专家/admin 创建走 sys_user.role_type, 执行单位走 biz_user_role_bind, 赞助方无实现
-- - /admin/users 查询 LEFT JOIN biz_user_role_bind, 因此专家显示空 role_type
-- - biz_user_role_bind 的 unit_id/unit_type 列始终为 NULL, 是死代码
--
-- 重构目标: 统一到 sys_user.role_type, 删 biz_user_role_bind
-- 改动:
-- 1. sys_user 表确保 role_type 列存在 (RuoYi 标准列, 已存在)
-- 2. 从 biz_user_role_bind 同步 role_type 到 sys_user.role_type (兜底已有数据)
-- 3. DROP TABLE biz_user_role_bind
-- ============================================================================
-- 1. 确保 sys_user.role_type 列存在 (RuoYi 标准列通常在 ruoyi-mysql.sql 已建, 这里兜底)
-- 生产环境如果已存在会报错, 用 IF NOT EXISTS 容错
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'sys_user' AND column_name = 'role_type');
SET @sql = IF(@col_exists = 0,
'ALTER TABLE sys_user ADD COLUMN role_type VARCHAR(20) DEFAULT NULL COMMENT ''业务角色 admin/leader/manager/doctor/executor/sponsor''',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2. 同步 biz_user_role_bind 的 role_type 到 sys_user.role_type
-- 一个 user 在 biz_user_role_bind 里最多 1 条 (replaceUserRoleType 强制), 用 MAX(role_type) 兜底
UPDATE sys_user u
LEFT JOIN (
SELECT user_id, MAX(role_type) AS role_type
FROM biz_user_role_bind
GROUP BY user_id
) b ON b.user_id = u.user_id
SET u.role_type = b.role_type
WHERE u.role_type IS NULL AND b.role_type IS NOT NULL;
-- 3. DROP biz_user_role_bind
DROP TABLE IF EXISTS biz_user_role_bind;
-- 4. 验证
SELECT user_id, user_name, role_type FROM sys_user WHERE role_type IS NOT NULL ORDER BY user_id LIMIT 20;