news 2026/10/12 8:33:06

SpringBoot+Vue企业人事管理系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue企业人事管理系统设计与实现

基于SpringBoot+Vue的企业人事管理系统+LW参考示例

在企业级开发这个圈子里,SpringBoot+Vue基本已经是前后端分离项目的标准答案了。这套企业人事管理系统,是我在带过好几批学生、也自己从头到尾重构过几遍之后,沉淀出来的一套最稳妥的落地方案。它要解决的无非是这些事:员工档案怎么维护、部门层级怎么设计、考勤和薪资的数据流怎么打通、登录之后的权限怎么控制,以及前后端分离在联调和部署阶段那些让人头疼的细节。如果你是正在做毕设,或者刚接了一个中小型管理系统的活儿,这篇文章可以直接照着落地,比你去论坛翻几十篇零散教程要省太多时间。

1. 整体设计与技术选型:为什么是SpringBoot+Vue

1.1 人事管理系统的核心需求拆解

很多人一上手就急着建项目、敲代码,结果做到一半发现功能之间互相打架。人事管理系统听着简单,落到需求上其实牵扯的东西不少。

我先说核心功能域。第一个是员工档案管理,包括员工基本信息、联系方式、入职日期、岗位、所属部门、在职状态,以及附带的增删改查和条件筛选。第二个是部门管理,企业里的部门是树形结构,比如总公司下面有技术部、市场部,技术部下面又有前端组、后端组。第三个是考勤管理,记录员工每天上下班时间,一个月下来统计出勤天数和迟到早退次数。第四个是薪资管理,根据基础工资、岗位工资、绩效和考勤结果算出实发工资。

这四块是骨架,再加上用户登录、角色权限、操作日志这些公共能力,就是一个完整闭环。最容易翻车的地方反而不是功能多复杂,而是数据约束没想清楚。比如员工离职不能直接删除记录,要改成离职状态;删除部门之前必须先确认部门下面没有员工;考勤数据一天只能绑定一条记录。这些问题如果没在设计阶段定好,后面补逻辑就是在一堆烂代码上打补丁。

1.2 前后端分离架构的优势与代价

选SpringBoot+Vue,本质上就是在走前后端分离路线。它的核心逻辑是:后端只负责提供JSON接口,不再关心页面长什么样;前端只负责页面交互,用Axios去拿数据。开发的时候两端可以并行推进,后端把接口定义好,前端就能用Mock数据先把页面写起来,不用互相干等。

但分离不是免费的午餐,代价主要在跨域和联调这两块。开发环境里,前端跑在Vite或Vue CLI的默认端口,后端跑在SpringBoot的8080,浏览器同源策略会把请求拦下来。解决办法我一般首选前端代理:在vue.config.js里改devServer.proxy,把/api开头的请求转发到后端地址。这个方案的好处是跨域配置只在开发环境生效,生产环境把前后端拆件部署在同一个Nginx域下,根本不需要额外处理跨域。

联调的代价更隐蔽。原来后端模板渲染时代,数据直接灌进页面,对不对一眼就能看到。现在前后端各自维护一层,接口字段名、日期格式、参数名对不上,就能消耗一整天。所以我强烈建议在前端请求封装层就把统一返回格式定死,比如{code, message, data},后端所有接口都遵守这个壳,联调起码少一半问题。

1.3 依赖选型与版本避坑

选型这块,我踩过的坑很多,直接给你一套不会出问题的组合。

后端我用SpringBoot 2.7.x,搭配MyBatis-Plus 3.5.x。为什么不选最新版Spring Boot 3.x?因为3.x是基于Jakarta命名空间,很多老教程的代码没法直接跑,而且对JDK版本有硬性要求。如果是毕设或者中小型系统,2.7.x稳定、教程多、遇到问题容易找到答案。持久层我用MyBatis-Plus,它自带分页插件,还有BaseMapper的通用CRUD,能把最简单的增删改查代码省到极致。但注意,复杂的多表查询我不用LambdaQueryWrapper硬拼,而是直接在XML里写SQL,可读性和维护性都好很多。

