Sfoglia il codice sorgente

feat(xg): 新增按学号重置学生密码(Web/移动端)

学工老师可按学号查询并重置学生登录密码为固定默认值,独立权限不复用系统用户重置。

Co-authored-by: Cursor <cursoragent@cursor.com>
“wuzhen” 4 giorni fa
parent
commit
df4c21bd0d

+ 24 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java

@@ -44,6 +44,7 @@ import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.xg.domain.vo.XgStudentMsgVo;
+import org.dromara.xg.domain.vo.StudentPwdResetVo;
 import org.dromara.xg.domain.bo.XgStudentMsgBo;
 import org.dromara.xg.domain.bo.XgStudentMsgAdvancedSearchBo;
 import org.dromara.xg.service.IXgStudentMsgService;
@@ -660,6 +661,29 @@ public class XgStudentMsgController extends BaseController {
         return R.ok(studentInfo);
     }
 
+    /**
+     * 按学号查询学生基本信息(重置密码页)
+     * 须声明在 /{id} 之前,避免被路径变量吞掉
+     */
+    @SaCheckPermission("studentMsg:studentPwdReset:query")
+    @GetMapping("/queryByStudentNo/{studentNo}")
+    public R<StudentPwdResetVo> queryByStudentNo(
+        @NotBlank(message = "学号不能为空") @PathVariable("studentNo") String studentNo) {
+        return R.ok(xgStudentMsgService.queryForPwdResetByStudentNo(studentNo));
+    }
+
+    /**
+     * 按学号重置学生登录密码为固定默认值
+     */
+    @SaCheckPermission("studentMsg:studentPwdReset:reset")
+    @Log(title = "重置学生密码", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/resetStudentPassword/{studentNo}")
+    public R<Void> resetStudentPassword(
+        @NotBlank(message = "学号不能为空") @PathVariable("studentNo") String studentNo) {
+        return toAjax(xgStudentMsgService.resetStudentPasswordByStudentNo(studentNo));
+    }
+
     /**
      * 获取学生信息详细信息
      *

+ 31 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/StudentPwdResetVo.java

@@ -0,0 +1,31 @@
+package org.dromara.xg.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 重置学生密码:按学号查询展示信息
+ */
+@Data
+public class StudentPwdResetVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 学生ID(与 sys_user.user_id 相同) */
+    private Long id;
+
+    /** 学号 */
+    private String studentNo;
+
+    /** 姓名 */
+    private String name;
+
+    /** 班级名称 */
+    private String className;
+
+    /** 班主任姓名 */
+    private String classTeacherName;
+}

+ 17 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java

@@ -3,6 +3,7 @@ package org.dromara.xg.service;
 import org.dromara.xg.domain.vo.XgStudentMsgExportVo;
 import org.dromara.xg.domain.vo.XgStudentMsgVo;
 import org.dromara.xg.domain.vo.XgStudentTreeVo;
+import org.dromara.xg.domain.vo.StudentPwdResetVo;
 import org.dromara.xg.domain.bo.XgStudentMsgBo;
 import org.dromara.xg.domain.bo.XgStudentMsgAdvancedSearchBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -176,4 +177,20 @@ public interface IXgStudentMsgService {
      * @return 学生信息树
      */
     List<XgStudentTreeVo> getCurrentUserStudentTree();
+
+    /**
+     * 按学号查询学生基本信息(重置密码页展示)
+     *
+     * @param studentNo 学号
+     * @return 姓名/学号/班级/班主任
+     */
+    StudentPwdResetVo queryForPwdResetByStudentNo(String studentNo);
+
+    /**
+     * 按学号重置学生登录密码为固定默认值
+     *
+     * @param studentNo 学号
+     * @return 是否成功
+     */
+    Boolean resetStudentPasswordByStudentNo(String studentNo);
 }

+ 77 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java

