.cursorrules 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387
  1. # 杭州第一技师学院智慧校园管理系统 - AI 代码生成规范
  2. ## 🎯 项目概述
  3. 本项目是基于 RuoYi-Vue-Plus 框架开发的智慧校园管理系统,主要用于学校的学工管理、宿舍管理、班级管理等业务场景。
  4. - **项目名称**: 杭州第一技师学院智慧校园管理系统
  5. - **基础框架**: RuoYi-Vue-Plus 5.4.0
  6. - **前端**: Vue 3 + TypeScript + Element Plus + Vite
  7. - **后端**: Spring Boot 3.4.6 + MyBatis-Plus + Sa-Token
  8. - **主工程代码路径**: 管理端前端在 **`crm/web/`**,Java 后端在 **`crm/backend/`**,主工程 Python 在 **`crm/py-backend/`**;AI 生成路径说明、文档与脚本中的 `web/`、`backend/` 均指以上目录,勿假定仓库根下仍有同名顶层文件夹。
  9. ---
  10. ## ⚠️ AI代码生成核心规则
  11. ### 文档生成规范
  12. - **禁止主动生成MD文档**:在代码生成过程中,除非用户明确要求,否则不要生成任何Markdown文档(.md文件)
  13. - **简洁优先原则**:如果必须生成说明文档,内容要极度简洁,只包含关键信息
  14. - **聚焦代码实现**:将精力集中在代码生成和功能实现上,而非文档编写
  15. - **例外情况**:仅在以下情况生成文档
  16. - 用户明确要求生成文档
  17. - 项目README或API文档等必要文件缺失
  18. - 复杂功能需要关键说明(限制在50行以内)
  19. ---
  20. ## 📦 前端技术栈总结
  21. ### 核心框架与版本
  22. - **Vue**: 3.5.13 (使用 Composition API)
  23. - **TypeScript**: 5.8.3
  24. - **Vite**: 6.3.2
  25. - **Element Plus**: 2.9.8
  26. - **Pinia**: 3.0.2 (状态管理)
  27. - **Vue Router**: 4.5.0 (路由管理)
  28. - **Axios**: 1.8.4 (HTTP请求)
  29. ### 关键工具库
  30. - **vxe-table**: 4.13.7 (高级表格组件)
  31. - **UnoCSS**: 66.0.0 (原子化CSS框架)
  32. - **echarts**: 5.6.0 (数据可视化)
  33. - **@vueuse/core**: 13.1.0 (Vue组合式工具集)
  34. - **vue-i18n**: 11.1.3 (国际化)
  35. - **screenfull**: 6.0.2 (全屏)
  36. - **nprogress**: 0.2.0 (进度条)
  37. ### 项目结构
  38. ```
  39. crm/web/
  40. ├── src/
  41. │ ├── api/ # API接口定义
  42. │ ├── assets/ # 静态资源
  43. │ ├── components/ # 公共组件
  44. │ ├── directive/ # 自定义指令
  45. │ ├── hooks/ # 组合式函数
  46. │ ├── lang/ # 国际化语言包
  47. │ ├── layout/ # 布局组件
  48. │ ├── plugins/ # 插件配置
  49. │ ├── router/ # 路由配置
  50. │ ├── store/ # Pinia状态管理
  51. │ ├── utils/ # 工具函数
  52. │ ├── views/ # 页面视图
  53. │ └── main.ts # 入口文件
  54. ├── vite/ # Vite插件配置
  55. ├── index.html
  56. ├── vite.config.ts
  57. └── package.json
  58. ```
  59. ---
  60. ## 📦 后端技术栈总结
  61. ### 核心框架与版本
  62. - **Spring Boot**: 3.4.6
  63. - **JDK**: 17+
  64. - **MyBatis**: 3.5.16
  65. - **MyBatis-Plus**: 3.5.12
  66. - **Sa-Token**: 1.42.0 (权限认证)
  67. - **Hutool**: 5.8.35 (工具库)
  68. ### 关键技术组件
  69. - **Redisson**: 3.45.1 (Redis客户端,分布式锁)
  70. - **Lock4j**: 2.2.7 (分布式锁)
  71. - **dynamic-datasource**: 4.3.1 (动态多数据源)
  72. - **p6spy**: 3.9.1 (SQL性能分析)
  73. - **FastExcel**: 1.2.0 (Excel导入导出)
  74. - **SpringDoc**: 2.8.8 (API文档生成)
  75. - **Warm-Flow**: 1.7.3 (国产工作流引擎)
  76. - **SnailJob**: 1.5.0 (分布式任务调度)
  77. - **AWS S3**: 2.28.22 (对象存储)
  78. - **SMS4J**: 3.3.4 (短信发送)
  79. - **ip2region**: 2.7.0 (IP地址定位)
  80. - **MapStruct-Plus**: 1.5.0 (对象转换)
  81. ### 项目结构
  82. ```
  83. crm/backend/
  84. ├── ruoyi-admin/ # 启动模块
  85. ├── ruoyi-common/ # 通用模块
  86. │ ├── ruoyi-common-core/ # 核心工具
  87. │ ├── ruoyi-common-mybatis/ # 数据库配置
  88. │ ├── ruoyi-common-redis/ # Redis配置
  89. │ ├── ruoyi-common-satoken/ # 权限配置
  90. │ ├── ruoyi-common-web/ # Web配置
  91. │ ├── ruoyi-common-excel/ # Excel工具
  92. │ └── ... # 其他通用模块
  93. ├── ruoyi-modules/ # 业务模块
  94. │ ├── ruoyi-system/ # 系统管理
  95. │ ├── ruoyi-job/ # 定时任务
  96. │ ├── ruoyi-workflow/ # 工作流
  97. │ └── ruoyi-xg/ # 学工管理 ⭐
  98. └── ruoyi-extend/ # 扩展模块
  99. ├── ruoyi-monitor-admin/ # 监控中心
  100. └── ruoyi-snailjob-server/# 任务调度服务
  101. ```
  102. ---
  103. ## 🎓 学工模块(ruoyi-xg)架构规范
  104. ### 后端模块结构
  105. ```
  106. ruoyi-xg/
  107. └── src/main/java/org/dromara/xg/
  108. ├── controller/ # 控制器层 - RESTful API
  109. ├── service/ # 服务层 - 业务逻辑
  110. │ └── impl/ # 服务实现
  111. ├── mapper/ # 数据访问层 - MyBatis接口
  112. ├── domain/ # 数据模型
  113. │ ├── bo/ # Business Object - 业务对象(接收参数)
  114. │ └── vo/ # View Object - 视图对象(返回数据)
  115. ├── convert/ # 对象转换器 - MapStruct
  116. ├── config/ # 配置类
  117. ├── utils/ # 工具类
  118. └── listener/ # 事件监听器
  119. ```
  120. ### 前端模块结构
  121. ```
  122. crm/web/src/
  123. ├── api/xg/ # 学工API接口定义
  124. │ ├── student/ # 学生管理API
  125. │ ├── xsjcxx/ # 学生基础信息API
  126. │ ├── xgss/ # 学工宿舍API
  127. │ ├── studentStatistics.ts # 学生统计API
  128. │ └── README.md
  129. └── views/xg/ # 学工页面视图
  130. ├── xsjcxx/ # 学生基础信息模块
  131. ├── xgss/ # 学工宿舍模块
  132. ├── studentStatistics/ # 学生统计模块
  133. └── README.md
  134. ```
  135. ---
  136. ## 🚀 后端开发规范(学工模块)
  137. ### 1. Controller 层规范
  138. ```java
  139. package org.dromara.xg.controller;
  140. import cn.dev33.satoken.annotation.SaCheckPermission;
  141. import jakarta.servlet.http.HttpServletResponse;
  142. import jakarta.validation.constraints.*;
  143. import lombok.RequiredArgsConstructor;
  144. import org.dromara.common.core.domain.R;
  145. import org.dromara.common.core.validate.AddGroup;
  146. import org.dromara.common.core.validate.EditGroup;
  147. import org.dromara.common.excel.utils.ExcelUtil;
  148. import org.dromara.common.idempotent.annotation.RepeatSubmit;
  149. import org.dromara.common.log.annotation.Log;
  150. import org.dromara.common.log.enums.BusinessType;
  151. import org.dromara.common.mybatis.core.page.*;
  152. import org.dromara.common.web.core.BaseController;
  153. import org.springframework.validation.annotation.Validated;
  154. import org.springframework.web.bind.annotation.*;
  155. /**
  156. * XXX管理控制器
  157. *
  158. * @author Your Name
  159. * @date 2025-01-XX
  160. */
  161. @Validated
  162. @RequiredArgsConstructor
  163. @RestController
  164. @RequestMapping("/xg/模块名")
  165. public class XgXxxController extends BaseController {
  166. private final IXgXxxService xxxService;
  167. /**
  168. * 查询列表(分页)
  169. */
  170. @SaCheckPermission("xg:xxx:list")
  171. @GetMapping("/list")
  172. public TableDataInfo<XgXxxVo> list(XgXxxBo bo, PageQuery pageQuery) {
  173. return xxxService.queryPageList(bo, pageQuery);
  174. }
  175. /**
  176. * 获取详细信息
  177. */
  178. @SaCheckPermission("xg:xxx:query")
  179. @GetMapping("/{id}")
  180. public R<XgXxxVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
  181. return R.ok(xxxService.queryById(id));
  182. }
  183. /**
  184. * 新增
  185. */
  186. @SaCheckPermission("xg:xxx:add")
  187. @Log(title = "XXX管理", businessType = BusinessType.INSERT)
  188. @RepeatSubmit()
  189. @PostMapping()
  190. public R<Void> add(@Validated(AddGroup.class) @RequestBody XgXxxBo bo) {
  191. return toAjax(xxxService.insertByBo(bo));
  192. }
  193. /**
  194. * 修改
  195. */
  196. @SaCheckPermission("xg:xxx:edit")
  197. @Log(title = "XXX管理", businessType = BusinessType.UPDATE)
  198. @RepeatSubmit()
  199. @PutMapping()
  200. public R<Void> edit(@Validated(EditGroup.class) @RequestBody XgXxxBo bo) {
  201. return toAjax(xxxService.updateByBo(bo));
  202. }
  203. /**
  204. * 删除
  205. */
  206. @SaCheckPermission("xg:xxx:remove")
  207. @Log(title = "XXX管理", businessType = BusinessType.DELETE)
  208. @DeleteMapping("/{ids}")
  209. public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
  210. return toAjax(xxxService.deleteWithValidByIds(List.of(ids), true));
  211. }
  212. /**
  213. * 导出
  214. */
  215. @SaCheckPermission("xg:xxx:export")
  216. @Log(title = "XXX管理", businessType = BusinessType.EXPORT)
  217. @PostMapping("/export")
  218. public void export(XgXxxBo bo, HttpServletResponse response) {
  219. List<XgXxxVo> list = xxxService.queryList(bo);
  220. ExcelUtil.exportExcel(list, "XXX数据", XgXxxVo.class, response);
  221. }
  222. }
  223. ```
  224. **关键点:**
  225. - 继承 `BaseController` 提供基础工具方法
  226. - 使用 `@RequiredArgsConstructor` 实现构造器注入
  227. - 使用 `@SaCheckPermission` 进行权限控制,格式:`xg:模块:操作`
  228. - 使用 `@Log` 记录操作日志
  229. - 使用 `@RepeatSubmit` 防止重复提交
  230. - 使用 `@Validated` 进行参数校验,分组校验使用 AddGroup/EditGroup
  231. - 分页查询返回 `TableDataInfo<T>`,其他接口返回 `R<T>`
  232. ### 2. Service 层规范
  233. ```java
  234. package org.dromara.xg.service;
  235. import org.dromara.common.mybatis.core.page.*;
  236. import org.dromara.xg.domain.bo.XgXxxBo;
  237. import org.dromara.xg.domain.vo.XgXxxVo;
  238. import java.util.Collection;
  239. import java.util.List;
  240. /**
  241. * XXX管理Service接口
  242. *
  243. * @author Your Name
  244. * @date 2025-01-XX
  245. */
  246. public interface IXgXxxService {
  247. /**
  248. * 查询分页列表
  249. */
  250. TableDataInfo<XgXxxVo> queryPageList(XgXxxBo bo, PageQuery pageQuery);
  251. /**
  252. * 查询列表
  253. */
  254. List<XgXxxVo> queryList(XgXxxBo bo);
  255. /**
  256. * 根据主键查询
  257. */
  258. XgXxxVo queryById(Long id);
  259. /**
  260. * 新增
  261. */
  262. Boolean insertByBo(XgXxxBo bo);
  263. /**
  264. * 修改
  265. */
  266. Boolean updateByBo(XgXxxBo bo);
  267. /**
  268. * 校验并批量删除
  269. */
  270. Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
  271. }
  272. ```
  273. **实现类规范:**
  274. ```java
  275. package org.dromara.xg.service.impl;
  276. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  277. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  278. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  279. import lombok.RequiredArgsConstructor;
  280. import org.dromara.common.core.utils.MapstructUtils;
  281. import org.dromara.common.core.utils.StringUtils;
  282. import org.dromara.common.mybatis.core.page.*;
  283. import org.dromara.xg.domain.XgXxx;
  284. import org.dromara.xg.domain.bo.XgXxxBo;
  285. import org.dromara.xg.domain.vo.XgXxxVo;
  286. import org.dromara.xg.mapper.XgXxxMapper;
  287. import org.dromara.xg.service.IXgXxxService;
  288. import org.springframework.stereotype.Service;
  289. import java.util.Collection;
  290. import java.util.List;
  291. /**
  292. * XXX管理Service实现
  293. *
  294. * @author Your Name
  295. * @date 2025-01-XX
  296. */
  297. @RequiredArgsConstructor
  298. @Service
  299. public class XgXxxServiceImpl implements IXgXxxService {
  300. private final XgXxxMapper baseMapper;
  301. /**
  302. * 构建查询条件
  303. */
  304. private LambdaQueryWrapper<XgXxx> buildQueryWrapper(XgXxxBo bo) {
  305. LambdaQueryWrapper<XgXxx> lqw = Wrappers.lambdaQueryWrapper();
  306. lqw.like(StringUtils.isNotBlank(bo.getName()), XgXxx::getName, bo.getName());
  307. lqw.eq(bo.getStatus() != null, XgXxx::getStatus, bo.getStatus());
  308. lqw.between(bo.getBeginTime() != null && bo.getEndTime() != null,
  309. XgXxx::getCreateTime, bo.getBeginTime(), bo.getEndTime());
  310. lqw.orderByDesc(XgXxx::getCreateTime);
  311. return lqw;
  312. }
  313. @Override
  314. public TableDataInfo<XgXxxVo> queryPageList(XgXxxBo bo, PageQuery pageQuery) {
  315. LambdaQueryWrapper<XgXxx> lqw = buildQueryWrapper(bo);
  316. Page<XgXxxVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
  317. return TableDataInfo.build(result);
  318. }
  319. @Override
  320. public List<XgXxxVo> queryList(XgXxxBo bo) {
  321. LambdaQueryWrapper<XgXxx> lqw = buildQueryWrapper(bo);
  322. return baseMapper.selectVoList(lqw);
  323. }
  324. @Override
  325. public XgXxxVo queryById(Long id) {
  326. return baseMapper.selectVoById(id);
  327. }
  328. @Override
  329. public Boolean insertByBo(XgXxxBo bo) {
  330. XgXxx entity = MapstructUtils.convert(bo, XgXxx.class);
  331. boolean flag = baseMapper.insert(entity) > 0;
  332. if (flag) {
  333. bo.setId(entity.getId());
  334. }
  335. return flag;
  336. }
  337. @Override
  338. public Boolean updateByBo(XgXxxBo bo) {
  339. XgXxx entity = MapstructUtils.convert(bo, XgXxx.class);
  340. return baseMapper.updateById(entity) > 0;
  341. }
  342. @Override
  343. public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
  344. if (isValid) {
  345. // 做删除前的业务校验
  346. }
  347. return baseMapper.deleteByIds(ids) > 0;
  348. }
  349. }
  350. ```
  351. **关键点:**
  352. - 使用 `@RequiredArgsConstructor` 注入 Mapper
  353. - 使用 MyBatis-Plus 的 LambdaQueryWrapper 构建查询条件
  354. - 使用 `MapstructUtils.convert` 进行对象转换
  355. - 使用 `selectVoPage` 和 `selectVoList` 直接查询VO对象
  356. ### 3. Domain 层规范
  357. **Entity(实体类):**
  358. ```java
  359. package org.dromara.xg.domain;
  360. import com.baomidou.mybatisplus.annotation.*;
  361. import lombok.Data;
  362. import lombok.EqualsAndHashCode;
  363. import org.dromara.common.tenant.core.TenantEntity;
  364. import java.io.Serial;
  365. import java.time.LocalDateTime;
  366. /**
  367. * XXX实体对象 xg_xxx
  368. *
  369. * @author Your Name
  370. * @date 2025-01-XX
  371. */
  372. @Data
  373. @EqualsAndHashCode(callSuper = true)
  374. @TableName("xg_xxx")
  375. public class XgXxx extends TenantEntity {
  376. @Serial
  377. private static final long serialVersionUID = 1L;
  378. /**
  379. * 主键ID
  380. */
  381. @TableId(value = "id", type = IdType.ASSIGN_ID)
  382. private Long id;
  383. /**
  384. * 名称
  385. */
  386. private String name;
  387. /**
  388. * 状态
  389. */
  390. private String status;
  391. /**
  392. * 备注
  393. */
  394. private String remark;
  395. /**
  396. * 逻辑删除标志(0存在 1删除)
  397. */
  398. @TableLogic
  399. private Integer delFlag;
  400. }
  401. ```
  402. **BO(业务对象 - 接收前端参数):**
  403. ```java
  404. package org.dromara.xg.domain.bo;
  405. import io.github.linpeilie.annotations.AutoMapper;
  406. import jakarta.validation.constraints.*;
  407. import lombok.Data;
  408. import lombok.EqualsAndHashCode;
  409. import org.dromara.common.core.validate.AddGroup;
  410. import org.dromara.common.core.validate.EditGroup;
  411. import org.dromara.common.mybatis.core.domain.BaseEntity;
  412. import org.dromara.xg.domain.XgXxx;
  413. /**
  414. * XXX业务对象 xg_xxx
  415. *
  416. * @author Your Name
  417. * @date 2025-01-XX
  418. */
  419. @Data
  420. @EqualsAndHashCode(callSuper = true)
  421. @AutoMapper(target = XgXxx.class, reverseConvertGenerate = false)
  422. public class XgXxxBo extends BaseEntity {
  423. /**
  424. * 主键ID
  425. */
  426. @NotNull(message = "主键不能为空", groups = { EditGroup.class })
  427. private Long id;
  428. /**
  429. * 名称
  430. */
  431. @NotBlank(message = "名称不能为空", groups = { AddGroup.class, EditGroup.class })
  432. private String name;
  433. /**
  434. * 状态
  435. */
  436. private String status;
  437. /**
  438. * 备注
  439. */
  440. private String remark;
  441. }
  442. ```
  443. **VO(视图对象 - 返回给前端):**
  444. ```java
  445. package org.dromara.xg.domain.vo;
  446. import com.alibaba.excel.annotation.ExcelProperty;
  447. import io.github.linpeilie.annotations.AutoMapper;
  448. import lombok.Data;
  449. import org.dromara.common.excel.annotation.ExcelDictFormat;
  450. import org.dromara.common.excel.convert.ExcelDictConvert;
  451. import org.dromara.xg.domain.XgXxx;
  452. import java.io.Serial;
  453. import java.io.Serializable;
  454. import java.time.LocalDateTime;
  455. /**
  456. * XXX视图对象 xg_xxx
  457. *
  458. * @author Your Name
  459. * @date 2025-01-XX
  460. */
  461. @Data
  462. @AutoMapper(target = XgXxx.class)
  463. public class XgXxxVo implements Serializable {
  464. @Serial
  465. private static final long serialVersionUID = 1L;
  466. /**
  467. * 主键ID
  468. */
  469. @ExcelProperty(value = "ID")
  470. private Long id;
  471. /**
  472. * 名称
  473. */
  474. @ExcelProperty(value = "名称")
  475. private String name;
  476. /**
  477. * 状态
  478. */
  479. @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
  480. @ExcelDictFormat(dictType = "sys_normal_disable")
  481. private String status;
  482. /**
  483. * 备注
  484. */
  485. @ExcelProperty(value = "备注")
  486. private String remark;
  487. /**
  488. * 创建时间
  489. */
  490. @ExcelProperty(value = "创建时间")
  491. private LocalDateTime createTime;
  492. }
  493. ```
  494. ### 4. Mapper 层规范
  495. ```java
  496. package org.dromara.xg.mapper;
  497. import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  498. import org.dromara.xg.domain.XgXxx;
  499. import org.dromara.xg.domain.vo.XgXxxVo;
  500. /**
  501. * XXX管理Mapper接口
  502. *
  503. * @author Your Name
  504. * @date 2025-01-XX
  505. */
  506. public interface XgXxxMapper extends BaseMapperPlus<XgXxx, XgXxxVo> {
  507. // 自定义SQL方法在这里定义
  508. }
  509. ```
  510. **对应的 XML(如需自定义SQL):**
  511. ```xml
  512. <?xml version="1.0" encoding="UTF-8" ?>
  513. <!DOCTYPE mapper
  514. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  515. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  516. <mapper namespace="org.dromara.xg.mapper.XgXxxMapper">
  517. <resultMap type="org.dromara.xg.domain.vo.XgXxxVo" id="XgXxxResult">
  518. <result property="id" column="id"/>
  519. <result property="name" column="name"/>
  520. <result property="status" column="status"/>
  521. <result property="remark" column="remark"/>
  522. <result property="createTime" column="create_time"/>
  523. </resultMap>
  524. </mapper>
  525. ```
  526. ---
  527. ## 🎨 前端开发规范(学工模块)
  528. ### 1. API 接口定义规范
  529. ```typescript
  530. // crm/web/src/api/xg/xxx.ts
  531. import request from '@/utils/request'
  532. /**
  533. * 查询参数接口
  534. */
  535. export interface XxxQuery {
  536. pageNum?: number
  537. pageSize?: number
  538. name?: string
  539. status?: string
  540. beginTime?: string
  541. endTime?: string
  542. }
  543. /**
  544. * 表单数据接口
  545. */
  546. export interface XxxForm {
  547. id?: number
  548. name: string
  549. status: string
  550. remark?: string
  551. }
  552. /**
  553. * 视图对象接口
  554. */
  555. export interface XxxVO {
  556. id: number
  557. name: string
  558. status: string
  559. remark?: string
  560. createTime: string
  561. updateTime: string
  562. }
  563. /**
  564. * 查询列表
  565. */
  566. export function listXxx(query: XxxQuery) {
  567. return request({
  568. url: '/xg/xxx/list',
  569. method: 'get',
  570. params: query
  571. })
  572. }
  573. /**
  574. * 查询详细
  575. */
  576. export function getXxx(id: number) {
  577. return request({
  578. url: `/xg/xxx/${id}`,
  579. method: 'get'
  580. })
  581. }
  582. /**
  583. * 新增
  584. */
  585. export function addXxx(data: XxxForm) {
  586. return request({
  587. url: '/xg/xxx',
  588. method: 'post',
  589. data: data
  590. })
  591. }
  592. /**
  593. * 修改
  594. */
  595. export function updateXxx(data: XxxForm) {
  596. return request({
  597. url: '/xg/xxx',
  598. method: 'put',
  599. data: data
  600. })
  601. }
  602. /**
  603. * 删除
  604. */
  605. export function delXxx(ids: number | number[]) {
  606. return request({
  607. url: `/xg/xxx/${ids}`,
  608. method: 'delete'
  609. })
  610. }
  611. /**
  612. * 导出
  613. */
  614. export function exportXxx(query: XxxQuery) {
  615. return request({
  616. url: '/xg/xxx/export',
  617. method: 'post',
  618. data: query,
  619. responseType: 'blob'
  620. })
  621. }
  622. ```
  623. **关键点:**
  624. - 接口定义使用 TypeScript interface
  625. - Query 参数用于列表查询(GET请求的params)
  626. - Form 用于新增/修改(POST/PUT请求的data)
  627. - VO 用于展示数据
  628. - 导出接口需要设置 `responseType: 'blob'`
  629. ### 2. 列表页面规范
  630. ```vue
  631. <!-- crm/web/src/views/xg/xxx/index.vue -->
  632. <template>
  633. <div class="app-container">
  634. <!-- 查询表单 -->
  635. <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
  636. <el-form-item label="名称" prop="name">
  637. <el-input
  638. v-model="queryParams.name"
  639. placeholder="请输入名称"
  640. clearable
  641. @keyup.enter="handleQuery"
  642. />
  643. </el-form-item>
  644. <el-form-item label="状态" prop="status">
  645. <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
  646. <el-option
  647. v-for="dict in sys_normal_disable"
  648. :key="dict.value"
  649. :label="dict.label"
  650. :value="dict.value"
  651. />
  652. </el-select>
  653. </el-form-item>
  654. <el-form-item>
  655. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  656. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  657. </el-form-item>
  658. </el-form>
  659. <!-- 工具栏 -->
  660. <el-row :gutter="10" class="mb8">
  661. <el-col :span="1.5">
  662. <el-button
  663. type="primary"
  664. plain
  665. icon="Plus"
  666. @click="handleAdd"
  667. v-hasPermi="['xg:xxx:add']"
  668. >新增</el-button>
  669. </el-col>
  670. <el-col :span="1.5">
  671. <el-button
  672. type="success"
  673. plain
  674. icon="Edit"
  675. :disabled="single"
  676. @click="handleUpdate()"
  677. v-hasPermi="['xg:xxx:edit']"
  678. >修改</el-button>
  679. </el-col>
  680. <el-col :span="1.5">
  681. <el-button
  682. type="danger"
  683. plain
  684. icon="Delete"
  685. :disabled="multiple"
  686. @click="handleDelete()"
  687. v-hasPermi="['xg:xxx:remove']"
  688. >删除</el-button>
  689. </el-col>
  690. <el-col :span="1.5">
  691. <el-button
  692. type="warning"
  693. plain
  694. icon="Download"
  695. @click="handleExport"
  696. v-hasPermi="['xg:xxx:export']"
  697. >导出</el-button>
  698. </el-col>
  699. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
  700. </el-row>
  701. <!-- 数据表格 -->
  702. <el-table
  703. v-loading="loading"
  704. :data="xxxList"
  705. @selection-change="handleSelectionChange"
  706. >
  707. <el-table-column type="selection" width="55" align="center" />
  708. <el-table-column label="ID" align="center" prop="id" />
  709. <el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" />
  710. <el-table-column label="状态" align="center" prop="status">
  711. <template #default="scope">
  712. <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
  713. </template>
  714. </el-table-column>
  715. <el-table-column label="创建时间" align="center" prop="createTime" width="180">
  716. <template #default="scope">
  717. <span>{{ parseTime(scope.row.createTime) }}</span>
  718. </template>
  719. </el-table-column>
  720. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  721. <template #default="scope">
  722. <el-tooltip content="修改" placement="top">
  723. <el-button
  724. link
  725. type="primary"
  726. icon="Edit"
  727. @click="handleUpdate(scope.row)"
  728. v-hasPermi="['xg:xxx:edit']"
  729. />
  730. </el-tooltip>
  731. <el-tooltip content="删除" placement="top">
  732. <el-button
  733. link
  734. type="danger"
  735. icon="Delete"
  736. @click="handleDelete(scope.row)"
  737. v-hasPermi="['xg:xxx:remove']"
  738. />
  739. </el-tooltip>
  740. </template>
  741. </el-table-column>
  742. </el-table>
  743. <!-- 分页 -->
  744. <pagination
  745. v-show="total > 0"
  746. :total="total"
  747. v-model:page="queryParams.pageNum"
  748. v-model:limit="queryParams.pageSize"
  749. @pagination="getList"
  750. />
  751. <!-- 添加或修改对话框 -->
  752. <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
  753. <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
  754. <el-form-item label="名称" prop="name">
  755. <el-input v-model="form.name" placeholder="请输入名称" />
  756. </el-form-item>
  757. <el-form-item label="状态" prop="status">
  758. <el-radio-group v-model="form.status">
  759. <el-radio
  760. v-for="dict in sys_normal_disable"
  761. :key="dict.value"
  762. :value="dict.value"
  763. >{{ dict.label }}</el-radio>
  764. </el-radio-group>
  765. </el-form-item>
  766. <el-form-item label="备注" prop="remark">
  767. <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
  768. </el-form-item>
  769. </el-form>
  770. <template #footer>
  771. <div class="dialog-footer">
  772. <el-button @click="cancel">取 消</el-button>
  773. <el-button type="primary" @click="submitForm">确 定</el-button>
  774. </div>
  775. </template>
  776. </el-dialog>
  777. </div>
  778. </template>
  779. <script setup lang="ts" name="Xxx">
  780. import { listXxx, getXxx, addXxx, updateXxx, delXxx, exportXxx } from '@/api/xg/xxx'
  781. import type { XxxQuery, XxxForm, XxxVO } from '@/api/xg/xxx'
  782. // 查询参数
  783. const queryParams = ref<XxxQuery>({
  784. pageNum: 1,
  785. pageSize: 10,
  786. name: undefined,
  787. status: undefined
  788. })
  789. // 表单数据
  790. const form = ref<XxxForm>({
  791. id: undefined,
  792. name: '',
  793. status: '0',
  794. remark: ''
  795. })
  796. // 表单校验规则
  797. const rules = ref({
  798. name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
  799. status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
  800. })
  801. // 数据列表
  802. const xxxList = ref<XxxVO[]>([])
  803. const loading = ref(true)
  804. const total = ref(0)
  805. const showSearch = ref(true)
  806. const single = ref(true)
  807. const multiple = ref(true)
  808. const ids = ref<number[]>([])
  809. // 字典数据
  810. const { sys_normal_disable } = useDict('sys_normal_disable')
  811. // 对话框
  812. const dialog = reactive<DialogOption>({
  813. visible: false,
  814. title: ''
  815. })
  816. const queryFormRef = ref<ElFormInstance>()
  817. const formRef = ref<ElFormInstance>()
  818. /** 查询列表 */
  819. const getList = async () => {
  820. loading.value = true
  821. try {
  822. const res = await listXxx(queryParams.value)
  823. xxxList.value = res.rows
  824. total.value = res.total
  825. } finally {
  826. loading.value = false
  827. }
  828. }
  829. /** 搜索按钮操作 */
  830. const handleQuery = () => {
  831. queryParams.value.pageNum = 1
  832. getList()
  833. }
  834. /** 重置按钮操作 */
  835. const resetQuery = () => {
  836. queryFormRef.value?.resetFields()
  837. handleQuery()
  838. }
  839. /** 多选框选中数据 */
  840. const handleSelectionChange = (selection: XxxVO[]) => {
  841. ids.value = selection.map(item => item.id)
  842. single.value = selection.length !== 1
  843. multiple.value = !selection.length
  844. }
  845. /** 新增按钮操作 */
  846. const handleAdd = () => {
  847. reset()
  848. dialog.visible = true
  849. dialog.title = '添加XXX'
  850. }
  851. /** 修改按钮操作 */
  852. const handleUpdate = async (row?: XxxVO) => {
  853. reset()
  854. const id = row?.id || ids.value[0]
  855. const res = await getXxx(id)
  856. Object.assign(form.value, res.data)
  857. dialog.visible = true
  858. dialog.title = '修改XXX'
  859. }
  860. /** 提交表单 */
  861. const submitForm = () => {
  862. formRef.value?.validate(async (valid) => {
  863. if (valid) {
  864. try {
  865. if (form.value.id) {
  866. await updateXxx(form.value)
  867. proxy?.$modal.msgSuccess('修改成功')
  868. } else {
  869. await addXxx(form.value)
  870. proxy?.$modal.msgSuccess('新增成功')
  871. }
  872. dialog.visible = false
  873. await getList()
  874. } catch (error) {
  875. console.error(error)
  876. }
  877. }
  878. })
  879. }
  880. /** 删除按钮操作 */
  881. const handleDelete = async (row?: XxxVO) => {
  882. const idList = row?.id ? [row.id] : ids.value
  883. await proxy?.$modal.confirm('是否确认删除选中的数据?')
  884. await delXxx(idList)
  885. proxy?.$modal.msgSuccess('删除成功')
  886. await getList()
  887. }
  888. /** 导出按钮操作 */
  889. const handleExport = () => {
  890. proxy?.$download(
  891. 'xg/xxx/export',
  892. { ...queryParams.value },
  893. `xxx_${new Date().getTime()}.xlsx`
  894. )
  895. }
  896. /** 取消按钮 */
  897. const cancel = () => {
  898. dialog.visible = false
  899. reset()
  900. }
  901. /** 表单重置 */
  902. const reset = () => {
  903. form.value = {
  904. id: undefined,
  905. name: '',
  906. status: '0',
  907. remark: ''
  908. }
  909. formRef.value?.resetFields()
  910. }
  911. onMounted(() => {
  912. getList()
  913. })
  914. </script>
  915. ```
  916. **关键点:**
  917. - 使用 `<script setup lang="ts">` 语法
  918. - 使用 `ref` 和 `reactive` 管理状态
  919. - 使用 `v-hasPermi` 指令控制按钮权限
  920. - 使用 `useDict` 获取字典数据
  921. - 使用 `$modal` 显示消息和确认框
  922. - 使用 `$download` 下载导出文件
  923. ### 3. 权限控制
  924. **权限编码规范:** `xg:模块:操作`
  925. 示例:
  926. - `xg:student:list` - 学生列表查询
  927. - `xg:student:query` - 学生详情查询
  928. - `xg:student:add` - 学生新增
  929. - `xg:student:edit` - 学生修改
  930. - `xg:student:remove` - 学生删除
  931. - `xg:student:export` - 学生导出
  932. - `xg:student:import` - 学生导入
  933. **使用方式:**
  934. ```vue
  935. <!-- 按钮权限控制 -->
  936. <el-button v-hasPermi="['xg:student:add']">新增</el-button>
  937. <!-- JS中判断权限 -->
  938. <script setup>
  939. import { hasPermi } from '@/utils/permission'
  940. const canEdit = hasPermi(['xg:student:edit'])
  941. </script>
  942. ```
  943. ---
  944. ## 📝 开发流程指南
  945. ### 1. 新建学工功能模块完整流程
  946. #### 后端开发步骤:
  947. 1. **创建数据库表**(表名格式:`xg_模块名`)
  948. 2. **创建 Entity 实体类**(`domain/XgXxx.java`)
  949. 3. **创建 VO 视图对象**(`domain/vo/XgXxxVo.java`)
  950. 4. **创建 BO 业务对象**(`domain/bo/XgXxxBo.java`)
  951. 5. **创建 Mapper 接口**(`mapper/XgXxxMapper.java`)
  952. 6. **创建 Mapper XML**(`resources/mapper/xg/XgXxxMapper.xml`,如需自定义SQL)
  953. 7. **创建 Service 接口**(`service/IXgXxxService.java`)
  954. 8. **创建 Service 实现**(`service/impl/XgXxxServiceImpl.java`)
  955. 9. **创建 Controller 控制器**(`controller/XgXxxController.java`)
  956. 10. **在菜单管理中配置菜单和权限**
  957. #### 前端开发步骤:
  958. 1. **创建 API 接口文件**(`api/xg/xxx.ts`)
  959. 2. **创建页面文件**(`views/xg/xxx/index.vue`)
  960. 3. **配置路由**(通过后端菜单管理配置,前端动态加载)
  961. 4. **测试功能**
  962. ### 2. 使用代码生成器(推荐)
  963. 框架提供了强大的代码生成器,可以一键生成所有代码:
  964. 1. 登录系统,进入 **系统工具 -> 代码生成**
  965. 2. 点击 **导入表**,选择要生成的数据库表
  966. 3. 点击 **编辑**,配置生成信息:
  967. - 基本信息:包名、模块名、作者等
  968. - 字段信息:字段类型、显示类型、是否必填等
  969. - 生成信息:生成模板、前端路径等
  970. 4. 点击 **生成代码**,下载代码包
  971. 5. 将生成的代码解压到对应目录
  972. 6. 重启后端服务,刷新前端页面
  973. ---
  974. ## 🎯 学工模块常见业务场景
  975. ### 1. 学生信息管理
  976. - **表命名**: `xg_student`, `xg_student_msg`
  977. - **权限前缀**: `xg:student`
  978. - **API路径**: `/xg/student`
  979. - **页面路径**: `views/xg/xsjcxx`(学生基础信息)
  980. ### 2. 宿舍管理
  981. - **表命名**: `xg_dorm_*`(校区、楼栋、房间、床位等)
  982. - **权限前缀**: `xg:dorm`
  983. - **API路径**: `/xg/dorm`
  984. - **页面路径**: `views/xg/xgss`(学工宿舍)
  985. ### 3. 班级管理
  986. - **表命名**: `xg_class_*`
  987. - **权限前缀**: `xg:class`
  988. - **API路径**: `/xg/class`
  989. - **页面路径**: `views/xg/class`
  990. ### 4. 统计分析
  991. - **表命名**: 可直接查询其他表统计
  992. - **权限前缀**: `xg:statistics`
  993. - **API路径**: `/xg/statistics` 或 `/xg/studentStatistics`
  994. - **页面路径**: `views/xg/studentStatistics`
  995. ---
  996. ## 🔧 常用工具类和方法
  997. ### 后端常用工具
  998. **字符串工具 - StringUtils**
  999. ```java
  1000. import org.dromara.common.core.utils.StringUtils;
  1001. StringUtils.isNotBlank(str) // 判断字符串不为空
  1002. StringUtils.isBlank(str) // 判断字符串为空
  1003. StringUtils.isEmpty(str) // 判断字符串是否为null或空串
  1004. ```
  1005. **对象转换 - MapstructUtils**
  1006. ```java
  1007. import org.dromara.common.core.utils.MapstructUtils;
  1008. // BO 转 Entity
  1009. XgStudent entity = MapstructUtils.convert(bo, XgStudent.class);
  1010. // Entity 转 VO
  1011. XgStudentVo vo = MapstructUtils.convert(entity, XgStudentVo.class);
  1012. // List 转换
  1013. List<XgStudentVo> voList = MapstructUtils.convert(entityList, XgStudentVo.class);
  1014. ```
  1015. **日期工具 - DateUtils**
  1016. ```java
  1017. import org.dromara.common.core.utils.DateUtils;
  1018. DateUtils.parseDate(str) // 解析日期字符串
  1019. DateUtils.parseDateToStr(date) // 日期转字符串
  1020. DateUtils.getNowDate() // 获取当前日期
  1021. ```
  1022. **Excel导出 - ExcelUtil**
  1023. ```java
  1024. import org.dromara.common.excel.utils.ExcelUtil;
  1025. ExcelUtil.exportExcel(list, "标题", XgXxxVo.class, response);
  1026. ```
  1027. ### 前端常用工具
  1028. **日期格式化**
  1029. ```typescript
  1030. import { parseTime } from '@/utils/ruoyi'
  1031. parseTime(new Date()) // 格式化日期时间
  1032. parseTime(date, '{y}-{m}-{d}') // 自定义格式
  1033. ```
  1034. **权限判断**
  1035. ```typescript
  1036. import { hasPermi, hasRole } from '@/utils/permission'
  1037. hasPermi(['xg:student:edit']) // 判断是否有权限
  1038. hasRole(['admin']) // 判断是否有角色
  1039. ```
  1040. **字典标签**
  1041. ```vue
  1042. <dict-tag :options="sys_normal_disable" :value="status" />
  1043. ```
  1044. **文件下载**
  1045. ```typescript
  1046. proxy?.$download('api/url', params, 'filename.xlsx')
  1047. ```
  1048. ---
  1049. ## 🚨 注意事项
  1050. ### 后端开发注意事项
  1051. 1. **权限注解必须添加**:所有 Controller 方法都必须添加 `@SaCheckPermission` 注解
  1052. 2. **操作日志记录**:重要操作(增删改)必须添加 `@Log` 注解
  1053. 3. **防重复提交**:新增/修改操作建议添加 `@RepeatSubmit` 注解
  1054. 4. **参数校验**:使用 `@Validated` 和校验注解(@NotNull、@NotBlank等)
  1055. 5. **事务管理**:涉及多表操作的 Service 方法要添加 `@Transactional` 注解
  1056. 6. **逻辑删除**:删除操作优先使用逻辑删除(delFlag字段)
  1057. 7. **租户隔离**:继承 `TenantEntity` 实现多租户数据隔离
  1058. 8. **SQL注入防护**:使用 MyBatis-Plus 的 Wrapper 构建查询条件,避免字符串拼接
  1059. ### 前端开发注意事项
  1060. 1. **权限控制**:所有操作按钮必须添加 `v-hasPermi` 指令
  1061. 2. **表单校验**:表单提交前必须进行前端校验
  1062. 3. **加载状态**:异步操作要显示 loading 状态
  1063. 4. **错误处理**:API 调用要用 try-catch 捕获异常
  1064. 5. **代码规范**:使用 TypeScript,定义好接口类型
  1065. 6. **组件复用**:相同功能的组件要抽取为公共组件
  1066. 7. **国际化**:涉及到文本的地方考虑国际化支持
  1067. 8. **响应式布局**:页面要考虑不同屏幕尺寸的适配
  1068. ---
  1069. ## 🎨 Web UI 强制规范(bx/bx-web,Fresh Mint 对齐)
  1070. > 本节用于 `bx/bx-web` 前端界面改造与新页面开发,视觉与交互以 `bx/docs/web-admin-ui-spec-fresh-mint.md` 为唯一权威;如与历史样式冲突,以本节和规范文档为准。
  1071. ### 1. 视觉 Token 与基础参数(强制)
  1072. - 主色与语义色:`--primary: #10b981`、`--primary-dark: #059669`、`--primary-bg: #ecfdf5`、`--text: #1e293b`、`--text-muted: #64748b`、`--border: #d9e8e2`、`--table-head: #f8fafc`
  1073. - 正文:`font-size: 14px`、`line-height: 1.5`
  1074. - 圆角:面板/大弹窗 `10px`,按钮/输入 `8px`,行内操作按钮 `6px`
  1075. - 卡片阴影:`0 1px 3px rgba(0,0,0,0.05)`
  1076. ### 2. 页面骨架与列表区(强制)
  1077. - 统一结构:`page-head -> panel -> filter-block -> list-toolbar -> list-body/list-wrap -> pagination(list-pager-bar)`
  1078. - 列表工具栏:仅第一个主动作使用主色实心按钮,其余使用中性描边
  1079. - 操作列必须 `fixed="right"`,并设置足够宽度,禁止按钮换行/截断
  1080. - 分页条必须与表格同容器,`margin-top: 0`,顶部单分割线,禁止脱节
  1081. ### 3. 筛选区规则(强制)
  1082. - 使用网格筛选:默认 4 列,<=1100px 2 列,<=560px 1 列
  1083. - 筛选 label:11px,次级色;控件高度统一 36px
  1084. - “更多筛选”展开区与操作行之间只用间距,不加横向分割线
  1085. - 查询/重置/展开更多按钮必须带 loading 状态(与请求态一致)
  1086. ### 4. 弹窗与详情查看(强制)
  1087. - 详情弹窗优先采用:`hero(标题+状态+摘要) + section 分块 + kv 网格`
  1088. - 详情信息区统一两列键值布局,自适应降为单列;禁止同页混用无规则单列/三列
  1089. - 审批流区域统一 `max-height + overflow-y:auto`
  1090. - 查看弹窗底部仅保留“关闭/返回”类动作,不混入提交语义按钮
  1091. - 长文本默认使用“最多 3 行 + 展开/收起”组件,避免拉长弹窗
  1092. ### 5. 表格型详情区块(强制)
  1093. - 诸如“冲抵记录/冲抵明细/流水”等区块必须复用统一组件(标题+表格+空态)
  1094. - 空数据必须展示统一空态,不允许直接隐藏整个区块导致信息断层
  1095. ### 6. 表单弹窗规则(强制)
  1096. - 新增/编辑弹窗默认两列表单(`label-position="top"`),长文本字段跨整行
  1097. - footer 按钮统一:取消(中性)/确定(主色),图标与文字间距 6px
  1098. - 长表单校验失败时需滚动定位到首个错误项(平滑滚动)
  1099. ### 7. 交互与可访问性(强制)
  1100. - 图片预览、下载、附件区块需统一行为与样式,不得各页自定义分叉
  1101. - 关键按钮与分页切换必须可键盘访问,焦点态清晰可见
  1102. - 颜色语义统一:success=已完成,warning=待处理,danger=异常,info=中性
  1103. ### 8. 改造执行与验收(强制)
  1104. - 改造顺序:Token -> 骨架 -> 组件(筛选/按钮/表格/弹窗)-> 页面微调
  1105. - 每次 UI 改造必须至少覆盖:字号、间距、边框、按钮层级、断点自适应
  1106. - 禁止“只改局部页面样式不抽共用组件”;重复结构优先组件化
  1107. ## 📱 BX Mobile UI 强制规范(bx/bx-mobile,Fresh Mint 对齐)
  1108. > 本节用于 `bx/bx-mobile`(Vant 4 + Vue 3)移动端页面开发与改造。视觉与交互以移动端规范文档 `bx/docs/mobile-ui-spec-fresh-mint.md` 为权威,如与历史页面冲突,以本节和移动端规范文档为准。
  1109. ### 1. 设计 Token 与主题映射(强制)
  1110. - 统一使用 `src/styles/index.css` 中 token,不允许页面内硬编码主色(`#00c98d`、`#10b981` 等)覆盖全局语义。
  1111. - 颜色语义固定:`success=通过/完成`、`warning=待处理`、`danger=异常/驳回`、`info=进行中`、`muted=草稿/中性`。
  1112. - Vant 主题色必须对齐 token:`van-button--primary`、`van-tabs__line`、`van-tabbar-item--active`、`van-nav-bar` 等保持统一薄荷主色链。
  1113. ### 2. 页面骨架与安全区(强制)
  1114. - 页面容器统一使用 `.page` 或同等结构,遵循 `padding: 12px 16px 80px` 的内容节奏。
  1115. - 底部操作区/底栏必须包含 `env(safe-area-inset-bottom)`,避免 iPhone 刘海屏/底部手势区遮挡。
  1116. - 全宽组件(`van-nav-bar`、`van-search`、`van-tabs`、`van-pull-refresh`)按全局样式贴边,不在业务页重复写负 margin。
  1117. ### 3. 卡片与列表规范(强制)
  1118. - 业务列表统一卡片化:白底、`12px` 圆角、`1px` 边框、轻阴影,信息密度优先“标题 + 状态 + 金额 + 时间”。
  1119. - 可点击卡片必须有按下反馈(缩放或底色变化),状态条/左侧强调线使用语义色,不得随页面自定义颜色体系。
  1120. - 卡片内操作按钮遵循“首要操作主色,其余中性/危险”规则;异步按钮必须有 loading 与禁用态。
  1121. ### 4. 表单与交互反馈(强制)
  1122. - 输入控件默认高度 42-46px,`label` 在上、输入在下;错误态必须有红色边框和明确文案。
  1123. - 登录/提交类按钮必须防重复点击(loading + disabled);失败反馈优先“字段错误态 + toast”组合。
  1124. - 密码框、树形选择、费用项联动等交互组件需支持“可逆操作”(清空、收起、回退)与状态回显。
  1125. ### 5. 首页与导航入口(强制)
  1126. - 首页使用“欢迎区 + 菜单卡片 + 待办区”三级层次,禁止菜单入口直接堆叠为无分组长列表。
  1127. - 菜单宫格支持消息红点、待办计数,红点只用于需要用户处理的业务入口。
  1128. - 顶部欢迎区可包含汇总数据(如本月金额、审批进度),强调信息不超过 2-3 个,避免视觉噪音。
  1129. ### 6. 弹层与底部 Sheet(强制)
  1130. - 底部选择器(如一级/二级费用项)统一使用 bottom sheet:遮罩 + 顶部标题栏 + 主体双栏 + 底部确认动作。
  1131. - 选择器必须支持关键词检索和已选路径回显;确认按钮在未完成选择时禁用。
  1132. - 弹层圆角、阴影、遮罩透明度与全局保持一致,禁止页面私有“另一个风格”的弹层。
  1133. ### 7. 移动端验收清单(强制)
  1134. - 至少覆盖:配色统一、卡片层级、按钮 loading、错误态、安全区、窄屏可读性。
  1135. - 真机验收至少覆盖 iOS/Android 各 1 台(或对应模拟器),重点检查底部操作栏、输入焦点和滚动穿透。
  1136. - 新增页面时优先复用已有公共样式/结构(`card`、`status-tag`、`apply-list-item`、`bottom-action-bar`),避免重复造样式。
  1137. ### 学工模块特殊注意
  1138. 1. **学生隐私保护**:身份证号、手机号等敏感信息需要脱敏处理
  1139. 2. **数据权限控制**:根据用户角色(辅导员、班主任等)限制数据访问范围
  1140. 3. **批量操作优化**:大量学生数据的批量导入/导出要考虑性能优化
  1141. 4. **工作流集成**:涉及审批流程的功能需要集成工作流引擎
  1142. 5. **数据统计性能**:统计分析功能要注意查询性能,必要时使用缓存
  1143. ---
  1144. ## 📚 参考资源
  1145. ### 官方文档
  1146. - **RuoYi-Vue-Plus 文档**: https://plus-doc.dromara.org
  1147. - **Vue 3 文档**: https://cn.vuejs.org
  1148. - **Element Plus 文档**: https://element-plus.org/zh-CN
  1149. - **MyBatis-Plus 文档**: https://baomidou.com
  1150. - **Sa-Token 文档**: https://sa-token.cc
  1151. ### 框架特色功能文档
  1152. - **多租户**: https://plus-doc.dromara.org/#/ruoyi-vue-plus/framework/tenant
  1153. - **数据权限**: https://plus-doc.dromara.org/#/ruoyi-vue-plus/framework/data_permission
  1154. - **数据脱敏**: https://plus-doc.dromara.org/#/ruoyi-vue-plus/framework/sensitive
  1155. - **工作流**: https://plus-doc.dromara.org/#/ruoyi-vue-plus/framework/workflow
  1156. - **代码生成器**: https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/generator
  1157. ---
  1158. ## 🎓 总结
  1159. 遵循本规范文件,AI可以:
  1160. 1. **理解项目整体架构**:前后端分离、模块化设计
  1161. 2. **快速生成标准代码**:Controller、Service、Mapper、Vue页面等
  1162. 3. **保持代码风格统一**:命名规范、注解使用、权限控制等
  1163. 4. **处理学工业务场景**:学生管理、宿舍管理、统计分析等
  1164. 5. **避免常见错误**:权限遗漏、参数校验缺失、SQL注入等
  1165. **开发原则**:
  1166. - ✅ 代码规范统一
  1167. - ✅ 安全性优先
  1168. - ✅ 性能优化考虑
  1169. - ✅ 用户体验良好
  1170. - ✅ 可维护性强
  1171. **生成代码时的核心要求**:
  1172. 1. 严格遵循本文档的命名规范和代码结构
  1173. 2. 必须包含完整的权限控制和参数校验
  1174. 3. 前后端接口定义要保持一致
  1175. 4. 注释要清晰完整
  1176. 5. 考虑学工业务的特殊性(隐私保护、数据权限等)