数据库用MySQL 8.0,连接驱动选mysql-connector-java 8.0.33。这里有个老坑:如果用5.x时代的com.mysql.jdbc.Driver,在MySQL 8下会直接启动失败,正确类名是com.mysql.cj.jdbc.Driver,而且URL里最好带上serverTimezone=Asia/Shanghai,不然日期字段会乱。

前端版本,如果你之前没用过Vite,求稳就选Vue 2.7 + Vue CLI 5 + Element UI,这套组合在中小型项目里非常成熟,路由、状态、组件库全齐。想练新技术就Vue 3 + Vite + Element Plus,逻辑大同小异,只是Composition API写法上的差异。我见过太多人死磕最新版本,结果花了两天解决脚手架兼容问题,代码一行没写,真的没必要。

2. 后端核心设计与实现

2.1 数据库表设计与关联关系

数据库设计是整个系统的地基,表关系搞不清楚,后面写接口全是泪。我一般分这几张核心表:sys_user(用户表)、department(部门表)、employee(员工表)、attendance(考勤表)、salary(薪资表)。

表之间的关系大概是这样的:sys_user和employee通过工号关联,一个员工对应一个登录账号;employee表里有department_id指向部门表,这就是员工和部门的归属关系;attendance通过employee_id关联员工;salary也通过employee_id关联员工。全部用逻辑外键,不要真在数据库里建外键约束,为什么?因为公司做数据迁移、批量导入的时候,外键约束会让操作顺序变得非常僵硬,很多实际项目都放弃物理外键,靠应用层保证数据一致性。

每个表的字段设计也有讲究。employee表不能只存姓名和手机号,还得有身份证号、性别、生日、学历、入职日期、在职状态这些基础字段。state字段建议用int类型,1在职、2离职、3试用期,比String好维护。attendance表要有date、check_in_time、check_out_time、status,注意date要加个唯一索引,组合是(employee_id, date)唯一,这样才能保证一个人一天只能有一条考勤记录。salary表建议保留base_salary、post_salary、bonus、deduction、actual_salary、month,每个月生成一条快照,而不是实时去算,这样年终统计的时候数据才是稳定的。

表名核心作用关键字段关联关系
sys_user登录账号username, password, role_id与employee通过work_no关联
department部门架构parent_id, name, leader_id自关联实现树形结构
employee员工档案name, phone, department_id, state关联department
attendance考勤记录employee_id, date, check_in, check_out关联employee
salary月度薪资employee_id, month, base_salary, actual_salary关联employee

2.2 SpringBoot三层架构与接口分层

后端代码我严格按Controller、Service、Mapper三层来分。Controller层只做一件事:接收请求参数、调Service、把结果包成统一返回体。Service层写业务逻辑,比如考勤打卡的时候要判断该员工当天是否已经打过卡,薪资计算要先统计考勤天数。Mapper层干数据访问的活,复杂SQL写在XML里。

我放一个员工查询的分页接口示例,这是整个系统里最有代表性的一个接口:

@RestController @RequestMapping("/api/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String name, @RequestParam(required = false) Integer departmentId) { Page<EmployeeVO> result = employeeService.pageEmployees(page, size, name, departmentId); return Result.ok(result); } }

Service里对应的是分页和条件查询逻辑:

public Page<EmployeeVO> pageEmployees(int page, int size, String name, Integer departmentId) { Page<Employee> p = new Page<>(page, size); LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(name), Employee::getName, name) .eq(departmentId != null, Employee::getDepartmentId, departmentId) .orderByDesc(Employee::getCreateTime); Page<Employee> employeePage = employeeMapper.selectPage(p, wrapper); // 把Entity转换成VO,补充分组名称等冗余字段 return convertToVO(employeePage); }

为什么要用VO(View Object)而不是直接把Entity返回给前端?因为数据库表结构不能完全暴露给页面。比如employee表里如果有身份证号这种敏感字段,直接返回Entity就把不该给的数据给出去了。我习惯建一个EmployeeVO,只包含前端需要的字段,然后在Service层做转换。这个习惯在真实项目里简直是保命级别的。

2.3 登录鉴权与权限拦截

人事管理系统肯定要有登录功能,而且不同角色看到的东西不一样。管理员能看所有模块,普通员工只能看自己的信息和考勤。鉴权方案我用JWT,整体思路是:用户登录成功后,后端生成一个Token返回给前端,前端存在localStorage里,之后每次请求都在Header里带上这个Token。后端写一个拦截器,拿到Token就解析出用户ID和角色,放行;没拿到或者Token过期,直接返回401。

JWT工具类的核心就两个方法,生成和解析:

public class JwtUtil { private static final String SECRET = "your-secret-key"; public static String generateToken(Integer userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

拦截器这块有个细节特别容易漏:放行登录接口本身。不然你还没登录,请求登录接口就被拦截了,直接死循环。路径用/api/auth/login和/api/auth/register白名单,剩下的都走拦截验证。密码存储绝不能明文,用BCrypt加密后再入库,Spring Security里自带BCryptPasswordEncoder,只引这一个类就够了,不用把整个Spring Security拉进来。

权限控制我建议做成简单模式:用户表里加个role字段,1是管理员,2是普通员工,后端拦截器解析出role之后,在接口方法上用注解或者直接判断。比如删除员工这个操作只有admin能做。别一上来就搞RBAC那一套完整的表设计,中小型系统用角色字段判断完全够用,而且代码好理解太多。

2.4 动态SQL与分页查询:MyBatis分页插件玩法

MyBatis-Plus的分页插件是必配的,配置方式很简单,写一个分页拦截器的配置类:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

配完之后,selectPage就是分页查询了,MyBatis-Plus自动帮你拼LIMIT语句。但这里注意一个坑:分页插件只能拦截单表查询,如果你在SQL里写了多表JOIN,用自定义XML的selectPage带上Page参数才行。比如员工列表要关联部门表查出部门名称,就不能用LambdaQueryWrapper,得自己在XML里写:

<select id="selectEmployeePage" resultType="com.example.vo.EmployeeVO"> SELECT e.*, d.name AS department_name FROM employee e LEFT JOIN department d ON e.department_id = d.id <where> <if test="name != null and name != ''"> AND e.name LIKE CONCAT('%', #{name}, '%') </if> <if test="departmentId != null"> AND e.department_id = #{departmentId} </if> </where> </select>

动态SQL的 标签是MyBatis的灵魂。它的语法很直白,上面这段代码里,name和departmentId传了就拼条件,不传就跳过。用这种写法,一个接口就能覆盖全部筛选场景,不用把page、pageAll、pageByName写好几个方法。这种"一个方法处理一类查询"的思路,在真实开发里太重要了。

3. 前端核心实现与页面落地

3.1 Vue项目初始化和基础配置

前端我从零开始搭。用Vue CLI 5创建项目,命令是vue create hr-front,然后在交互式选择里勾上Router和Vuex。装完基础依赖,再加Element UI和Axios:

npm install element-ui axios

main.js里做全局注册:

import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import App from './App.vue' import router from './router' import store from './store' Vue.use(ElementUI) Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')

目录结构这块,我习惯按模块分目录,而不是全堆在views里。我的项目结构大概是这样的:views下面按功能模块分子目录,比如employee、department、attendance、salary;components目录放可复用的通用组件,比如一个上传头像组件、一个导出Excel按钮;api目录下按模块拆JS文件,每个文件里封装对应接口的Axios请求;router目录只留一个index.js,所有路由集中在这里管理。

这种目录的划分方式跟后端Service的分类思路一样,就是"按业务功能聚合"。以后要加一个培训管理模块,前端复制一份api/employee.js改成api/training.js,视图目录加一个training文件夹,路由加几行,完事。项目结构一旦清晰,开发效率完全是两码事。

3.2 路由设计与页面权限控制

路由这块,我把它分成两大部分:静态路由和动态路由。静态路由就是登录页、404页这种不需要权限的;动态路由是根据用户角色去过滤出来的菜单和页面。

我用一个简单方案来模拟动态路由的效果。先在router/index.js里把需要权限的页面全部定义好,每个路由meta里加上roles字段:

{ path: '/employee', name: 'Employee', component: () => import('../views/employee/index.vue'), meta: { title: '员工管理', roles: ['admin'] } }

然后在路由全局前置守卫里,每次跳转前检查当前用户的角色有没有在该路由的roles里面:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') if (to.path === '/login') { next() } else if (!token) { next('/login') } else if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403') } else { next() } })