@@ -25,6 +25,8 @@ import org.dromara.system.mapper.SysDeptMapper;
 import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.SysDept;
 import org.dromara.system.domain.vo.SysDeptVo;
+import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.xg.domain.vo.StudentPwdResetVo;
 import cn.hutool.crypto.digest.BCrypt;
 import cn.hutool.core.util.ObjectUtil;
 import java.util.List;
@@ -1557,4 +1559,79 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
     private Long getCurrentUserCollegeId() {
         return currentUserCollegeHelper.resolveCurrentUserCollegeId();
     }
+
+    /** 学生账号重置密码固定值(仅用于管理端重置学生密码) */
+    private static final String STUDENT_RESET_PASSWORD = "Hzdyjsxs123456!@#";
+
+    @Override
+    public StudentPwdResetVo queryForPwdResetByStudentNo(String studentNo) {
+        if (StringUtils.isBlank(studentNo)) {
+            throw new ServiceException("学号不能为空");
+        }
+        XgStudentMsg student = baseMapper.selectOne(
+            Wrappers.<XgStudentMsg>lambdaQuery()
+                .eq(XgStudentMsg::getStudentNo, studentNo.trim())
+                .last("LIMIT 1")
+        );
+        if (student == null) {
+            throw new ServiceException("未找到学号对应的学生信息");
+        }
+        StudentPwdResetVo vo = new StudentPwdResetVo();
+        vo.setId(student.getId());
+        vo.setStudentNo(student.getStudentNo());
+        vo.setName(student.getName());
+        vo.setClassName(student.getClassName());
+        vo.setClassTeacherName(student.getClassTeacherName());
+        // 学生档案班主任为空时,回退班级表
+        if (StringUtils.isBlank(vo.getClassTeacherName()) && StringUtils.isNotBlank(student.getClassId())) {
+            try {
+                XgClassMsg classMsg = xgClassMsgMapper.selectById(Long.valueOf(student.getClassId()));
+                if (classMsg != null && StringUtils.isNotBlank(classMsg.getClassTeacherName())) {
+                    vo.setClassTeacherName(classMsg.getClassTeacherName());
+                }
+                if (StringUtils.isBlank(vo.getClassName()) && classMsg != null) {
+                    vo.setClassName(classMsg.getClassName());
+                }
+            } catch (NumberFormatException ignored) {
+                // classId 非数字时忽略回退
+            }
+        }
+        return vo;
+    }
+
+    @Override
+    public Boolean resetStudentPasswordByStudentNo(String studentNo) {
+        if (StringUtils.isBlank(studentNo)) {
+            throw new ServiceException("学号不能为空");
+        }
+        String no = studentNo.trim();
+        XgStudentMsg student = baseMapper.selectOne(
+            Wrappers.<XgStudentMsg>lambdaQuery()
+                .eq(XgStudentMsg::getStudentNo, no)
+                .last("LIMIT 1")
+        );
+        if (student == null) {
+            throw new ServiceException("未找到学号对应的学生信息");
+        }
+        if (student.getId() == null) {
+            throw new ServiceException("学生数据异常,缺少主键");
+        }
+        SysUserVo user = sysUserService.selectUserById(student.getId());
+        if (user == null) {
+            throw new ServiceException("该学生没有登录账号,无法重置密码");
+        }
+        if (!"2".equals(user.getAccountType())) {
+            throw new ServiceException("该账号不是学生账号,禁止重置");
+        }
+        if (StringUtils.isNotBlank(user.getUserName()) && !no.equals(user.getUserName())) {
+            throw new ServiceException("学号与登录账号不一致,请联系管理员");
+        }
+        int rows = sysUserService.resetUserPwd(student.getId(), BCrypt.hashpw(STUDENT_RESET_PASSWORD));
+        if (rows <= 0) {
+            throw new ServiceException("重置密码失败,请稍后重试");
+        }
+        log.info("学生密码已重置, studentNo={}, studentId={}, operator={}",
+            no, student.getId(), LoginHelper.getUserId());
+        return true;
+    }
 }

