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