这个方案的优点是容易理解,不需要后端动态返回路由表。真正的动态路由要后端根据角色吐路由配置,前端用router.addRoutes动态添加,适合大型后台系统。人事管理系统这个规模,用meta标记+前端判断就绰绰有余。

路由懒加载这个点值得提一下。组件用() => import(...)这种写法,Webpack/Vite就会把每个页面单独打包成一个chunk,而不是全部塞进一个bundle.js里。效果就是首屏加载时间能少一半,尤其当系统页面多了以后,这个优化非常明显。

3.3 Axios请求封装与接口对接

Axios不能每个页面各写各的,必须封装成一个统一请求工具。我在api/request.js里做了三层处理:BaseURL配置、请求拦截器、响应拦截器。

import axios from 'axios' import { Message } from 'element-ui' import router from '../router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动带token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) // 响应拦截器:统一处理错误码 service.interceptors.response.use( response => { const res = response.data if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return res }, error => { Message.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } ) export default service

有了这层封装,后面写接口API就是纯粹的配置工作了。比如api/employee.js:

import request from '../request' export function getEmployeePage(params) { return request({ url: '/employee/page', method: 'get', params }) } export function deleteEmployee(id) { return request({ url: `/employee/delete/${id}`, method: 'post' }) }

响应拦截器里把code===401做统一跳转处理,这是很多项目忽视的关键点。Token过期是必然会发生的事,如果不统一处理,每个页面都写一遍"如果接口返回401就跳登录页"的代码,代码冗余不说,很容易漏掉某个页面,导致用户操作半天才发现session过期。

3.4 核心功能页面实战:员工管理+考勤统计落地

员工管理页面的实现思路是表格+搜索表单+对话框弹窗,这是中后台页面最常见的组合模式。

页面上半部分是搜索区,有姓名输入框、部门下拉框、在职状态下拉框,下面一个查询按钮和一个重置按钮。下半部分是表格区,表格列对应员工所有展示字段,操作列放编辑和删除按钮。新增和编辑共用一个Dialog弹窗,里面是el-form表单,字段校验规则用Element UI自带的那套验证规则就行。

考勤统计页面稍微复杂一点。因为考勤数据不是手动录的,而是每天打卡进来。我做了两个视图:一个是考勤记录表,展示每天每个人的打卡情况;另一个是月度汇总表,按员工+月份做group by,统计出勤天数、迟到次数、早退次数。月度汇总的数据可以由后端接口实时统计返回,不需要单独建表。

这里有个小技巧:前端表格里展示日期和数字格式,最好统一放在一个工具文件里用过滤器处理,比如日期格式化成yyyy-MM-dd,薪资数字千分位显示。不要在每个页面里复制粘贴同样的格式化代码,后期改格式你会想抽自己。

4. 联调、打包与上线部署

4.1 开发环境与联调经典问题

联调阶段是我遇到问题最多的时候,但问题类型其实很集中,翻来覆去就那几个。

第一个是前端拿到的时间字段是一串数字。这是因为后端默认序列化方式是Java时间戳,前端拿到的是毫秒数。解决办法有两个:一是后端全局配置Jackson格式化:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

二是在实体字段上加@JsonFormat注解。我建议全局配置优先,因为字段多的时候挨个加注解会烦死人。

第二个是Long类型ID精度丢失。如果你的主键用雪花算法生成,前端JS的Number类型存不下那么长的整数,最后几位会被0替代,导致删除或编辑时传错ID。解决办法是后端把Long序列化成String,或者干脆前端把ID字段改成el-input时禁用鼠标滚轮数字精确处理。实际项目里我在Jackson配置里统一把Long转成String,这个问题一劳永逸。