+ 76 - 0
backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql

@@ -0,0 +1,76 @@
+-- =============================================================================
+-- 重置学生密码:菜单 + 按钮权限
+-- 日期:2026-10-26
+-- 父菜单:学生管理 (1959499382894141441)
+-- Web 页面:xg/xsjcxx/studentMsg/studentPwdReset/index
+-- 移动端首页入口权限:student:pwdreset:listapp
+-- 重置密码固定值(服务端写死):Hzdyjsxs123456!@#
+-- 执行:开发/生产库均可幂等执行;执行后给角色勾选菜单或跑下方角色授权段
+-- =============================================================================
+
+-- 1. 菜单页(学生管理下级)
+INSERT INTO `sys_menu` (
+  `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`,
+  `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+  `create_dept`, `create_by`, `create_time`, `remark`
+)
+SELECT 2026102610000000101, '重置学生密码', 1959499382894141441, 16, 'studentPwdReset',
+       'xg/xsjcxx/studentMsg/studentPwdReset/index', NULL,
+       1, 0, 'C', '0', '0', 'studentMsg:studentPwdReset:list', 'password',
+       103, 1, NOW(), '按学号查询并重置学生登录密码'
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000101);
+
+-- 2. 按钮:查询
+INSERT INTO `sys_menu` (
+  `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`,
+  `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+  `create_dept`, `create_by`, `create_time`, `remark`
+)
+SELECT 2026102610000000102, '查询学生', 2026102610000000101, 1, '#', '', NULL,
+       1, 0, 'F', '0', '0', 'studentMsg:studentPwdReset:query', '#',
+       103, 1, NOW(), '按学号查询学生信息'
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000102);
+
+-- 3. 按钮:重置密码
+INSERT INTO `sys_menu` (
+  `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`,
+  `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+  `create_dept`, `create_by`, `create_time`, `remark`
+)
+SELECT 2026102610000000103, '确认重置', 2026102610000000101, 2, '#', '', NULL,
+       1, 0, 'F', '0', '0', 'studentMsg:studentPwdReset:reset', '#',
+       103, 1, NOW(), '确认重置学生密码'
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000103);
+
+-- 4. 移动端首页入口权限(挂在同一菜单页下,供 hasPermi 控制入口可见)
+INSERT INTO `sys_menu` (
+  `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`,
+  `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`,
+  `create_dept`, `create_by`, `create_time`, `remark`
+)
+SELECT 2026102610000000104, '重置学生密码(移动端)', 2026102610000000101, 3, '', '', NULL,
+       1, 0, 'F', '0', '0', 'student:pwdreset:listapp', '#',
+       103, 1, NOW(), '移动端首页入口权限'
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000104);
+
+-- 5. 给已有「学生信息」菜单权限的角色补授权(幂等)
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT DISTINCT rm.role_id, m.menu_id
+FROM `sys_role_menu` rm
+CROSS JOIN (
+  SELECT 2026102610000000101 AS menu_id UNION ALL
+  SELECT 2026102610000000102 UNION ALL
+  SELECT 2026102610000000103 UNION ALL
+  SELECT 2026102610000000104
+) m
+WHERE rm.menu_id = 1948289114628902914
+  AND NOT EXISTS (
+    SELECT 1 FROM `sys_role_menu` x
+    WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id
+  );
+
+-- 核对
+SELECT menu_id, menu_name, parent_id, path, component, perms, menu_type
+FROM sys_menu
+WHERE menu_id BETWEEN 2026102610000000101 AND 2026102610000000104
+ORDER BY menu_id;

+ 1 - 0
docs/开发文档/API/API文档总览.md

@@ -8,6 +8,7 @@
 
 ## API模块列表
 - [学生信息管理API](./学生信息管理API.md)
+- [学生密码重置API](./学生密码重置API.md) - **新增**:按学号查询并重置学生登录密码为固定默认值(2026-10-26)
 - [学生信息收集批次管理API](./学生信息收集批次管理API.md)
 - [学生信息收集批次API](./学生信息收集批次API.md) - **重要更新**:修改列表排序方式为按创建时间倒序(2025-01-03)
 - [班级信息管理API](./班级信息管理API.md)

+ 141 - 0
docs/开发文档/API/学生密码重置API.md

@@ -0,0 +1,141 @@
+# 学生密码重置 API 接口文档
+
+## 模块概述
+
+**模块名称**:Student Password Reset / 学生密码重置  
+**基础路径**:`/studentMsg/studentMsg`  
+**版本**:v1.0  
+**负责人**:学工模块  
+**更新时间**:2026-10-26
+
+### 模块功能
+
+学工老师按学号查询学生基本信息后,一键将对应学生登录账号密码重置为系统固定默认值。  
+**不复用** `system:user:resetPwd`,避免把系统用户管理权下放给学工老师。
+
+**核心约束**:
+- 仅允许重置 `sys_user.account_type = '2'`(学生账号)
+- 要求 `user_id = xg_student_msg.id`,且 `user_name` 与学号一致(若有用户名)
+- 密码由服务端写死常量,前端不可传入自定义密码
+- 有菜单/按钮权限即可操作任意学生,不做班主任数据范围限制
+
+**固定默认密码**(服务端常量,勿在客户端硬编码用于改密请求):`Hzdyjsxs123456!@#`
+
+### 权限说明
+
+| 权限标识 | 说明 |
+| -------- | ---- |
+| `studentMsg:studentPwdReset:list` | Web 菜单页可见 |
+| `studentMsg:studentPwdReset:query` | 按学号查询 |
+| `studentMsg:studentPwdReset:reset` | 确认重置密码 |
+| `student:pwdreset:listapp` | 移动端首页入口可见 |
+
+菜单脚本:`backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql`
+
+---
+
+## 接口概览
+
+| 接口名称 | HTTP 方法 | 路径 | 权限要求 | 功能描述 |
+| -------- | --------- | ---- | -------- | -------- |
+| 按学号查询学生 | GET | `/studentMsg/studentMsg/queryByStudentNo/{studentNo}` | `studentMsg:studentPwdReset:query` | 返回姓名、学号、班级、班主任 |
+| 重置学生密码 | PUT | `/studentMsg/studentMsg/resetStudentPassword/{studentNo}` | `studentMsg:studentPwdReset:reset` | 重置为固定默认密码 |
+
+---
+
+## 详细接口说明
+
+### 1. 按学号查询学生(重置密码页)
+
+- **接口路径**:`GET /studentMsg/studentMsg/queryByStudentNo/{studentNo}`
+- **权限标识**:`studentMsg:studentPwdReset:query`
+- **功能描述**:按学号查询学生档案,用于重置前确认身份;班主任姓名优先取学生档案,为空时回退班级表
+
+#### 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+| ------ | ---- | ---- | ---- |
+| studentNo | string | 是 | 学号 |
+
+#### 成功响应
+
+```json
+{
+  "code": 200,
+  "msg": "操作成功",
+  "data": {
+    "id": 1234567890123456789,
+    "studentNo": "202513063048",
+    "name": "张三",
+    "className": "计应2401",
+    "classTeacherName": "李老师"
+  }
+}
+```
+
+#### 错误场景
+
+| 场景 | 提示 |
+| ---- | ---- |
+| 学号为空 | 学号不能为空 |
+| 学号不存在 | 未找到学号对应的学生信息 |
+| 无权限 | 403 |
+
+---
+
+### 2. 重置学生密码
+
+- **接口路径**:`PUT /studentMsg/studentMsg/resetStudentPassword/{studentNo}`
+- **权限标识**:`studentMsg:studentPwdReset:reset`
+- **功能描述**:将学号对应学生登录密码重置为固定默认值;写操作日志「重置学生密码」
+- **防重复提交**:`@RepeatSubmit`
+
+#### 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+| ------ | ---- | ---- | ---- |
+| studentNo | string | 是 | 学号 |
+
+#### 请求体
+
+无。密码由服务端常量生成 BCrypt 后写入,不允许客户端传密。
+
+#### 成功响应
+
+```json
+{
+  "code": 200,
+  "msg": "操作成功"
+}
+```
+
+#### 错误场景
+
+| 场景 | 提示 |
+| ---- | ---- |
+| 学号不存在 | 未找到学号对应的学生信息 |
+| 无登录账号 | 该学生没有登录账号,无法重置密码 |
+| 非学生账号 | 该账号不是学生账号,禁止重置 |
+| 学号与登录名不一致 | 学号与登录账号不一致,请联系管理员 |
+| 无权限 | 403 |
+
+---
+
+## 前端与移动端
+
+| 端 | 路径 / 入口 |
+| -- | ----------- |
+| Web | `web/src/views/xg/xsjcxx/studentMsg/studentPwdReset/index.vue` |
+| Web API | `web/src/api/xg/xsjcxx/studentMsg/studentMsg/index.ts` → `queryStudentForPwdReset` / `resetStudentPassword` |
+| 移动端页面 | `mobile/src/pages/xg/xsjcxx/studentPwdReset/index.vue` |
+| 移动端 API | `mobile/src/api/studentPwdReset.js` |
+| 移动端首页 | 学工管理 →「重置学生密码」(`student:pwdreset:listapp`) |
+
+---
+
+## 验收要点
+
+1. 有权限账号:学号查得到 → 重置成功 → 该学号可用默认密码登录
+2. 无权限账号:接口 403
+3. 不存在学号 / 无用户 / 非学生账号:友好错误,不改密
+4. Web、移动端流程一致:输入学号 → 查询 → 确认 → 重置

+ 26 - 0
mobile/src/api/studentPwdReset.js

@@ -0,0 +1,26 @@
+/**
+ * 重置学生密码 API
+ */
+import { request } from '@/common/request.js';
+
+/**
+ * 按学号查询学生基本信息(重置密码页)
+ * @param {string} studentNo 学号
+ */
+export const queryStudentForPwdReset = (studentNo) => {
+    return request({
+        url: `/studentMsg/studentMsg/queryByStudentNo/${encodeURIComponent(studentNo)}`,
+        method: 'GET'
+    });
+};
+
+/**
+ * 按学号重置学生登录密码为固定默认值
+ * @param {string} studentNo 学号
+ */
+export const resetStudentPassword = (studentNo) => {
+    return request({
+        url: `/studentMsg/studentMsg/resetStudentPassword/${encodeURIComponent(studentNo)}`,
+        method: 'PUT'
+    });
+};

+ 8 - 0
mobile/src/pages.json

@@ -65,6 +65,14 @@
 				"navigationBarTextStyle": "white"
 			}
 		},
