From ccfd03a6eb8462db5257bb12d43a41c18b679b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=BA=86=E6=B3=B0?= <12369755+htcloud1@user.noreply.gitee.com> Date: Sun, 16 Aug 2026 21:24:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ry-api/ruoyi-business/pom.xml | 4 + .../controller/BizArticleController.java | 69 +++ .../controller/BizAuthController.java | 102 ++++ .../controller/BizExecutorController.java | 3 +- .../controller/BizForgotController.java | 90 ++++ .../controller/BizPublicController.java | 36 ++ .../controller/BizSpecialPlanController.java | 71 +++ .../com/ruoyi/business/domain/BizArticle.java | 41 ++ .../com/ruoyi/business/domain/BizMeeting.java | 2 +- .../com/ruoyi/business/domain/BizProject.java | 29 +- .../ruoyi/business/domain/BizSpecialPlan.java | 51 ++ .../business/mapper/BizArticleMapper.java | 15 + .../business/mapper/BizSpecialPlanMapper.java | 20 + .../business/service/IBizArticleService.java | 18 + .../service/IBizSpecialPlanService.java | 18 + .../service/impl/BizArticleServiceImpl.java | 60 +++ .../impl/BizSpecialPlanServiceImpl.java | 39 ++ .../mapper/business/BizArticleMapper.xml | 93 ++++ .../mapper/business/BizProjectMapper.xml | 49 +- .../mapper/business/BizSpecialPlanMapper.xml | 104 ++++ ry-vue3/package-lock.json | 493 ++++++++++++++++++ ry-vue3/package.json | 2 + ry-vue3/src/api/auth.js | 10 + ry-vue3/src/components/OssFileUploader.vue | 152 ++++++ ry-vue3/src/components/RichEditor.vue | 79 +++ ry-vue3/src/layout/AdminLayout.vue | 74 ++- ry-vue3/src/router/index.js | 8 +- ry-vue3/src/utils/useAsyncLock.js | 41 ++ ry-vue3/src/views/admin/Account.vue | 9 - ry-vue3/src/views/admin/BizArticleAdmin.vue | 102 ++++ ry-vue3/src/views/admin/BizArticleEdit.vue | 107 ++++ .../src/views/admin/BizSpecialPlanAdmin.vue | 102 ++++ .../src/views/admin/BizSpecialPlanEdit.vue | 124 +++++ ry-vue3/src/views/auth/Login.vue | 380 ++++++++++++-- ry-vue3/src/views/auth/RegisterExecutor.vue | 64 ++- ry-vue3/src/views/auth/RegisterExpert.vue | 56 +- ry-vue3/src/views/auth/RegisterSponsor.vue | 10 +- ry-vue3/src/views/doctor/Account.vue | 47 +- ry-vue3/src/views/doctor/Projects.vue | 2 +- ry-vue3/src/views/executor/Projects.vue | 2 +- .../views/manager/ManagerProjectDetail.vue | 2 +- ry-vue3/src/views/manager/Projects.vue | 140 +++-- ry-vue3/src/views/manager/ProjectsNew.vue | 187 ++----- ry-vue3/src/views/portal/ArticleView.vue | 374 +++++++++++++ ry-vue3/src/views/portal/Home.vue | 109 ++-- ry-vue3/src/views/portal/Publicity.vue | 5 +- ry-vue3/src/views/portal/PublicityDetail.vue | 98 ++-- .../src/views/portal/SpecialPlanDetail.vue | 341 ++++++++++++ ry-vue3/src/views/sponsor/Account.vue | 47 +- ry-vue3/src/views/sponsor/Projects.vue | 12 +- sql/biz_data.sql | 53 +- sql/biz_reseed_5tables.sql | 73 +++ sql/biz_schema.sql | 58 ++- sql/create_biz_article.sql | 27 + sql/create_biz_special_plan.sql | 45 ++ 55 files changed, 3772 insertions(+), 577 deletions(-) create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizArticleController.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizForgotController.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSpecialPlanController.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizArticle.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSpecialPlan.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizArticleMapper.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSpecialPlanMapper.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizArticleService.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSpecialPlanService.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizArticleServiceImpl.java create mode 100644 ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSpecialPlanServiceImpl.java create mode 100644 ry-api/ruoyi-business/src/main/resources/mapper/business/BizArticleMapper.xml create mode 100644 ry-api/ruoyi-business/src/main/resources/mapper/business/BizSpecialPlanMapper.xml create mode 100644 ry-vue3/src/components/OssFileUploader.vue create mode 100644 ry-vue3/src/components/RichEditor.vue create mode 100644 ry-vue3/src/utils/useAsyncLock.js create mode 100644 ry-vue3/src/views/admin/BizArticleAdmin.vue create mode 100644 ry-vue3/src/views/admin/BizArticleEdit.vue create mode 100644 ry-vue3/src/views/admin/BizSpecialPlanAdmin.vue create mode 100644 ry-vue3/src/views/admin/BizSpecialPlanEdit.vue create mode 100644 ry-vue3/src/views/portal/ArticleView.vue create mode 100644 ry-vue3/src/views/portal/SpecialPlanDetail.vue create mode 100644 sql/biz_reseed_5tables.sql create mode 100644 sql/create_biz_article.sql create mode 100644 sql/create_biz_special_plan.sql diff --git a/ry-api/ruoyi-business/pom.xml b/ry-api/ruoyi-business/pom.xml index 519e7fe..b21ee2e 100644 --- a/ry-api/ruoyi-business/pom.xml +++ b/ry-api/ruoyi-business/pom.xml @@ -20,6 +20,10 @@ com.ruoyi ruoyi-common + + com.ruoyi + ruoyi-framework + com.ruoyi ruoyi-system diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizArticleController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizArticleController.java new file mode 100644 index 0000000..fb30791 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizArticleController.java @@ -0,0 +1,69 @@ +package com.ruoyi.business.controller; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.business.domain.BizArticle; +import com.ruoyi.business.service.IBizArticleService; +import com.ruoyi.common.utils.SecurityUtils; + +/** + * 平台协议/隐私政策文章 Controller (后台 admin) + */ +@RestController +@RequestMapping("/business/article") +public class BizArticleController extends BaseController +{ + @Autowired + private IBizArticleService articleService; + + @PreAuthorize("@ss.hasPermi('business:article:list')") + @GetMapping("/list") + public TableDataInfo list(BizArticle entity) + { + startPage(); + List list = articleService.selectList(entity); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('business:article:query')") + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(articleService.getById(id)); + } + + @PreAuthorize("@ss.hasPermi('business:article:add')") + @Log(title = "平台协议", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody BizArticle entity) + { + if (entity.getStatus() == null) entity.setStatus("0"); + entity.setCreateBy(SecurityUtils.getUsername()); + entity.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(articleService.insert(entity)); + } + + @PreAuthorize("@ss.hasPermi('business:article:edit')") + @Log(title = "平台协议", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody BizArticle entity) + { + entity.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(articleService.updateByPrimaryKey(entity)); + } + + @PreAuthorize("@ss.hasPermi('business:article:remove')") + @Log(title = "平台协议", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(articleService.deleteByPrimaryKeys(ids)); + } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java index ff078e3..1bd5579 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizAuthController.java @@ -2,6 +2,9 @@ package com.ruoyi.business.controller; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -11,9 +14,19 @@ import com.ruoyi.business.domain.BizOrg; import com.ruoyi.business.dto.SmsValidForm; import com.ruoyi.business.service.IBizOrgService; import com.ruoyi.business.service.SysSmsService; +import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.service.ISysUserService; /** @@ -27,6 +40,10 @@ import com.ruoyi.system.service.ISysUserService; * 注: 主账号不建 biz_person * * 2026-08-16 重构: 删 biz_user_role_bind, 统一用 sys_user.role_type (见迁移脚本) + * + * 2026-08-16 新增: 短信验证码登录 (loginMode=sms): + * 1. smsSendCode 发送短信 (校验手机号已注册) + * 2. smsLogin 校验短信验证码 + 颁发 JWT token (不走 AuthenticationManager, 因为无密码) */ @RestController @RequestMapping("/business/auth") @@ -44,6 +61,15 @@ public class BizAuthController extends BaseController { @Autowired private BCryptPasswordEncoder passwordEncoder; + @Autowired + private SysUserMapper userMapper; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + @PostMapping("/login") public AjaxResult login(@RequestBody Map body) { String username = (String) body.get("username"); @@ -52,6 +78,82 @@ public class BizAuthController extends BaseController { return success().put("roleType", roleType).put("token", "mock-token"); } + /** + * 登录用 - 发送短信验证码 (手机号必须已注册) + */ + @PostMapping("/smsSendCode") + public AjaxResult smsSendCode(@RequestBody Map body) { + String phone = body == null ? null : body.get("phone"); + if (phone == null || !phone.matches("^1\\d{10}$")) { + return error("请输入正确的手机号"); + } + // 登录反义: 手机号未注册则不发 + if (!userService.isPhoneRegistered(phone)) { + return error("该手机号未注册, 请先注册账号"); + } + String uuid = smsService.sendCode(phone); + return AjaxResult.success("验证码已发送", uuid); + } + + /** + * 短信验证码登录 (无密码) + * 1. 校验手机号 + 短信验证码 + * 2. 查用户 (按手机号) + * 3. 校验用户状态 + * 4. 构造 LoginUser, 颁发 JWT token (SecurityContext 一并设置, 兼容后续鉴权) + * 5. 记录登录信息 + logininfor + */ + @PostMapping("/smsLogin") + public AjaxResult smsLogin(@RequestBody Map body) { + String phone = body == null ? null : body.get("phone"); + String code = body == null ? null : body.get("smsCode"); + String uuid = body == null ? null : body.get("uuid"); + + // 1. 基础校验 + if (phone == null || !phone.matches("^1\\d{10}$")) return error("手机号格式错误"); + if (code == null || code.isEmpty()) return error("请输入短信验证码"); + + // 2. 校验短信验证码 + SmsValidForm smsForm = new SmsValidForm(); + smsForm.setPhone(phone); + smsForm.setSmsCode(code); + smsForm.setUuid(uuid); + try { + smsService.verifyCode(smsForm); + } catch (RuntimeException e) { + return error("验证码错误或已过期: " + e.getMessage()); + } + + // 3. 查用户 (mapper.checkPhoneUnique 返回 SysUser 或 null) + SysUser user = userMapper.checkPhoneUnique(phone); + if (user == null || user.getUserId() == null) { + return error("该手机号未注册"); + } + + // 4. 校验用户状态 + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + return error("账号已被删除"); + } + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + return error("账号已被停用"); + } + + // 5. 构造 LoginUser 并设 SecurityContext (兼容后续 spring security 鉴权) + LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + Authentication authentication = new UsernamePasswordAuthenticationToken( + loginUser, null, loginUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 6. 记录登录信息 + logininfor + AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, + "短信验证码登录成功")); + userService.updateLoginInfo(user.getUserId(), IpUtils.getIpAddr(), DateUtils.getNowDate()); + + // 7. 颁发 token + String token = tokenService.createToken(loginUser); + return AjaxResult.success("登录成功").put(Constants.TOKEN, token); + } + @PostMapping("/registerExecutor") public AjaxResult registerExecutor(@RequestBody Map body) { String username = (String) body.get("username"); diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExecutorController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExecutorController.java index b0a1226..beaa7d4 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExecutorController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizExecutorController.java @@ -39,8 +39,9 @@ public class BizExecutorController extends BaseController @RequestParam(required = false, defaultValue = "50") Integer pageSize) { StringBuilder sql = new StringBuilder() + // 仅返回 MAIN 主账号: 排除 SUB 子账号, 避免项目分配下拉混入下属执行人 .append("SELECT user_id, dept_id, user_name, nick_name, email, phonenumber, role_type, status, create_time ") - .append("FROM sys_user WHERE del_flag = '0' AND status = '0' AND role_type = ?"); + .append("FROM sys_user WHERE del_flag = '0' AND status = '0' AND role_type = ? AND account_type = 'MAIN'"); List args = new ArrayList<>(); args.add(roleType); if (userId != null) { diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizForgotController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizForgotController.java new file mode 100644 index 0000000..74cc1ce --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizForgotController.java @@ -0,0 +1,90 @@ +package com.ruoyi.business.controller; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.business.dto.SmsValidForm; +import com.ruoyi.business.service.SysSmsService; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.service.ISysUserService; + +/** + * 忘记密码 - 通过短信验证码重置密码 (匿名访问) + * + * 流程: + * 1. forgotSendSms 发送短信 (校验手机号已注册) + * 2. resetPassword 校验短信验证码 + 重置密码 (一次提交, 简化前端) + */ +@RestController +@RequestMapping("/business/auth") +public class BizForgotController extends BaseController { + + @Autowired + private SysSmsService smsService; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @PostMapping("/forgotSendSms") + public AjaxResult forgotSendSms(@RequestBody Map body) { + String phone = body == null ? null : body.get("phone"); + if (phone == null || !phone.matches("^1\\d{10}$")) { + return error("请输入正确的手机号"); + } + // 手机号必须已注册 (忘记密码反义: 该号查不到则不发) + if (!userService.isPhoneRegistered(phone)) { + return error("该手机号未注册, 请先注册账号"); + } + String uuid = smsService.sendCode(phone); + return AjaxResult.success("验证码已发送", uuid); + } + + @PostMapping("/resetPassword") + public AjaxResult resetPassword(@RequestBody Map body) { + String phone = (String) body.get("phone"); + String code = (String) body.get("smsCode"); + String uuid = (String) body.get("uuid"); + String password = (String) body.get("password"); + String confirmPassword = (String) body.get("confirmPassword"); + + // 1. 基础校验 + if (phone == null || !phone.matches("^1\\d{10}$")) return error("手机号格式错误"); + if (code == null || code.isEmpty()) return error("请输入短信验证码"); + if (password == null || password.length() < 6 || password.length() > 20) return error("密码长度 6-20 位"); + if (!password.equals(confirmPassword)) return error("两次密码输入不一致"); + + // 2. 校验短信验证码 + SmsValidForm smsForm = new SmsValidForm(); + smsForm.setPhone(phone); + smsForm.setSmsCode(code); + smsForm.setUuid(uuid); + try { + smsService.verifyCode(smsForm); + } catch (RuntimeException e) { + return error("验证码错误或已过期: " + e.getMessage()); + } + + // 3. 查用户 (mapper.checkPhoneUnique 返回 SysUser 或 null, IService 包装成 boolean 不能用) + SysUser info = userMapper.checkPhoneUnique(phone); + if (info == null || info.getUserId() == null) { + return error("该手机号未注册"); + } + + // 4. 重置密码 + int rows = userService.resetUserPwd(info.getUserId(), passwordEncoder.encode(password)); + return rows > 0 ? success("密码重置成功, 请用新密码登录") : error("密码重置失败"); + } +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicController.java index f43fc1e..23f25c3 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicController.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicController.java @@ -10,14 +10,18 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.business.domain.BizArticle; import com.ruoyi.business.domain.BizProject; import com.ruoyi.business.domain.BizSupportLetter; import com.ruoyi.business.domain.BizInvitation; import com.ruoyi.business.domain.BizProjectPlan; +import com.ruoyi.business.service.IBizArticleService; import com.ruoyi.business.service.IBizProjectService; import com.ruoyi.business.service.IBizSupportLetterService; import com.ruoyi.business.service.IBizInvitationService; import com.ruoyi.business.service.IBizProjectPlanService; +import com.ruoyi.business.service.IBizSpecialPlanService; +import com.ruoyi.business.domain.BizSpecialPlan; /** * 公开门户接口(无需登录) @@ -30,6 +34,38 @@ public class BizPublicController extends BaseController { @Autowired private IBizSupportLetterService supportLetterService; @Autowired private IBizInvitationService invitationService; @Autowired private IBizProjectPlanService projectPlanService; + @Autowired private IBizArticleService articleService; + @Autowired private IBizSpecialPlanService specialPlanService; + + /** + * 按类型获取已发布的协议/政策 (供注册页底部链接打开) + * type: agreement | privacy + */ + @GetMapping("/article/{type}") + public AjaxResult articleByType(@PathVariable("type") String type) + { + BizArticle article = articleService.getByType(type); + return success(article); + } + + /** + * 公开接口 - 七大专项计划 (首页用) + */ + @GetMapping("/specialPlan/list") + public AjaxResult specialPlanList() { + return success(specialPlanService.selectPublicList()); + } + + /** + * 公开接口 - 专项计划详情 (供详情页) + * 注意: id 可能是临时id (小于0 = 新建未保存), 也支持按 序号 (sortOrder) 查 + */ + @GetMapping("/specialPlan/{id}") + public AjaxResult specialPlanById(@PathVariable("id") Long id) { + BizSpecialPlan plan = specialPlanService.getById(id); + if (plan == null) return error("专项计划不存在"); + return success(plan); + } @GetMapping("/index") public AjaxResult index() { diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSpecialPlanController.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSpecialPlanController.java new file mode 100644 index 0000000..49e6e7e --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizSpecialPlanController.java @@ -0,0 +1,71 @@ +package com.ruoyi.business.controller; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.business.domain.BizSpecialPlan; +import com.ruoyi.business.service.IBizSpecialPlanService; + +/** + * 七大专项计划 Controller (后台 admin) + */ +@RestController +@RequestMapping("/business/specialPlan") +public class BizSpecialPlanController extends BaseController +{ + @Autowired + private IBizSpecialPlanService specialPlanService; + + @PreAuthorize("@ss.hasPermi('business:specialPlan:list')") + @GetMapping("/list") + public TableDataInfo list(BizSpecialPlan entity) + { + startPage(); + List list = specialPlanService.selectList(entity); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('business:specialPlan:query')") + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(specialPlanService.getById(id)); + } + + @PreAuthorize("@ss.hasPermi('business:specialPlan:add')") + @Log(title = "七大专项计划", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody BizSpecialPlan entity) + { + if (entity.getContentType() == null) entity.setContentType("rich"); + if (entity.getStatus() == null) entity.setStatus("0"); + if (entity.getSortOrder() == null) entity.setSortOrder(0); + entity.setCreateBy(SecurityUtils.getUsername()); + entity.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(specialPlanService.insert(entity)); + } + + @PreAuthorize("@ss.hasPermi('business:specialPlan:edit')") + @Log(title = "七大专项计划", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody BizSpecialPlan entity) + { + entity.setUpdateBy(SecurityUtils.getUsername()); + return toAjax(specialPlanService.updateByPrimaryKey(entity)); + } + + @PreAuthorize("@ss.hasPermi('business:specialPlan:remove')") + @Log(title = "七大专项计划", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(specialPlanService.deleteByPrimaryKeys(ids)); + } +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizArticle.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizArticle.java new file mode 100644 index 0000000..44b629d --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizArticle.java @@ -0,0 +1,41 @@ +package com.ruoyi.business.domain; + +import java.util.Date; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** 平台协议/隐私政策文章对象 BizArticle */ +public class BizArticle extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 文章ID */ + private Long id; + + /** 标题 */ + @Excel(name = "标题") + private String title; + + /** 类型 agreement=用户协议 privacy=隐私政策 */ + @Excel(name = "类型", readConverterExp = "agreement=用户协议,privacy=隐私政策") + private String type; + + /** 正文 (HTML/富文本) */ + @Excel(name = "正文") + private String content; + + /** 状态 0启用 1停用 */ + @Excel(name = "状态", readConverterExp = "0=启用,1=停用") + private String status; + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java index 5f725f6..44d1ecf 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java @@ -58,7 +58,7 @@ public class BizMeeting extends BaseEntity { private Long projectId; /** 项目名称 */ private String projectName; - /** 所属公司名称 (冗余字段, 由 biz_project.org_name 同步) */ + /** 所属公司名称 (冗余字段, 由 biz_project.sponsor_admin_user_name 同步) */ private String orgName; /** 监察意见 */ private String supervisionOpinion; diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java index e731c5f..7f00f4d 100644 --- a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java @@ -49,12 +49,11 @@ public class BizProject extends BaseEntity { private BigDecimal ratingQ3; /** 评分维度: 合规安全 */ private BigDecimal ratingQ4; - /** org_name (赞助方/执行方, 由 org_type 区分) */ - @Excel(name = "org_name") - private String orgName; - /** org_type: sponsor 赞助方 / executor 执行方 */ - @Excel(name = "org_type") - private String orgType; + /** 赞助方负责人用户名(冗余) — 原 org_name */ + @Excel(name = "sponsor_admin_user_name") + private String sponsorAdminUserName; + /** 服务机构名称 (查询条件, 仅匹配 org_type='executor' + org_name LIKE) */ + private String execOrgName; /** project_form */ @Excel(name = "project_form") private String projectForm; @@ -64,9 +63,9 @@ public class BizProject extends BaseEntity { /** is_settled */ @Excel(name = "is_settled") private String isSettled; - /** org_id (赞助方/执行方, 由 org_type 区分) */ - @Excel(name = "org_id") - private Long orgId; + /** 赞助方负责人用户ID (sys_user.user_id, role_type=sponsor) — 原 org_id */ + @Excel(name = "sponsor_admin_user_id") + private Long sponsorAdminUserId; /** create_by */ @Excel(name = "create_by") private String createBy; @@ -149,18 +148,18 @@ public class BizProject extends BaseEntity { public void setRatingQ3(BigDecimal ratingQ3) { this.ratingQ3 = ratingQ3; } public BigDecimal getRatingQ4() { return ratingQ4; } public void setRatingQ4(BigDecimal ratingQ4) { this.ratingQ4 = ratingQ4; } - public String getOrgName() { return orgName; } - public void setOrgName(String orgName) { this.orgName = orgName; } - public String getOrgType() { return orgType; } - public void setOrgType(String orgType) { this.orgType = orgType; } + public String getSponsorAdminUserName() { return sponsorAdminUserName; } + public void setSponsorAdminUserName(String sponsorAdminUserName) { this.sponsorAdminUserName = sponsorAdminUserName; } + public String getExecOrgName() { return execOrgName; } + public void setExecOrgName(String execOrgName) { this.execOrgName = execOrgName; } public String getProjectForm() { return projectForm; } public void setProjectForm(String projectForm) { this.projectForm = projectForm; } public String getIsFinished() { return isFinished; } public void setIsFinished(String isFinished) { this.isFinished = isFinished; } public String getIsSettled() { return isSettled; } public void setIsSettled(String isSettled) { this.isSettled = isSettled; } - public Long getOrgId() { return orgId; } - public void setOrgId(Long orgId) { this.orgId = orgId; } + public Long getSponsorAdminUserId() { return sponsorAdminUserId; } + public void setSponsorAdminUserId(Long sponsorAdminUserId) { this.sponsorAdminUserId = sponsorAdminUserId; } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSpecialPlan.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSpecialPlan.java new file mode 100644 index 0000000..dfba816 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizSpecialPlan.java @@ -0,0 +1,51 @@ +package com.ruoyi.business.domain; + +import java.util.Date; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** 七大专项计划对象 BizSpecialPlan */ +public class BizSpecialPlan extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 专项计划ID */ + private Long id; + + /** 专项计划名称 */ + @Excel(name = "名称") + private String title; + + /** 内容类型 rich=富文本 file=上传文件 */ + @Excel(name = "类型", readConverterExp = "rich=富文本,file=上传文件") + private String contentType; + + /** 富文本内容 */ + private String content; + + /** 上传文件URL (PDF/PNG) */ + private String fileUrl; + + /** 排序 */ + @Excel(name = "排序") + private Integer sortOrder; + + /** 状态 0启用 1停用 */ + @Excel(name = "状态", readConverterExp = "0=启用,1=停用") + private String status; + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getContentType() { return contentType; } + public void setContentType(String contentType) { this.contentType = contentType; } + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + public String getFileUrl() { return fileUrl; } + public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl; } + public Integer getSortOrder() { return sortOrder; } + public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizArticleMapper.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizArticleMapper.java new file mode 100644 index 0000000..7ff712c --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizArticleMapper.java @@ -0,0 +1,15 @@ +package com.ruoyi.business.mapper; + +import java.util.List; +import com.ruoyi.business.domain.BizArticle; + +public interface BizArticleMapper +{ + BizArticle selectByPrimaryKey(Long id); + List selectList(BizArticle entity); + BizArticle selectByType(String type); + int insert(BizArticle entity); + int updateByPrimaryKey(BizArticle entity); + int deleteByPrimaryKey(Long id); + int deleteByPrimaryKeys(Long[] ids); +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSpecialPlanMapper.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSpecialPlanMapper.java new file mode 100644 index 0000000..d62701c --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizSpecialPlanMapper.java @@ -0,0 +1,20 @@ +package com.ruoyi.business.mapper; + +import java.util.List; +import com.ruoyi.business.domain.BizSpecialPlan; + +public interface BizSpecialPlanMapper +{ + BizSpecialPlan selectByPrimaryKey(Long id); + + /** 仅查 status='0' 的, 用于首页 / 公开页 */ + List selectPublicList(); + + /** 后台管理: 按条件查全部 */ + List selectList(BizSpecialPlan entity); + + int insert(BizSpecialPlan entity); + int updateByPrimaryKey(BizSpecialPlan entity); + int deleteByPrimaryKey(Long id); + int deleteByPrimaryKeys(Long[] ids); +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizArticleService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizArticleService.java new file mode 100644 index 0000000..bb817ae --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizArticleService.java @@ -0,0 +1,18 @@ +package com.ruoyi.business.service; + +import java.util.List; +import com.ruoyi.business.domain.BizArticle; + +/** + * 平台协议/隐私政策文章 Service 接口 + */ +public interface IBizArticleService +{ + BizArticle getById(Long id); + List selectList(BizArticle entity); + BizArticle getByType(String type); + int insert(BizArticle entity); + int updateByPrimaryKey(BizArticle entity); + int deleteByPrimaryKey(Long id); + int deleteByPrimaryKeys(Long[] ids); +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSpecialPlanService.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSpecialPlanService.java new file mode 100644 index 0000000..e9812d0 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizSpecialPlanService.java @@ -0,0 +1,18 @@ +package com.ruoyi.business.service; + +import java.util.List; +import com.ruoyi.business.domain.BizSpecialPlan; + +/** + * 七大专项计划 Service 接口 + */ +public interface IBizSpecialPlanService +{ + BizSpecialPlan getById(Long id); + List selectPublicList(); + List selectList(BizSpecialPlan entity); + int insert(BizSpecialPlan entity); + int updateByPrimaryKey(BizSpecialPlan entity); + int deleteByPrimaryKey(Long id); + int deleteByPrimaryKeys(Long[] ids); +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizArticleServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizArticleServiceImpl.java new file mode 100644 index 0000000..9caac85 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizArticleServiceImpl.java @@ -0,0 +1,60 @@ +package com.ruoyi.business.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.business.domain.BizArticle; +import com.ruoyi.business.mapper.BizArticleMapper; +import com.ruoyi.business.service.IBizArticleService; + +/** + * 平台协议/隐私政策文章 Service 业务层 + */ +@Service +public class BizArticleServiceImpl implements IBizArticleService +{ + @Autowired + private BizArticleMapper articleMapper; + + @Override + public BizArticle getById(Long id) + { + return articleMapper.selectByPrimaryKey(id); + } + + @Override + public List selectList(BizArticle entity) + { + return articleMapper.selectList(entity); + } + + @Override + public BizArticle getByType(String type) + { + return articleMapper.selectByType(type); + } + + @Override + public int insert(BizArticle entity) + { + return articleMapper.insert(entity); + } + + @Override + public int updateByPrimaryKey(BizArticle entity) + { + return articleMapper.updateByPrimaryKey(entity); + } + + @Override + public int deleteByPrimaryKey(Long id) + { + return articleMapper.deleteByPrimaryKey(id); + } + + @Override + public int deleteByPrimaryKeys(Long[] ids) + { + return articleMapper.deleteByPrimaryKeys(ids); + } +} diff --git a/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSpecialPlanServiceImpl.java b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSpecialPlanServiceImpl.java new file mode 100644 index 0000000..0c34f85 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizSpecialPlanServiceImpl.java @@ -0,0 +1,39 @@ +package com.ruoyi.business.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.business.domain.BizSpecialPlan; +import com.ruoyi.business.mapper.BizSpecialPlanMapper; +import com.ruoyi.business.service.IBizSpecialPlanService; + +/** + * 七大专项计划 Service 业务层 + */ +@Service +public class BizSpecialPlanServiceImpl implements IBizSpecialPlanService +{ + @Autowired + private BizSpecialPlanMapper specialPlanMapper; + + @Override + public BizSpecialPlan getById(Long id) { return specialPlanMapper.selectByPrimaryKey(id); } + + @Override + public List selectPublicList() { return specialPlanMapper.selectPublicList(); } + + @Override + public List selectList(BizSpecialPlan entity) { return specialPlanMapper.selectList(entity); } + + @Override + public int insert(BizSpecialPlan entity) { return specialPlanMapper.insert(entity); } + + @Override + public int updateByPrimaryKey(BizSpecialPlan entity) { return specialPlanMapper.updateByPrimaryKey(entity); } + + @Override + public int deleteByPrimaryKey(Long id) { return specialPlanMapper.deleteByPrimaryKey(id); } + + @Override + public int deleteByPrimaryKeys(Long[] ids) { return specialPlanMapper.deleteByPrimaryKeys(ids); } +} \ No newline at end of file diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizArticleMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizArticleMapper.xml new file mode 100644 index 0000000..2ef5451 --- /dev/null +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizArticleMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + select id, title, type, content, status, create_by, create_time, update_by, update_time, remark + from biz_article + + + + + + + + + + insert into biz_article + + title, + type, + content, + status, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{title}, + #{type}, + #{content}, + #{status}, + #{createBy}, + sysdate(), + #{updateBy}, + sysdate(), + #{remark}, + + + + + update biz_article + + title = #{title}, + type = #{type}, + content = #{content}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate(), + remark = #{remark}, + + where id = #{id} + + + + delete from biz_article where id = #{id} + + + + delete from biz_article where id in + + #{id} + + + diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml index af5e028..91286a1 100644 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml @@ -17,12 +17,12 @@ - - + + - + @@ -43,7 +43,7 @@ - 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, org_name, org_type, project_form, is_finished, is_settled, org_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 + 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 @@ -51,8 +51,8 @@ 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.org_name, p.org_type, p.project_form, p.is_finished, p.is_settled, - p.org_id, + p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled, + p.sponsor_admin_user_id, 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, @@ -107,8 +107,26 @@ and project_no like concat('%', #{projectNo}, '%') and project_name like concat('%', #{projectName}, '%') and project_form = #{projectForm} - and org_name like concat('%', #{orgName}, '%') - and org_type = #{orgType} + + 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}, '%')) + ) + + + and exists ( + select 1 from biz_project_assign bpa + 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 is_finished = #{isFinished} and is_settled = #{isSettled} and is_published = #{isPublished} @@ -133,12 +151,11 @@ rating_q2, rating_q3, rating_q4, - org_name, - org_type, + sponsor_admin_user_name, project_form, is_finished, is_settled, - org_id, + sponsor_admin_user_id, create_by, create_time, update_by, @@ -174,12 +191,11 @@ #{ratingQ2}, #{ratingQ3}, #{ratingQ4}, - #{orgName}, - #{orgType}, + #{sponsorAdminUserName}, #{projectForm}, #{isFinished}, #{isSettled}, - #{orgId}, + #{sponsorAdminUserId}, #{createBy}, #{createTime}, #{updateBy}, @@ -231,12 +247,11 @@ rating_q2 = #{ratingQ2}, rating_q3 = #{ratingQ3}, rating_q4 = #{ratingQ4}, - org_name = #{orgName}, - org_type = #{orgType}, + sponsor_admin_user_name = #{sponsorAdminUserName}, project_form = #{projectForm}, is_finished = #{isFinished}, is_settled = #{isSettled}, - org_id = #{orgId}, + sponsor_admin_user_id = #{sponsorAdminUserId}, create_by = #{createBy}, create_time = #{createTime}, update_by = #{updateBy}, diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSpecialPlanMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSpecialPlanMapper.xml new file mode 100644 index 0000000..946222b --- /dev/null +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizSpecialPlanMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + select id, title, content_type, content, file_url, sort_order, status, + create_by, create_time, update_by, update_time, remark + from biz_special_plan + + + + + + + + + + + + insert into biz_special_plan + + title, + content_type, + content, + file_url, + sort_order, + status, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{title}, + #{contentType}, + #{content}, + #{fileUrl}, + #{sortOrder}, + #{status}, + #{createBy}, + sysdate(), + #{updateBy}, + sysdate(), + #{remark}, + + + + + update biz_special_plan + + title = #{title}, + content_type = #{contentType}, + content = #{content}, + file_url = #{fileUrl}, + sort_order = #{sortOrder}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate(), + remark = #{remark}, + + where id = #{id} + + + + delete from biz_special_plan where id = #{id} + + + + delete from biz_special_plan where id in + + #{id} + + + \ No newline at end of file diff --git a/ry-vue3/package-lock.json b/ry-vue3/package-lock.json index 1b429a0..c5efd48 100644 --- a/ry-vue3/package-lock.json +++ b/ry-vue3/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "dependencies": { "@element-plus/icons-vue": "^2.3.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", "axios": "^1.6.0", "element-plus": "^2.4.0", "pinia": "^2.1.0", @@ -63,6 +65,14 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.8", "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", @@ -1169,12 +1179,22 @@ "win32" ] }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true }, + "node_modules/@types/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==" + }, "node_modules/@types/lodash": { "version": "4.17.25", "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.25.tgz", @@ -1193,6 +1213,56 @@ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==" }, + "node_modules/@uppy/companion-client": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", + "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", + "dependencies": { + "@uppy/utils": "^4.1.2", + "namespace-emitter": "^2.0.1" + } + }, + "node_modules/@uppy/core": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", + "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", + "dependencies": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^2.1.1", + "@uppy/utils": "^4.1.3", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^3.1.25", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/store-default": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", + "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==" + }, + "node_modules/@uppy/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", + "dependencies": { + "lodash.throttle": "^4.1.1" + } + }, + "node_modules/@uppy/xhr-upload": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", + "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", + "dependencies": { + "@uppy/companion-client": "^2.2.2", + "@uppy/utils": "^4.1.2", + "nanoid": "^3.1.25" + }, + "peerDependencies": { + "@uppy/core": "^2.3.3" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1335,6 +1405,156 @@ "vue": "^3.5.0" } }, + "node_modules/@wangeditor/basic-modules": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", + "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", + "dependencies": { + "is-url": "^1.2.4" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/code-highlight": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", + "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", + "dependencies": { + "prismjs": "^1.23.0" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/core": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", + "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", + "dependencies": { + "@types/event-emitter": "^0.3.3", + "event-emitter": "^0.3.5", + "html-void-elements": "^2.0.0", + "i18next": "^20.4.0", + "scroll-into-view-if-needed": "^2.2.28", + "slate-history": "^0.66.0" + }, + "peerDependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor": { + "version": "5.1.23", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", + "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", + "dependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "^1.1.7", + "@wangeditor/code-highlight": "^1.0.3", + "@wangeditor/core": "^1.1.19", + "@wangeditor/list-module": "^1.0.5", + "@wangeditor/table-module": "^1.1.4", + "@wangeditor/upload-image-module": "^1.0.2", + "@wangeditor/video-module": "^1.1.4", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor-for-vue": { + "version": "5.1.12", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz", + "integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==", + "peerDependencies": { + "@wangeditor/editor": ">=5.1.0", + "vue": "^3.0.5" + } + }, + "node_modules/@wangeditor/list-module": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", + "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/table-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", + "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/upload-image-module": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", + "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", + "peerDependencies": { + "@uppy/core": "^2.0.3", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "1.x", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.foreach": "^4.5.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/video-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", + "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", + "peerDependencies": { + "@uppy/core": "^2.1.4", + "@uppy/xhr-upload": "^2.0.7", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz", @@ -1537,6 +1757,11 @@ "node": ">= 0.8" } }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, "node_modules/confbox": { "version": "0.1.8", "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", @@ -1548,6 +1773,18 @@ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/dayjs": { "version": "1.11.21", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", @@ -1600,6 +1837,14 @@ "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, + "node_modules/dom7": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", + "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", + "dependencies": { + "ssr-window": "^3.0.0-alpha.1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1695,6 +1940,43 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", @@ -1745,17 +2027,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/exsolve": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.1.1.tgz", "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", "dev": true }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1963,6 +2276,15 @@ "node": ">= 0.4" } }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1975,6 +2297,23 @@ "node": ">= 6" } }, + "node_modules/i18next": { + "version": "20.6.1", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", + "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", + "dependencies": { + "@babel/runtime": "^7.12.0" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "5.1.9", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.9.tgz", @@ -2037,6 +2376,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", @@ -2046,6 +2390,19 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", @@ -2099,6 +2456,42 @@ "lodash-es": "*" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", @@ -2162,6 +2555,14 @@ "node": ">= 0.6" } }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "dependencies": { + "wildcard": "^1.1.0" + } + }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", @@ -2205,6 +2606,11 @@ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz", @@ -2222,6 +2628,11 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -2380,6 +2791,31 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "engines": { + "node": ">=6" + } + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -2585,6 +3021,14 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", @@ -2596,6 +3040,35 @@ "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/slate": { + "version": "0.72.8", + "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", + "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", + "dependencies": { + "immer": "^9.0.6", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.66.0", + "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", + "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/snabbdom": { + "version": "3.6.4", + "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.4.tgz", + "integrity": "sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w==", + "engines": { + "node": ">=12.17.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2604,6 +3077,11 @@ "node": ">=0.10.0" } }, + "node_modules/ssr-window": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", + "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", @@ -2652,6 +3130,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2664,6 +3147,11 @@ "node": ">=8.0" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, "node_modules/ufo": { "version": "1.6.4", "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", @@ -3013,6 +3501,11 @@ "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/ry-vue3/package.json b/ry-vue3/package.json index 6295c3d..c243b36 100644 --- a/ry-vue3/package.json +++ b/ry-vue3/package.json @@ -9,6 +9,8 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", "axios": "^1.6.0", "element-plus": "^2.4.0", "pinia": "^2.1.0", diff --git a/ry-vue3/src/api/auth.js b/ry-vue3/src/api/auth.js index 5151855..d324323 100644 --- a/ry-vue3/src/api/auth.js +++ b/ry-vue3/src/api/auth.js @@ -18,4 +18,14 @@ export function getCaptcha() { export function getRouters() { return request({ url: '/getRouters', method: 'get' }) +} + +// 短信登录 - 发送验证码 (手机号必须已注册) +export function sendLoginSms(phone) { + return request({ url: '/business/auth/smsSendCode', method: 'post', data: { phone } }) +} + +// 短信登录 - 校验验证码并登录, 返回 res.data 含 token +export function smsLogin(data) { + return request({ url: '/business/auth/smsLogin', method: 'post', data }) } \ No newline at end of file diff --git a/ry-vue3/src/components/OssFileUploader.vue b/ry-vue3/src/components/OssFileUploader.vue new file mode 100644 index 0000000..50b68e5 --- /dev/null +++ b/ry-vue3/src/components/OssFileUploader.vue @@ -0,0 +1,152 @@ + + + + + + \ No newline at end of file diff --git a/ry-vue3/src/components/RichEditor.vue b/ry-vue3/src/components/RichEditor.vue new file mode 100644 index 0000000..f5f5cf6 --- /dev/null +++ b/ry-vue3/src/components/RichEditor.vue @@ -0,0 +1,79 @@ + + + + + \ No newline at end of file diff --git a/ry-vue3/src/layout/AdminLayout.vue b/ry-vue3/src/layout/AdminLayout.vue index f2e38b6..893f619 100644 --- a/ry-vue3/src/layout/AdminLayout.vue +++ b/ry-vue3/src/layout/AdminLayout.vue @@ -6,10 +6,24 @@
BAHIM 工作台
- - - {{ m.title }} - + @@ -41,7 +55,7 @@ import { computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useUserStore } from '@/store/user' import { logout as logoutApi } from '@/api/auth' -import { House, Document, Calendar, User, List, OfficeBuilding, Setting, Bell, EditPen, DataAnalysis, Tickets, CaretBottom, Folder, Medal, UserFilled, Connection } from '@element-plus/icons-vue' +import { House, Document, Calendar, User, List, OfficeBuilding, Setting, Bell, EditPen, DataAnalysis, Tickets, CaretBottom, Folder, Medal, UserFilled, Connection, Box, Star, Grid, Files, Compass } from '@element-plus/icons-vue' const route = useRoute() const router = useRouter() @@ -58,11 +72,17 @@ const MENU = { { path: '/admin/roles', title: '角色管理', icon: Setting }, { path: '/admin/projects', title: '项目全览', icon: Document }, { path: '/admin/meetings', title: '会议全览', icon: Calendar }, - { path: '/admin/department', title: '科室管理', icon: Folder }, - { path: '/admin/title', title: '职称管理', icon: Medal }, - { path: '/admin/experts', title: '专家管理', icon: UserFilled }, - { path: '/admin/sponsor-orgs', title: '支持单位管理', icon: Connection }, - { path: '/admin/executor-orgs', title: '服务机构管理', icon: OfficeBuilding }, + { path: '/admin/library', title: '资料库管理', icon: Box, children: [ + { path: '/admin/experts', title: '专家管理', icon: UserFilled }, + { path: '/admin/sponsor-orgs', title: '支持单位管理', icon: Star }, + { path: '/admin/executor-orgs', title: '服务机构管理', icon: OfficeBuilding }, + { path: '/admin/department', title: '科室管理', icon: Grid }, + { path: '/admin/title', title: '职称管理', icon: Medal } + ]}, + { path: '/admin/manage', title: '网站管理', icon: Setting, children: [ + { path: '/admin/article', title: '协议管理', icon: Files }, + { path: '/admin/special-plan', title: '七大专项计划', icon: Compass } + ]}, { path: '/admin/account', title: '账号信息', icon: User } ], leader: [ @@ -124,8 +144,38 @@ const onCommand = (cmd) => { if (cmd === 'logout') logout() } .logo-text { color: #fff; font-size: 16px; font-weight: 600; } .logo-sub { color: #8a99b3; font-size: 12px; } .aside-menu { background: #001529; border: none; } -:deep(.el-menu-item) { color: #c0c4cc; } -:deep(.el-menu-item.is-active) { background: #1890ff; color: #fff; } + +/* 一级菜单 + 二级父标题: 统一默认/hover/active 样式 */ +:deep(.el-menu-item), +:deep(.el-sub-menu__title) { + color: #c0c4cc; +} +:deep(.el-menu-item:hover), +:deep(.el-sub-menu__title:hover) { + background-color: #1f3a5f; + color: #fff; +} +/* 子项激活: 蓝底白字 */ +:deep(.el-menu-item.is-active) { + background: #1890ff; + color: #fff; +} +/* 父标题在子项激活时高亮 (Element Plus 自动给 el-sub-menu 加 is-active) */ +:deep(.el-sub-menu.is-active > .el-sub-menu__title) { + color: #fff; +} + +/* 二级菜单容器: 跟随侧边栏深蓝底, 不要 Element Plus 默认白底 */ +:deep(.el-menu--inline), +:deep(.el-menu--inline .el-menu-item) { + background-color: #001529; +} +:deep(.el-menu--inline .el-menu-item:hover) { + background-color: #1f3a5f; +} +:deep(.el-menu--inline .el-menu-item.is-active) { + background: #1890ff; +} .topbar { background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; border-bottom: 1px solid #ebeef5; } .topbar-left { display: flex; align-items: center; } .page-title { font-size: 16px; font-weight: 600; } diff --git a/ry-vue3/src/router/index.js b/ry-vue3/src/router/index.js index 2bf58e3..e666d7d 100644 --- a/ry-vue3/src/router/index.js +++ b/ry-vue3/src/router/index.js @@ -13,7 +13,9 @@ const routes = [ { 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: 'login', name: 'login', component: () => import('@/views/auth/Login.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: '专项计划详情' } } ] }, // 各角色后台(登录后) @@ -40,6 +42,10 @@ const routes = [ { path: 'executor-people/new', name: 'admin-executor-people-new', component: () => import('@/views/admin/ExecutorPersonNew.vue'), meta: { title: '新建人员' } }, { path: 'executor-people/edit/:id', name: 'admin-executor-people-edit', component: () => import('@/views/admin/ExecutorPersonNew.vue'), meta: { title: '编辑人员' } }, { path: 'orgs', name: 'admin-orgs', component: () => import('@/views/admin/Orgs.vue'), meta: { title: '公司管理' } }, + { path: 'article', name: 'admin-article', component: () => import('@/views/admin/BizArticleAdmin.vue'), meta: { title: '协议管理' } }, + { path: 'article/edit/:id', name: 'admin-article-edit', component: () => import('@/views/admin/BizArticleEdit.vue'), meta: { title: '编辑文章' } }, + { path: 'special-plan', name: 'admin-special-plan', component: () => import('@/views/admin/BizSpecialPlanAdmin.vue'), meta: { title: '七大专项计划' } }, + { path: 'special-plan/edit/:id', name: 'admin-special-plan-edit', component: () => import('@/views/admin/BizSpecialPlanEdit.vue'), meta: { title: '编辑专项计划' } }, { path: 'account', name: 'admin-account', component: () => import('@/views/admin/Account.vue'), meta: { title: '账号信息' } } ] }, diff --git a/ry-vue3/src/utils/useAsyncLock.js b/ry-vue3/src/utils/useAsyncLock.js new file mode 100644 index 0000000..14f965e --- /dev/null +++ b/ry-vue3/src/utils/useAsyncLock.js @@ -0,0 +1,41 @@ +import { ref } from 'vue' + +/** + * 异步操作防重入锁 (UI 层面的"防抖") + * + * 适用场景: 接口响应慢, 用户在等待期间可重复点击同一按钮, 导致并发请求. + * - 第一次调用 run(): locked 置 true, 执行 fn, 完成后 (无论成败) 解锁 + * - 锁定期间的调用: 立即 return, 不发起任何请求 + * + * 用法: + * const { locked, run } = useAsyncLock() + * + * async function sendSms() { + * if (!valid) return + * await run(async () => { + * const r = await request(...) + * // ... 业务处理 ... + * }) + * } + * + * 模板绑定: + * 发送验证码 + * + * 注意: 这是"按钮重入锁", 不是传统 lodash.debounce (后者会延迟执行). 这里 + * 第一次点击立即发出, 但锁定期间所有点击被丢弃 — 这才是"防重复请求"的正确语义. + */ +export function useAsyncLock() { + const locked = ref(false) + + async function run(fn) { + if (locked.value) return undefined + locked.value = true + try { + return await fn() + } finally { + locked.value = false + } + } + + return { locked, run } +} \ No newline at end of file diff --git a/ry-vue3/src/views/admin/Account.vue b/ry-vue3/src/views/admin/Account.vue index 44fe688..6794376 100644 --- a/ry-vue3/src/views/admin/Account.vue +++ b/ry-vue3/src/views/admin/Account.vue @@ -2,15 +2,6 @@ + + + diff --git a/ry-vue3/src/views/auth/RegisterExecutor.vue b/ry-vue3/src/views/auth/RegisterExecutor.vue index c83046a..1b917aa 100644 --- a/ry-vue3/src/views/auth/RegisterExecutor.vue +++ b/ry-vue3/src/views/auth/RegisterExecutor.vue @@ -38,8 +38,8 @@
- - {{ smsCountdown > 0 ? `${smsCountdown}s 后重试` : '获取验证码' }} + + {{ smsLocked ? '发送中...' : smsCountdown > 0 ? `${smsCountdown}s 后重试` : '获取验证码' }}
@@ -53,7 +53,11 @@ - 已阅读并接受 《平台协议》 + 我已阅读并同意 + 《用户协议》 + 和 + 《隐私政策》 + @@ -78,16 +82,6 @@ 已有账号? 立即登录 - - - -
-

1. 本平台仅提供项目撮合与流程管理服务, 不参与任何具体项目的执行.

-

2. 注册企业需提供真实有效的资质材料, 平台有权随时核查.

-

3. 平台对企业的商业信息负有保密义务.

-

4. 注册即表示同意上述条款.

-
-
@@ -96,12 +90,14 @@ import { reactive, ref, onUnmounted } from 'vue' import { ElMessage } from 'element-plus' import request from '@/utils/request' import { registerExecutor } from '@/api/public' +import { useAsyncLock } from '@/utils/useAsyncLock' + +const { locked: smsLocked, run: sendSmsOnce } = useAsyncLock() const step = ref(0) const formRef = ref() const submitting = ref(false) const agreed = ref(false) -const showAgreement = ref(false) const form = reactive({ username: '', @@ -150,25 +146,27 @@ async function sendSms() { if (!/^1\d{10}$/.test(form.phone)) { return ElMessage.warning('请先输入正确的手机号') } - 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 || '验证码发送失败') - } + 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 || '验证码发送失败') + } + }) } // ===== 提交 ===== diff --git a/ry-vue3/src/views/auth/RegisterExpert.vue b/ry-vue3/src/views/auth/RegisterExpert.vue index 2dad92a..694a1d7 100644 --- a/ry-vue3/src/views/auth/RegisterExpert.vue +++ b/ry-vue3/src/views/auth/RegisterExpert.vue @@ -36,8 +36,8 @@
- - {{ smsCountdown > 0 ? `${smsCountdown}s 后重新获取` : '获取验证码' }} + + {{ smsLocked ? '发送中...' : smsCountdown > 0 ? `${smsCountdown}s 后重新获取` : '获取验证码' }}
@@ -55,7 +55,11 @@
- 已阅读并接受 《隐私保护条款及授权书》 + 我已阅读并同意 + 《用户协议》 + 和 + 《隐私政策》 + @@ -72,16 +76,6 @@ - - - -
-

1. 我们仅在为您提供专家注册、项目评审等服务的过程中,收集您的姓名、手机号、工作单位、职称等必要信息。

-

2. 您的个人信息将严格保密,仅用于项目流程内部使用,不会向任何第三方披露。

-

3. 您有权随时查看、更正、删除您的个人信息。

-

4. 注册即表示您同意上述条款。

-
-
@@ -94,6 +88,9 @@ import request from '@/utils/request' import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue' import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue' import OssImageUploader from '@/components/OssImageUploader.vue' +import { useAsyncLock } from '@/utils/useAsyncLock' + +const { locked: smsLocked, run: sendSmsOnce } = useAsyncLock() const router = useRouter() const step = ref(0) @@ -139,7 +136,6 @@ const rules = { } const agreed = ref(false) -const showAgreement = ref(false) const smsCountdown = ref(0) let smsTimer = null @@ -148,21 +144,23 @@ async function sendCode() { if (!/^1\d{10}$/.test(form.phone)) { return ElMessage.warning('请先输入正确的手机号') } - 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 || '验证码发送失败') - } + 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() { diff --git a/ry-vue3/src/views/auth/RegisterSponsor.vue b/ry-vue3/src/views/auth/RegisterSponsor.vue index c92ed02..42cda7a 100644 --- a/ry-vue3/src/views/auth/RegisterSponsor.vue +++ b/ry-vue3/src/views/auth/RegisterSponsor.vue @@ -10,7 +10,7 @@
@@ -27,9 +27,15 @@ import { registerSponsor } from '@/api/public' import { ElMessage } from 'element-plus' const step = ref(0) const form = reactive({ username: '', password: '', unitName: '', contact: '', phone: '', intent: '' }) +const agreed = ref(false) const prev = () => step.value = Math.max(0, step.value - 1) const next = async () => { - if (step.value === 1) { try { await registerSponsor(form); ElMessage.success('已提交') } catch { /* demo */ } step.value = 2; return } + if (step.value === 1) { + if (!agreed.value) return ElMessage.warning('请先阅读并同意用户协议和隐私政策') + try { await registerSponsor(form); ElMessage.success('已提交') } catch { /* demo */ } + step.value = 2 + return + } step.value++ } diff --git a/ry-vue3/src/views/doctor/Account.vue b/ry-vue3/src/views/doctor/Account.vue index 8abfc2a..e6c6ff5 100644 --- a/ry-vue3/src/views/doctor/Account.vue +++ b/ry-vue3/src/views/doctor/Account.vue @@ -147,8 +147,8 @@
- - {{ phoneSmsCountdown > 0 ? `${phoneSmsCountdown}s 后重试` : '发送验证码' }} + + {{ phoneSmsLocked ? '发送中...' : phoneSmsCountdown > 0 ? `${phoneSmsCountdown}s 后重试` : '发送验证码' }}
@@ -174,6 +174,9 @@ import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue' import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue' import IdCardUploader from '@/components/IdCardUploader.vue' import OssImageUploader from '@/components/OssImageUploader.vue' +import { useAsyncLock } from '@/utils/useAsyncLock' + +const { locked: phoneSmsLocked, run: sendPhoneSmsOnce } = useAsyncLock() const store = useUserStore() const tab = ref('profile') @@ -409,25 +412,27 @@ async function sendSms() { if (!/^(1[0-9])\d{9}$/.test(phone)) { return ElMessage.warning('请输入正确的手机号') } - try { - const res = await request({ - url: '/system/user/profile/sendSmsCode', - method: 'post', - params: { phone } - }) - phoneSmsUuid.value = res.uuid || '' - ElMessage.success('验证码已发送') - phoneSmsCountdown.value = 60 - phoneSmsTimer = setInterval(() => { - phoneSmsCountdown.value-- - if (phoneSmsCountdown.value <= 0) { - clearInterval(phoneSmsTimer) - phoneSmsTimer = null - } - }, 1000) - } catch (e) { - ElMessage.error(e?.msg || '发送失败') - } + await sendPhoneSmsOnce(async () => { + try { + const res = await request({ + url: '/system/user/profile/sendSmsCode', + method: 'post', + params: { phone } + }) + phoneSmsUuid.value = res.uuid || '' + ElMessage.success('验证码已发送') + phoneSmsCountdown.value = 60 + phoneSmsTimer = setInterval(() => { + phoneSmsCountdown.value-- + if (phoneSmsCountdown.value <= 0) { + clearInterval(phoneSmsTimer) + phoneSmsTimer = null + } + }, 1000) + } catch (e) { + ElMessage.error(e?.msg || '发送失败') + } + }) } async function onChangePhone() { diff --git a/ry-vue3/src/views/doctor/Projects.vue b/ry-vue3/src/views/doctor/Projects.vue index 75fad4e..b733c98 100644 --- a/ry-vue3/src/views/doctor/Projects.vue +++ b/ry-vue3/src/views/doctor/Projects.vue @@ -45,7 +45,7 @@ {{ detail.projectNo }} {{ detail.projectForm || '-' }} {{ detail.projectName }} - {{ detail.orgName || '-' }} + {{ detail.sponsorAdminUserName || '-' }} {{ detail.totalSessions || 0 }} {{ detail.doneSessions || 0 }} {{ detail.todoSessions || 0 }} diff --git a/ry-vue3/src/views/executor/Projects.vue b/ry-vue3/src/views/executor/Projects.vue index d8620ac..8e87938 100644 --- a/ry-vue3/src/views/executor/Projects.vue +++ b/ry-vue3/src/views/executor/Projects.vue @@ -124,7 +124,7 @@ {{ detail.projectNo }} {{ detail.projectForm }} {{ detail.projectName }} - {{ detail.orgName || '-' }} + {{ detail.sponsorAdminUserName || '-' }} {{ detail.totalSessions }}/{{ detail.totalPeriods }} {{ detail.doneSessions || 0 }} {{ detail.todoSessions || 0 }} diff --git a/ry-vue3/src/views/manager/ManagerProjectDetail.vue b/ry-vue3/src/views/manager/ManagerProjectDetail.vue index e95d26f..9cc3ce9 100644 --- a/ry-vue3/src/views/manager/ManagerProjectDetail.vue +++ b/ry-vue3/src/views/manager/ManagerProjectDetail.vue @@ -29,7 +29,7 @@ {{ fmtDate(row.endTime) }} - {{ row.orgName || '-' }} + {{ row.sponsorAdminUserName || '-' }} {{ row.projectForm || '-' }} diff --git a/ry-vue3/src/views/manager/Projects.vue b/ry-vue3/src/views/manager/Projects.vue index cf3bd91..e5a8d87 100644 --- a/ry-vue3/src/views/manager/Projects.vue +++ b/ry-vue3/src/views/manager/Projects.vue @@ -3,18 +3,13 @@ - - - - - - - + + - - + + @@ -24,12 +19,12 @@ - + - + @@ -69,14 +64,7 @@ - - - - - + + + \ No newline at end of file diff --git a/ry-vue3/src/views/portal/Home.vue b/ry-vue3/src/views/portal/Home.vue index 9282bfb..8e266b6 100644 --- a/ry-vue3/src/views/portal/Home.vue +++ b/ry-vue3/src/views/portal/Home.vue @@ -77,83 +77,16 @@
七大专项计划
-
-
01
+
+
{{ String(i + 1).padStart(2, '0') }}
-

《规范化诊疗及医疗质量提升专项计划(2026年)》

-

聚焦临床诊疗规范制定与质量提升,推动各级医疗机构诊疗标准化、同质化。

+

《{{ p.title }}》

查看详情 - 项目提案 -
-
-
-
-
02
-
-

《科研创新专项行动计划(2026-2030年)》

-

支持医学科研创新,推动整合医学研究成果转化与产业化应用,构建协同创新生态。

-
- 查看详情 - 项目提案 -
-
-
-
-
03
-
-

《医疗卫生人才培育专项行动计划(2026-2030年)》

-

建立多层次医学人才培养体系,重点加强基层医疗人才与跨学科复合型人才建设。

-
- 查看详情 - 项目提案 -
-
-
-
-
04
-
-

《医院管理及高质量发展促进专项计划(2026-2030年)》

-

推动医院管理现代化,提升运营效率与服务水平,助力公立医院高质量发展。

-
- 查看详情 - 项目提案 -
-
-
-
-
05
-
-

《社会公益与可及性提升专项计划(2026-2030年)》

-

聚焦医疗服务的可及性与公平性,开展公益救助、基层帮扶与健康科普活动。

-
- 查看详情 - 项目提案 -
-
-
-
-
06
-
-

《政学协作综合项目专项计划(2026-2030年)》

-

搭建政府与学术机构协同平台,推动政策研究与决策支持,促进政产学研用深度融合。

-
- 查看详情 - 项目提案 -
-
-
-
-
07
-
-

《组织建设与内部治理专项计划(2026-2030年)》

-

完善学会组织架构,健全内部治理机制,提升规范化、专业化、信息化管理水平。

-
- 查看详情 - 项目提案
+
暂无专项计划
@@ -215,11 +148,26 @@ import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { useUserStore } from '@/store/user' import { logout as logoutApi } from '@/api/auth' +import request from '@/utils/request' const router = useRouter() const userStore = useUserStore() const isScrolled = ref(false) +const specialPlans = ref([]) + +async function loadSpecialPlans() { + try { + const r = await request({ url: '/business/public/specialPlan/list', method: 'get' }) + specialPlans.value = (r.data && r.data.data) || r.data || [] + } catch (e) { + specialPlans.value = [] + } +} + +function openPlan(id) { + window.open(`/#/special-plan/${id}`, '_blank') +} const topNavClass = computed(() => isScrolled.value ? 'is-scrolled' : '') const loggedIn = computed(() => !!userStore.token) const userName = computed(() => userStore.user?.userName || '用户') @@ -266,12 +214,6 @@ const planSvg = `07 08 - - - - - - 2025-2030年 @@ -291,7 +233,10 @@ function handleScroll() { isScrolled.value = window.scrollY > 10 } -onMounted(() => window.addEventListener('scroll', handleScroll, { passive: true })) +onMounted(() => { + window.addEventListener('scroll', handleScroll, { passive: true }) + loadSpecialPlans() +}) onBeforeUnmount(() => window.removeEventListener('scroll', handleScroll)) function onHome() { isScrolled.value = false } @@ -632,8 +577,16 @@ a { color: inherit; text-decoration: none; } background: #fff; border: 1px solid #e5e7eb; display: flex; + cursor: pointer; transition: border-color 0.2s, transform 0.2s; } +.specialty-empty { + grid-column: 1 / -1; + text-align: center; + color: #9ca3af; + padding: 60px 0; + font-size: 14px; +} .specialty-card:hover { border-color: var(--brand-primary); diff --git a/ry-vue3/src/views/portal/Publicity.vue b/ry-vue3/src/views/portal/Publicity.vue index 4d053be..3a4ce16 100644 --- a/ry-vue3/src/views/portal/Publicity.vue +++ b/ry-vue3/src/views/portal/Publicity.vue @@ -56,7 +56,7 @@ {{ String((currentPage - 1) * PAGE_SIZE + i + 1).padStart(3, '0') }} {{ fmtDate(n.date) }} - 【{{ n.projectName }}】 + 【{{ n.projectNo }}】 {{ n.title }}
@@ -172,8 +172,9 @@ async function load() { annId: r.projectId, date: r.publishTime, type, - title: r.projectName, + projectNo: r.projectNo || '', projectName: r.projectName, + title: r.projectName, fileUrl: urlMap[type] || r.invitationUrl || r.supportLetterUrl || r.publishUrl || '' } }) diff --git a/ry-vue3/src/views/portal/PublicityDetail.vue b/ry-vue3/src/views/portal/PublicityDetail.vue index bb9f0c3..8c90486 100644 --- a/ry-vue3/src/views/portal/PublicityDetail.vue +++ b/ry-vue3/src/views/portal/PublicityDetail.vue @@ -156,28 +156,27 @@
- -