第三个是接口返回结构不统一。有的接口返回的是对象,有的是数组,有的错误信息塞在data里,这会逼着前端写一堆判断分支。我定的规矩是后端所有接口统一返回Result对象,data里面具体是什么和前端协商清楚。正是这种约定,才让联调效率高起来。

4.2 前后端部署方案:本地打包到服务器上线

部署我一般走两条路,各位根据自己的服务器条件选。

后端打包比较简单,在pom.xml里配好打包插件,执行mvn clean package -DskipTests,生成jar包,然后上传到服务器,用nohup启动:

nohup java -jar hr-server.jar --server.port=8081 > hr.log 2>&1 &

前端打包执行npm run build,生成dist目录,里面是静态文件,把这个目录上传到服务器的Nginx静态目录。Nginx里同时配置静态文件服务和/api反向代理:

server { listen 80; server_name hr.example.com; location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /var/www/hr/dist; index index.html; try_files $uri $uri/ /index.html; } }

try_files那行一定要写,因为Vue是单页应用,路由切换刷新页面时,如果Nginx找不到对应的物理文件,就会返回404,加了try_files就会自动兜底到index.html。这个坑,不亲身体会一遍很难注意到。

4.3 参考论文(LW)撰写时的要点

这套系统如果用于毕设,那LW(论文)部分其实占的分量很重。很多同学系统做出来了,论文不会写,最后反而栽在论文上。我给个通用提纲:摘要部分是整个系统的浓缩,要交代背景、说清楚系统用什么技术、实现哪些功能、达到什么效果。目录一般从绪论开始,讲研究背景和意义,然后是国内外研究现状。技术介绍章节把SpringBoot、Vue、MyBatis-Plus的原理写一遍。需求分析章节把功能需求和非功能需求分开描述,配用例图。系统设计章节把架构图、功能模块图、数据库ER图放进去,表结构截图是少不了的。系统实现章节按功能模块一个模块一个模块写,配核心代码和截图。最后是系统测试,写测试用例和结果。

论文里最忌讳的是堆流水账,比如"该系统采用SpringBoot框架"这样一句话介绍完。正确的写法是解释为什么选、选它解决了什么问题。比如"系统采用前后端分离架构,前端通过Axios调用后端RESTful接口,这解决了传统单体应用前后端耦合度高、无法并行开发的问题",这种描述才是有技术含量的。

5. 踩坑记录与常见问题速查

5.1 高频报错与排查思路

我把这两年带学生遇到的高频报错整理成了一张速查表,每个错误基本都有一个最常见的诱因。

报错现象常见原因解决思路
前端请求404后端接口路径和前端不一致先看后端Controller的@RequestMapping,再看前端api里的url,严格对齐
前端请求500后端空指针或SQL异常去看后端控制台完整堆栈,不要只看浏览器Network里的错误提示
登录接口CORS错误跨域配置没生效开发环境用Vue的proxy代理,生产环境用Nginx反代
数据库中文乱码连接URL缺少characterEncodingURL加characterEncoding=utf8&serverTimezone=Asia/Shanghai
分页查不出数据Page信息没传给Mapper自定义SQL方法第一个参数必须写Page
Vue页面白屏组件导入路径写错F12看Console具体报错路径,注意大小写
文件上传失败上传目录权限不够Linux下给文件夹chmod 755或777

排查思路上,我特别建议养成先看日志再猜原因的习惯。后端启动后把SpringBoot的日志级别调成DEBUG,SQL语句和执行参数都能打出来。前端调接口打不开的时候,打开浏览器开发者工具Network面板,看到底是404、500还是CORS错误,对症下药。很多人一报错就盯着代码看,却忘了看日志和请求状态,这是效率最低的做法。

5.2 几个值得养成的开发习惯

最后分享几个我从这个项目里提炼出来的习惯,可能比功能代码本身更有价值。

第一个是接口文档先于代码。前后端约定好URL、请求参数、返回结构再动手,我用的是ApiPost这类工具,直接在线维护接口文档,比动嘴说半天管用。第二个是Git提交要勤,不要改完一大坨才提交。每完成一个功能点commit一次,备注写清楚是做什么的。真改崩了能回滚,这个保障比什么都值钱。第三个是日志要打得有水平,不要只打System.out.println,统一用Slf4j写logger.info。系统出问题的时候,日志是你排查问题的唯一线索,打印出关键入参、出参和异常堆栈,能帮你省下大把排查时间。