+		{
+			"path": "pages/xg/xsjcxx/studentPwdReset/index",
+			"style": {
+				"navigationBarTitleText": "重置学生密码",
+				"navigationBarBackgroundColor": "#0051d9",
+				"navigationBarTextStyle": "white"
+			}
+		},
 		{
 			"path": "pages/xg/xsjcxx/xsws/index",
 			"style": {

+ 17 - 0
mobile/src/pages/index/index.vue

@@ -86,6 +86,15 @@
                             />
                         </template>
                     </ServiceItem>
+                    <ServiceItem
+                        v-if="hasPermi(['student:pwdreset:listapp'])"
+                        title="重置学生密码"
+                        @click="gotoStudentPwdReset"
+                    >
+                        <template #icon>
+                            <u-icon name="lock-open" size="24" color="#1890ff" />
+                        </template>
+                    </ServiceItem>
                     <!-- 卡片结束 -->
                     <!-- <ServiceItem
                         v-if="hasPermi(['student:dorm:list'])"
@@ -776,6 +785,7 @@ export default {
         hasStudentServices() {
             return (
                 this.hasPermi(['student:info:listapp']) ||
+                this.hasPermi(['student:pwdreset:listapp']) ||
                 this.hasPermi(['student:dorm:list']) ||
                 this.hasPermi(['student:leave:listapp']) ||
                 this.hasPermi(['student:psychology:list']) ||
@@ -1203,6 +1213,13 @@ export default {
             });
         },
 
+        // 跳转到重置学生密码页面
+        gotoStudentPwdReset() {
+            uni.navigateTo({
+                url: '/pages/xg/xsjcxx/studentPwdReset/index',
+            });
+        },
+
         // 跳转到假期留宿申请(学生)
         gotoHolidayStayApply() {
             uni.navigateTo({

+ 203 - 0
mobile/src/pages/xg/xsjcxx/studentPwdReset/index.vue

@@ -0,0 +1,203 @@
+<template>
+    <view class="pwd-reset-page">
+        <view class="search-card">
+            <view class="form-row">
+                <text class="label">学号</text>
+                <u-input
+                    v-model="studentNo"
+                    placeholder="请输入学生学号"
+                    clearable
+                    maxlength="32"
+                    border="surround"
+                    :custom-style="{ background: '#fff' }"
+                    @confirm="handleQuery"
+                />
+            </view>
+            <u-button
+                type="primary"
+                text="查询"
+                :loading="queryLoading"
+                :custom-style="{ marginTop: '24rpx' }"
+                @click="handleQuery"
+            />
+        </view>
+
+        <view v-if="student" class="info-card">
+            <view class="card-title">学生信息</view>
+            <view class="info-item">
+                <text class="info-label">姓名</text>
+                <text class="info-value">{{ student.name || '-' }}</text>
+            </view>
+            <view class="info-item">
+                <text class="info-label">学号</text>
+                <text class="info-value">{{ student.studentNo || '-' }}</text>
+            </view>
+            <view class="info-item">
+                <text class="info-label">班级</text>
+                <text class="info-value">{{ student.className || '-' }}</text>
+            </view>
+            <view class="info-item">
+                <text class="info-label">班主任</text>
+                <text class="info-value">{{ student.classTeacherName || '-' }}</text>
+            </view>
+            <u-button
+                type="error"
+                text="重置密码"
+                :loading="resetLoading"
+                :custom-style="{ marginTop: '32rpx' }"
+                @click="handleReset"
+            />
+        </view>
+
+        <view v-else-if="queried && !queryLoading" class="empty-tip">
+            <u-empty mode="search" text="请输入学号后查询" />
+        </view>
+    </view>
+</template>
+
+<script>
+import {
+    queryStudentForPwdReset,
+    resetStudentPassword
+} from '@/api/studentPwdReset.js';
+
+export default {
+    data() {
+        return {
+            studentNo: '',
+            student: null,
+            queried: false,
+            queryLoading: false,
+            resetLoading: false
+        };
+    },
+    methods: {
+        async handleQuery() {
+            const no = (this.studentNo || '').trim();
+            if (!no) {
+                uni.showToast({ title: '请输入学号', icon: 'none' });
+                return;
+            }
+            this.queryLoading = true;
+            this.queried = true;
+            this.student = null;
+            try {
+                const res = await queryStudentForPwdReset(no);
+                const payload =
+                    res && res.data && typeof res.data === 'object' && !Array.isArray(res.data)
+                        ? res.data
+                        : res;
+                if (!payload || (!payload.studentNo && !payload.name && payload.id == null)) {
+                    uni.showToast({
+                        title: (res && res.msg) || '未查询到学生信息',
+                        icon: 'none'
+                    });
+                    this.student = null;
+                    return;
+                }
+                this.student = payload;
+                this.studentNo = no;
+            } catch (e) {
+                this.student = null;
+            } finally {
+                this.queryLoading = false;
+            }
+        },
+        handleReset() {
+            if (!this.student || !this.student.studentNo) {
+                uni.showToast({ title: '请先查询学生', icon: 'none' });
+                return;
+            }
+            const name = this.student.name || '';
+            const no = this.student.studentNo;
+            uni.showModal({
+                title: '重置密码确认',
+                content: `确认将学号「${no}」(${name})的登录密码重置为系统默认密码?`,
+                confirmText: '确定',
+                cancelText: '取消',
+                success: async (modalRes) => {
+                    if (!modalRes.confirm) {
+                        return;
+                    }
+                    this.resetLoading = true;
+                    try {
+                        await resetStudentPassword(no);
+                        uni.showToast({ title: '密码已重置为默认密码', icon: 'success' });
+                    } catch (e) {
+                        // 错误提示由 request 拦截器处理
+                    } finally {
+                        this.resetLoading = false;
+                    }
+                }
+            });
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.pwd-reset-page {
+    min-height: 100vh;
+    background: #f5f6f8;
+    padding: 24rpx;
+    box-sizing: border-box;
+}
+
+.search-card,
+.info-card {
+    background: #fff;
+    border-radius: 16rpx;
+    padding: 28rpx;
+    margin-bottom: 24rpx;
+}
+
+.form-row {
+    display: flex;
+    align-items: center;
+    gap: 16rpx;
+}
+
+.form-row .label {
+    width: 80rpx;
+    flex-shrink: 0;
+    font-size: 28rpx;
+    color: #303133;
+}
+
+.card-title {
+    font-size: 30rpx;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 20rpx;
+}
+
+.info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    padding: 18rpx 0;
+    border-bottom: 1px solid #f0f0f0;
+}
+
+.info-item:last-of-type {
+    border-bottom: none;
+}
+
+.info-label {
+    font-size: 28rpx;
+    color: #909399;
+    flex-shrink: 0;
+    margin-right: 24rpx;
+}
+
+.info-value {
+    font-size: 28rpx;
+    color: #303133;
+    text-align: right;
+    word-break: break-all;
+}
+
+.empty-tip {
+    padding-top: 80rpx;
+}
+</style>

+ 2 - 0
mobile/src/utils/router.js

@@ -7,6 +7,7 @@
 const SERVICE_ROUTES = {
     // 学工管理
     'student:info': '/pages/xg/xsjcxx/xsjcxx/index',
+    'student:pwdreset': '/pages/xg/xsjcxx/studentPwdReset/index',
     'student:dorm': '/pages/student/dorm/index',
     'student:leave': '/pages/xg/xsjcxx/xsqj/leave/index',
     'student:psychology': '/pages/student/psychology/index',
@@ -48,6 +49,7 @@ const SERVICE_ROUTES = {
 // 服务名称映射
 const SERVICE_NAMES = {
     学生信息: 'student:info',
+    重置学生密码: 'student:pwdreset',
     宿舍管理: 'student:dorm',
     学生请假: 'student:leave',
     心理健康: 'student:psychology',

+ 28 - 0
web/src/api/xg/xsjcxx/studentMsg/studentMsg/index.ts

@@ -117,3 +117,31 @@ export const getCollegeLeaders = (studentId: string | number): AxiosPromise<{ da
     method: 'get'
   });
 };
+
+export interface StudentPwdResetVO {
+  id?: string | number;
+  studentNo?: string;
+  name?: string;
+  className?: string;
+  classTeacherName?: string;
+}
+
+/**
+ * 按学号查询学生(重置密码页)
+ */
+export const queryStudentForPwdReset = (studentNo: string): AxiosPromise<StudentPwdResetVO> => {
+  return request({
+    url: `/studentMsg/studentMsg/queryByStudentNo/${encodeURIComponent(studentNo)}`,
+    method: 'get'
+  });
+};
+
+/**
+ * 按学号重置学生密码为固定默认值
+ */
+export const resetStudentPassword = (studentNo: string) => {
+  return request({
+    url: `/studentMsg/studentMsg/resetStudentPassword/${encodeURIComponent(studentNo)}`,
+    method: 'put'
+  });
+};

+ 130 - 0
web/src/views/xg/xsjcxx/studentMsg/studentPwdReset/index.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="hover" class="mb-3">
+      <el-form :inline="true" @submit.prevent>
+        <el-form-item label="学号">
+          <el-input
+            v-model="studentNo"
+            placeholder="请输入学生学号"
+            clearable
+            maxlength="32"
+            style="width: 260px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            v-hasPermi="['studentMsg:studentPwdReset:query']"
+            type="primary"
+            icon="Search"
+            :loading="queryLoading"
+            @click="handleQuery"
+          >
+            查询
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card v-if="student" shadow="never" v-loading="queryLoading">
+      <template #header>
+        <span>学生信息</span>
+      </template>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="姓名">{{ student.name || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="学号">{{ student.studentNo || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="班级">{{ student.className || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="班主任">{{ student.classTeacherName || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <div class="mt-4">
+        <el-button
+          v-hasPermi="['studentMsg:studentPwdReset:reset']"
+          type="danger"
+          icon="Key"
+          :loading="resetLoading"
+          @click="handleReset"
+        >
+          重置密码
+        </el-button>
+      </div>
+    </el-card>
+
+    <el-empty v-else-if="queried && !queryLoading" description="请输入学号后查询" />
+  </div>
+</template>
+
+<script setup lang="ts" name="StudentPwdReset">
+import { ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+  queryStudentForPwdReset,
+  resetStudentPassword,
+  type StudentPwdResetVO
+} from '@/api/xg/xsjcxx/studentMsg/studentMsg';
+
+const studentNo = ref('');
+const student = ref<StudentPwdResetVO | null>(null);
+const queried = ref(false);
+const queryLoading = ref(false);
+const resetLoading = ref(false);
+
+const handleQuery = async () => {
+  const no = studentNo.value?.trim();
+  if (!no) {
+    ElMessage.warning('请输入学号');
+    return;
+  }
+  queryLoading.value = true;
+  queried.value = true;
+  student.value = null;
+  try {
+    const res: any = await queryStudentForPwdReset(no);
+    // 拦截器返回 { code, msg, data },需取 data;兼容偶发直接返回业务对象
+    const payload =
+      res?.data && typeof res.data === 'object' && !Array.isArray(res.data) ? res.data : res;
+    if (!payload?.studentNo && !payload?.name && payload?.id == null) {
+      ElMessage.warning(res?.msg || '未查询到学生信息,请确认后端已部署最新接口');
+      student.value = null;
+      return;
+    }
+    student.value = payload as StudentPwdResetVO;
+    studentNo.value = no;
+  } catch (e) {
+    student.value = null;
+  } finally {
+    queryLoading.value = false;
+  }
+};
+
+const handleReset = async () => {
+  if (!student.value?.studentNo) {
+    ElMessage.warning('请先查询学生');
+    return;
+  }
+  try {
+    await ElMessageBox.confirm(
+      `确认将学号「${student.value.studentNo}」(${student.value.name || ''})的登录密码重置为系统默认密码?`,
+      '重置密码确认',
+      { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
+    );
+  } catch {
+    return;
+  }
+  resetLoading.value = true;
+  try {
+    await resetStudentPassword(student.value.studentNo);
+    ElMessage.success('密码已重置为默认密码');
+  } finally {
+    resetLoading.value = false;
+  }
+};
+</script>
+
+<style scoped>
+.mt-4 {
+  margin-top: 16px;
+}
+.mb-3 {
+  margin-bottom: 12px;
+}
+</style>