第四个习惯是关于学习的方法的。遇到不懂的知识点,不要只看文章和视频,一定要去翻官方文档。SpringBoot和Vue的官方文档写得都很清楚,而且版本更新后文章经常过时,文档却是活的。我在排查问题的时候,翻官方文档的概率比搜博客高得多。

这个项目的整个流程从头到尾走一遍,你会发现真正收获不是那几张表和几个接口,而是把一套系统从零到一落地的完整认知:需求怎么拆、架构怎么选、前后端怎么协作、问题怎么排查、文档怎么沉淀。这套方法论,是做一个继续深造的项目还是接私活做外包,都能直接用上。我个人在实际带项目过程中最大的感受是,很多同学不是学不会技术,而是缺少一个完整项目的全局视角。这个系统刚好提供了一个很合适的切入点,哪怕你只把它做一遍,再去接触更复杂的业务系统,心里也会踏实很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 8:31:22

计算机基础PDF高效学习法:从标注到知识卡片

简介&#xff1a;这份文档是《代码随想录知识星球精华》第五版中的计算机基础篇&#xff0c;以计算机网络面试高频考点为核心&#xff0c;专为后端、前端开发者及计算机专业学生备战技术面试编写。文件为单个PDF文档&#xff0c;整体压缩包约三十七点八三兆字节&#xff0c;内容…

作者头像 李华
网站建设 2026/10/12 8:30:25

数据指标体系从0到1:OSM、UJM与指标字典落地全攻略

做数据这行&#xff0c;绕不开一个场景&#xff1a;业务方拿着两份数据来找你&#xff0c;电商那边说GMV同比增长30%&#xff0c;运营这边说增长只有15%&#xff0c;两边都觉得自己没错&#xff0c;查了半天发现统计口径不一样&#xff0c;一个算的是支付成功订单&#xff0c;一…

作者头像 李华
网站建设 2026/10/12 8:23:37

哈希洪荒攻击:当HashMap变成性能炸弹

1. 这不是密码学课&#xff0c;是给你讲个能听懂的“快递站爆仓”故事你有没有经历过这种场景&#xff1a;小区门口突然堆满快递&#xff0c;不是因为双十一大促&#xff0c;而是有人用同一张收货单——反复写了1000遍“3栋2单元501”&#xff0c;然后一股脑全塞进快递柜。柜子…

作者头像 李华
网站建设 2026/10/12 8:21:21

生成式图像创作流水线:从随机抽卡到可控成稿的完整指南

在生成艺术这个圈子里泡久了&#xff0c;你会发现一个特别普遍的现象&#xff1a;同样一段提示词&#xff0c;有人扔进工具里点一下就能出图&#xff0c;有人却能得到一组几乎可以直接商用的成品序列&#xff0c;两者差距不在于模型本身&#xff0c;而在于你是否把生成这件事当…

作者头像 李华
网站建设 2026/10/12 8:20:40

Playwright用户视觉定位详解:告别脆弱的CSS选择器

不想再被 CSS 选择器半夜叫醒&#xff1f;来试试 Playwright 的“用户视觉定位”在自动化测试圈子里混久了&#xff0c;你会发现一个特别有意思的现象&#xff1a;用 CSS 选择器定位元素&#xff0c;就像在后厨靠菜谱找配菜&#xff0c;厨师不换菜谱一切安好&#xff0c;可前端…

作者头像 李华
网站建设 2026/10/12 8:20:39

PCA图像压缩原理:30个数字如何重建256×256灰度图

1. 为什么一张256256的灰度图&#xff0c;用PCA压缩后只存30个数字就能重建出“差不多”的样子&#xff1f;你有没有试过打开一张手机拍的风景照&#xff0c;放大到像素级——满屏是密密麻麻、毫无规律的明暗点&#xff1f;这些点不是随机生成的&#xff0c;而是被某种“隐含结…

作者头像 李华