资讯详情 心理测试源码v1.0:一套可跑通的PHP+MySQL闭环答题系统
📅 2026/10/10 4:10:11
简介一份心理测试源码 v1.0 压缩包定位为可直接部署的静态网页资源适合用作情感/心理测试类网站栏目原型或前端学习素材。资源面向网站开发者、站长及心理学相关页面制作爱好者包含测试空间、情爱测试、心理测试、社交测试、成功测试、性格测试、性爱测试、个性测试、异类测试等栏目全部为纯 HTML 静态页面便于阅读与二次修改。压缩包共482个文件以447个 htm 页面为主配合33个 gif 图片、1个 js 脚本与1个 db 辅助文件整体大小约996KB轻量易部署。已有1515人下载学习适合快速搭建多栏目测试站点或作为静态页面排版、栏目导航设计的参考范本。通过源码可掌握一套完整栏目页结构、栏目间链接关系和轻量前端资源组织方式二次开发时只需替换图片与文案即可上线使用。1. 心理测试源码 v1.0 是什么一套能直接跑通的 Web 答题系统最近常有人问“心理测试源码 v1.0”这类包能不能用能不能当课设、内部问卷或者产品原型来用。这类包听起来朴素但只要把“出题—答题—计分—结果页”这条链路完整跑通它的价值就已经出来了。大多数人找这套源码真正要的不是大而全的平台而是一套能改、能演示、能交差的闭环工程。v1.0 这个版本号本身已经标定了边界功能闭环、结构清晰、细节略糙。它没有复杂的前后端分离也没有推荐算法和千人千面PHP MySQL 是这套源码里最常见的落法。对课程设计答辩、小组内部匿名测评、测试类产品原型验证来说原生 PHP 恰恰比引入框架更省心——少一层抽象就少一层排错成本。适合谁看这篇需要快速交付可运行系统的开发者想把现成题库搬进网页的管理者以及第一次接触“答题系统怎么做”的新手。接下来我从启动路径、数据流、管理端、避坑到交付细节把这套源码彻底讲清楚。2. 先把 v1.0 跑起来目录结构、环境要求与最快启动路径2.1 先认目录v1.0 为什么长成这样拿到这类 php 源码工程我习惯先看目录结构再动数据库。目录能直接反映工程有没有分层也能提前判断后续改造成本。一套典型心理测试源码 v1.0 的目录大概长这样psych-test-v1.0/ ├── config/ │ └── database.php # 数据库连接配置 ├── sql/ │ └── install.sql # 建库建表脚本首次安装执行 ├── api/ │ ├── score.php # 答题提交与计分入口 │ └── result.php # 结果查询接口可选 ├── admin/ │ ├── login.php # 管理端登录 │ ├── index.php # 管理端首页题目列表 │ └── question_edit.php # 新增/编辑题目 ├── quiz_take.php # 前台答题页读取题库并渲染表单 ├── result.php # 结果展示页 ├── index.php # 首页跳转/引导 └── README.md # 安装与说明这种分层方式的优点很直接config 只放配置sql 只放安装脚本api 统一处理后端逻辑admin 独立成管理端前台页面留在根目录。和 java 课程设计案例源码里常见的 Spring Boot Vue 拆两层不同这套 v1.0 更贴近“单机、可复制、能演示”的使用场景拿到手改完 config 就能跑不必先启动两个服务再配跨域。我一般会先把 README 里的数据库名、字符集、初始账号记下来再逐个文件扫一遍。重点看两个地方config/database.php里的连接参数以及sql/install.sql里的建表语句。前者决定你能不能连上数据库后者决定题库数据结构长什么样。2.2 本地跑通导入数据库、改配置、启动服务本地跑通这套源码典型步骤是三步建库导入、改连接配置、启动 PHP 内置服务。先建库mysql -uroot -p sql/install.sql如果你的 MySQL 还没创建对应库install.sql 里一般会有CREATE DATABASE语句或者你先手动建一个空库再导入。导入成功后用SHOW TABLES;检查一下是否生成了questions、results、answer_logs这几张核心表。接着改config/database.php把连接信息换成你本机的实际账号?php // config/database.php // 改成本地实际数据库账号密码留空表示 root 无密码仅限本地开发 return [ host 127.0.0.1, port 3306, dbname psych_test, username root, password , charset utf8mb4 ];这里的charset我建议固定成utf8mb4别为了省空间改回utf8mb3否则题目里出现 emoji 或生僻字时写入数据库会直接变成乱码后面排查起来很费劲。最后在项目根目录启动 PHP 内置开发服务器cd psych-test-v1.0 php -S 127.0.0.1:8080浏览器打开http://127.0.0.1:8080/quiz_take.php能看到题目列表并且可以正常提交说明 v1.0 已经跑通了。注意php -S是开发服务器只适合本地验证正式部署建议换 Apache 或 Nginx并把站点根目录指向项目根目录同时开启 PHP 的pdo_mysql扩展。2.3 换题库从默认题到自己的题数据表怎么设计跑通之后第一件正经事就是把默认题库换成自己的。v1.0 的题目数据存在questions表里结果分档存在results表里两张表联动构成一套完整的测试闭环。建表语句大概是这个样子-- sql/install.sql 节选 CREATE TABLE questions ( id int(11) NOT NULL AUTO_INCREMENT, content varchar(500) NOT NULL COMMENT 题目内容, type tinyint(4) NOT NULL DEFAULT 1 COMMENT 1单选 2多选, options text NOT NULL COMMENT JSON数组[{label:A,text:...,score:1}], sort_order int(11) NOT NULL DEFAULT 0 COMMENT 排序小的在前, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1启用 0停用, PRIMARY KEY (id), KEY idx_sort (sort_order, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE results ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 结果标题, min_score int(11) NOT NULL DEFAULT 0, max_score int(11) NOT NULL DEFAULT 0, content text NOT NULL COMMENT 结果描述, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;options字段是整个题库设计里最容易绕晕的地方。它存的是 JSON 字符串每个选项带label、text、score三个键label是前端提交值的标识比如 A、B、Ctext是展示给答题者看的文案score是该选项对应的分数。v1.0 之所以把选项设计成 JSON 而不是单独建一张选项表是为了减少联表查询——答题时一次取题、一次解析 JSON就能拼出完整表单。我初用这套结构时犯过一个错把score直接写进前端表单的 value 里想着省一次后端解析。结果答题者打开浏览器源码就能看到每道题的分数权重填空题还能直接伪造分值提交。正确做法是前端只提交labelscore留在options里由后端计分前端根本不需要知道权重。v1.0 的默认实现通常是后一种你在改题库时不要把这个边界破坏掉。-- 示例插入 3 道单选的职场压力自测题非临床量表仅用于演示 INSERT INTO questions (content, type, options, sort_order) VALUES (最近一周你是否经常感觉睡不够, 1, [{label:A,text:几乎不,score:0},{label:B,text:偶尔,score:1},{label:C,text:经常,score:2},{label:D,text:每天如此,score:3}], 10), (工作群里一有新消息你的第一反应是什么, 1, [{label:A,text:无感有空再回,score:0},{label:B,text:有点烦躁,score:1},{label:C,text:马上点开看,score:2},{label:D,text:心跳加速,score:3}], 20), (下班后你还能顺利切换回个人状态吗, 1, [{label:A,text:完全能,score:0},{label:B,text:需要半小时缓冲,score:1},{label:C,text:很难,score:2},{label:D,text:根本切换不了,score:3}], 30);插入后先不要急着调整results表等跑通一次完整答题再根据实际总分分布去改分档区间。这里有个操作习惯我每次改完题库都会打开quiz_take.php肉眼确认一遍渲染结果再故意提交一次确认score在后端被正确解析。数据库里题写错了可以改但计分链路写错问题往往要到结果页才暴露。3. 答题链路从页面到结果页v1.0 的数据在后端怎么流转3.1 答题页实现一次性提交最不容易出错的方式v1.0 的答题页通常做成一次性提交页面加载时把题库全部渲染出来答题者点“提交”按钮后form里的全部答案一次性 POST 到api/score.php。这个选择很朴素但符合 v1.0 的定位——不做逐题异步保存就不需要处理草稿、续答、超时这些复杂状态。渲染表单的代码长这样?php // quiz_take.php 核心逻辑节选 require __DIR__ . /config/database.php; $pdo new PDO( mysql:host{$config[host]};dbname{$config[dbname]};charset{$config[charset]}, $config[username], $config[password] ); $stmt $pdo-query(SELECT id, content, type, options FROM questions WHERE status 1 ORDER BY sort_order ASC, id ASC); $questions $stmt-fetchAll(PDO::FETCH_ASSOC); ? form methodpost actionapi/score.php ?php foreach ($questions as $q) : ? ?php $opts json_decode($q[options], true); ? div classquestion p? htmlspecialchars($q[content]) ?/p ?php foreach ($opts as $opt) : ? label input typeradio nameanswers[? (int)$q[id] ?] value? htmlspecialchars($opt[label]) ? ? htmlspecialchars($opt[text]) ? /label ?php endforeach; ? /div ?php endforeach; ? button typesubmit提交/button /form这段代码里有三个容易被忽略的细节。第一name用了answers[题目ID]这种数组写法后端$_POST[answers]直接就是一个以题目 ID 为键的数组省去前端组装 JSON 的步骤。第二所有输出都套了htmlspecialchars防止题目文案里带引号或尖括号时破坏页面结构这是安全底线。第三json_decode($q[options], true)把数据库里的 JSON 字符串解析成数组渲染层和计分层共用同一套解析逻辑。至于前端为什么不直接存分数权重而是提交label核心是防作弊和防手贱。如果页面源码里写着每个选项的分值答题者改一下浏览器请求参数就能刷出满分结果心理测试的结果可信度就没有了。v1.0 的做法是后端拿label去匹配数据库里的options再取score累加前端全程碰不到计分数据。如果你手里的源码是直接把分数写进 value 的我在第 5 章会详细讲怎么改。3.2 后端计分按选项取分、按区间分档几十行代码串起整个闭环计分入口api/score.php负责三件事接收答案、按题目 ID 查题库、累加分数并匹配结果。下面这段是我个人比较推荐的写法结构清晰也容易扩展?php // api/score.php 核心实现 session_start(); require __DIR__ . /../config/database.php; $pdo new PDO( mysql:host{$config[host]};dbname{$config[dbname]};charset{$config[charset]}, $config[username], $config[password] ); $answers $_POST[answers] ?? []; if (empty($answers) || !is_array($answers)) { header(Location: ../quiz_take.php?errorempty); exit; } // 1. 批量查题减少逐条查询的数据库压力 $ids array_keys($answers); $placeholders implode(,, array_fill(0, count($ids), ?)); $sql SELECT id, type, options FROM questions WHERE id IN ($placeholders) AND status 1; $stmt $pdo-prepare($sql); $stmt-execute($ids); $questions $stmt-fetchAll(PDO::FETCH_ASSOC); // 2. 遍历题目解析选项累加得分 $totalScore 0; foreach ($questions as $q) { $opts json_decode($q[options], true); if (!is_array($opts)) { continue; } $answer $answers[$q[id]]; if ((int)$q[type] 1) { // 单选匹配 label 取 score foreach ($opts as $opt) { if ($opt[label] $answer) { $totalScore (int)$opt[score]; break; } } } else { // 多选全部选项都命中才得分否则得 0 分v1.0 最稳的策略 $selected (array)$answer; $allHit true; foreach ($opts as $opt) { $hit in_array($opt[label], $selected, true); if ($opt[required] !$hit) { $allHit false; break; } } if ($allHit) { foreach ($opts as $opt) { $totalScore (int)$opt[score]; } } } } // 3. 按总分匹配结果分档闭区间匹配避免边界漏分 $resultSql SELECT * FROM results WHERE min_score ? AND max_score ? ORDER BY min_score ASC LIMIT 1; $resultStmt $pdo-prepare($resultSql); $resultStmt-execute([$totalScore, $totalScore]); $result $resultStmt-fetch(PDO::FETCH_ASSOC); // 4. 保存答题记录方便后续做数据导出 $logSql INSERT INTO answer_logs (session_key, total_score, result_id) VALUES (?, ?, ?); $logStmt $pdo-prepare($logSql); $logStmt-execute([session_id(), $totalScore, $result[id] ?? null]); header(Location: ../result.php?sid . urlencode(session_id())); exit;这段代码里最值得关注的是批量查询的写法IN (?, ?, ?)配合预处理语句既避免了 SQL 注入又避免了一次提交 20 道题就发 20 条查询的笨办法。多选部分我特意用“全部命中才得分”策略虽然严格但边界清晰如果以后想做“选对一半给一半分”再在$opt上单独加权重系数就行不用改动整体流程。3.3 结果分档表与总分对不齐边界值怎么设计计分链路最容易出问题的环节不在累加而在“总分落到哪个结果区间”。很多源码会用WHERE total min_score AND total max_score看起来没问题但坑在边界上——如果前一个区间是 0-20后一个是 21-40那 20.5 分这种带小数的情况就要考虑类型转换。更实际的做法是保证results表的区间是连续且整数对齐的INSERT INTO results (title, min_score, max_score, content) VALUES (状态在线, 0, 6, 你目前的压力水平较低保持现状就好。), (有些波动, 7, 12, 你处于轻度压力区间建议每天留 20 分钟放空。), (需要关注, 13, 18, 你已经进入中度压力区间睡眠和运动优先级要拉高。), (亮红灯, 19, 99, 你的压力信号比较强烈建议主动找人聊聊或做专业评估。);注意这里我用的是 0-6、7-12、13-18、19-99而不是 0-6、6-12、12-18 这种重叠区间。重叠区间在查 SQL 时因为带了LIMIT 1通常能取到第一条但一旦排序条件写错就可能出现“同样的分数昨天是轻度今天是重度”的诡异现象。v1.0 里最省心的约定是区间连续、不重叠、上界封顶到 99。另一个常见的边界坑是题目总数变了分档区间没跟着变。加了 5 道题后总分上限从 18 变成 27但results表里最大值还停在 18结果是所有高分选手全部落进“亮红灯”测试彻底失真。所以我每次改题库都会顺手算一下理论最高分然后重新过一遍分档区间这个操作比调代码更频繁也更容易忘。4. 管理端v1.0 里怎么改题、加题、上线新问卷4.1 管理端登录v1.0 的 session 鉴权够不够管理端是维护题库的入口v1.0 的常见实现是admin/login.php走 session 登录后端只认一个管理员账号不做角色权限。这个粒度对内部管理已经够用毕竟心理测试源码的受众不是拿来运营电商后台而是维护题目和查看数据。?php // admin/login.php 核心逻辑 session_start(); require __DIR__ . /../config/database.php; $adminConfig [ username admin, // 不要存明文密码用 password_hash(yourpass, PASSWORD_DEFAULT) 生成后写死 password_hash $2y$10$... ]; $error ; if ($_SERVER[REQUEST_METHOD] POST) { $username trim($_POST[username] ?? ); $password $_POST[password] ?? ; if ($username $adminConfig[username] password_verify($password, $adminConfig[password_hash])) { $_SESSION[admin_logged] true; header(Location: index.php); exit; } $error 用户名或密码错误; } ?这里有两个细节值得较真。第一密码用password_hash生成哈希后写死在配置里不要直接写明文。很多 php 源码工程为了方便把密码明文写在 config 里一旦源码被传到公共仓库或被扫描工具看到等于把管理端钥匙贴在门口。第二登录成功后只设置了$_SESSION[admin_logged]每个管理端页面在入口处检查这个标记没有就跳回login.php。session 鉴权够不够对 v1.0 够。它的极限是“一台服务器、一个管理员、一个后台”不存在多角色多租户场景。但如果你的管理端要开放给多人使用建议在登录时额外记录$_SESSION[admin_user]并且每次操作都记一条操作日志否则出了问题连谁改的题都查不到。4.2 新增一道题的完整动作从表单到数据库管理端最核心的操作是新增和编辑题目。admin/question_edit.php通常是一个同时承担“新增”和“编辑”的页面通过 URL 参数区分模式?id10是编辑第 10 题没有id参数就是新增。提交处理逻辑如下?php // admin/question_edit.php 提交处理节选 session_start(); if (empty($_SESSION[admin_logged])) { header(Location: login.php); exit; } require __DIR__ . /../config/database.php; $pdo new PDO( mysql:host{$config[host]};dbname{$config[dbname]};charset{$config[charset]}, $config[username], $config[password] ); $error ; if ($_SERVER[REQUEST_METHOD] POST) { $content trim($_POST[content] ?? ); $type (int)($_POST[type] ?? 1); $sortOrder (int)($_POST[sort_order] ?? 0); $rawOptions $_POST[options] ?? ; // 校验 JSON 是否合法避免前端把格式写坏 $optionsArr json_decode($rawOptions, true); if (empty($content) || !is_array($optionsArr)) { $error 题目内容不能为空且选项必须是合法 JSON 数组; } else { $sql INSERT INTO questions (content, type, options, sort_order, status) VALUES (?, ?, ?, ?, 1); $stmt $pdo-prepare($sql); $stmt-execute([$content, $type, json_encode($optionsArr, JSON_UNESCAPED_UNICODE), $sortOrder]); header(Location: index.php); exit; } } ?表单里最值得提醒的是options这个 textarea。它要求按[{label:A,text:...,score:1}]的格式手写 JSON新手经常少写个引号或多写个逗号。我自己的习惯是让json_encode做最终输出提交后台只负责校验不负责推断前端写坏的直接提示不修。JSON_UNESCAPED_UNICODE参数很关键不加的话中文选项会被转成\uXXXX虽然能正常存但后续排查很痛苦。4.3 排序、停用与问卷扩展v1.0 的边界在哪里v1.0 对题目排序的处理通常很简单按sort_order升序再按id升序。sort_order不要求连续给 10、20、30 这样的间隔以后插队题目不用改一堆数字。停用功能靠status字段设成 0 的题目不会出现在答题页但历史答题记录不受影响。这套结构的扩展边界在前端不强、后端不弱如果想把一套题库拆成多套问卷只需要给questions表加一个survey_id字段答题页入口改成quiz_take.php?survey_id2即可。v1.0 版本通常不做这个是因为“知道该加这张表和这个字段”比“直接做出来”更重要——很多改造其实不是技术难而是数据结构没留余地。5. 避坑排查心理测试源码 v1.0 最容易翻车的 5 个现场5.1 数据库导入失败SQL 版本差异与字符集不兼容现象按 README 执行导入命令终端报错Unknown collation: utf8mb4_0900_ai_ci或者1064语法错误。原因很多 v1.0 源码的 install.sql 是在 MySQL 8.0 环境里导出的默认字符集排序规则是utf8mb4_0900_ai_ci而 MySQL 5.7 及以下版本根本不认识这个排序规则。这不是代码问题是数据库版本和 SQL 文件“代沟”。解决打开sql/install.sql全文搜索utf8mb4_0900_ai_ci替换成utf8mb4_general_ci然后再执行导入。如果文件里没有明确写排序规则就在建表语句末尾显式加上DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci。改完后重新导入一般能直接过。这一步做完也顺手确认一下代码里config/database.php的 charset 是utf8mb4两边一致才不会有后续麻烦。5.2 提交后白屏或直接下载 PHP 文件入口配置和报错隐藏现象答题页能打开点提交后页面白屏或者访问api/score.php时浏览器直接弹出下载框。原因白屏通常是 PHP 报错被display_errorsOff隐藏了代码里某个致命错误没有展示出来下载框则是 Web 服务器没把.php交给 PHP 解释器Apache 没开mod_php或 Nginx 没配fastcgi_pass。很多用户第一反应是改代码实际上问题根本不在代码层。解决先在项目根目录新建一个临时error_test.php写上?php phpinfo();访问看能否正常输出。如果能输出说明 PHP 解析正常白屏是业务代码报错如果直接下载说明服务器配置不对去把 PHP 与 Web 服务器的解析绑定关系修复。确认能解析后打开php -l api/score.php做一次语法检查再临时把display_errors设为On复现报错解决后改回Off。5.3 题目里带引号、换行或 emoji 时被截断现象管理端保存的题目文字完整前台答题页显示时后半截消失或者 emoji 变成两个问号。原因这类问题九成出在字符集另外一成出在转义。数据库表和连接字符集如果用的是utf8mb3一个 emoji 需要 4 字节存储放不进去就被截断或写成问号。更隐蔽的是 PHP 代码里使用htmlspecialchars($content)之前没有做字符集声明htmlspecialchars在非 UTF-8 环境下对多字节字符的转换结果不可控。解决连库时强制charsetutf8mb4建表时统一DEFAULT CHARSETutf8mb4页面输出前加header(Content-Type: text/html; charsetutf-8);。同时检查 PHP 源文件本身是否带 BOM带 BOM 的文件在输出时会先吐三个不可见字符接口返回 JSON 时会直接解析失败。用编辑器“存为 UTF-8 无 BOM”统一处理一遍这类玄学问题基本绝迹。5.4 计分结果对不上单选多选混排时的类型陷阱现象某道题明明选了 A前端显示正常后端最后算出来的分数却少了 2 分或者多选题目里只选一项也得了满分。原因v1.0 最典型的坑是单选用$_POST[answers][$id]能拿到字符串多选因为 input 的 name 相同PHP 会把它转成数组如果代码里没有统一转类型就会出问题。另一种常见情况是前端 value 传的是label后端却拿text去比对文案里带空格或大小写不一就匹配不上分数自然偏低。解决单选和多选分别处理多选入口先用(array)$answer强制转换再统一用严格比较in_array($opt[label], $selected, true)第三个参数true确保类型和值都相等。另外在计分时加一个短暂的过程日志把每道题命中哪个选项、加了几分写进临时文件业务上能肉眼核对如果所有题目都对得上但总分还不对再回头看results表区间。5.5 改完代码不生效OPcache 和浏览器缓存现象改了score.php的计分逻辑刷新页面发现还是旧效果甚至结果分数没变化。原因PHP 7 之后默认开启了 OPcache开发环境下如果没配置opcache.revalidate_freq0PHP 会复用缓存里的旧脚本你改的文件根本没被重新执行。加上前端页面用?v1这种参数控制缓存时改了?v2才能强制刷新很多人栽在这一步。解决本地开发在php.ini里设opcache.enable1、opcache.revalidate_freq0确保每次请求都重新校验文件时间戳。正式环境不想频繁重载就保持revalidate_freq为一个合理值比如 60 秒。改完代码后建议先访问管理端或答题页加个查询参数强制刷新确认不是浏览器缓存后再判断是否代码本身的问题。6. 从 v1.0 到能交付防重复、分享页与 CSV 导出的三个落地技巧6.1 用 Cookie 答题记录表做防重复提交心理测试最怕同一个答题者刷 N 次把结果数据完全污染。v1.0 阶段不做登录体系靠 Cookie 和answer_logs表就能挡住大部分手滑和轻度刷题。?php // api/score.php 提交前增加防重复校验 if (isset($_COOKIE[psych_test_done])) { header(Location: ../result.php?sid . urlencode($_COOKIE[psych_test_done])); exit; } // 计分完成后种 Cookie标识已答题 setcookie(psych_test_done, session_id(), time() 86400 * 30, /);这个方案只能挡君子挡不住清 Cookie 的熟练用户。真正要加固后端可以同时记录 IP 题目 ID 组合判断同 IP 最近 30 分钟内是否已经提交过再狠一点加时间戳校验要求答题耗时至少大于 60 秒否则判定异常。v1.0 做到 Cookie 记录表这层已经够用往上加会显著增加复杂度。6.2 结果页做成可分享卡片很多心理测试的传播场景是“测完把结果甩给别人”所以结果页必须支持分享预览。做法是在result.php的head里输出标准开放分享协议标签同时让结果页 URL 带上答题记录 ID保证每个人拿到的都是自己那份结果。!-- result.php head 中动态输出 -- meta propertyog:title content我的职场压力测试结果状态在线 meta propertyog:description content我已经完成了职场压力自测得分 5/18你也来试试。 meta propertyog:type contentwebsite这里的关键是这些标签必须服务端渲染不能用前端 JS 动态生成否则大部分平台的分享抓取器读不到内容。拿到这套源码后把result.php改成先查answer_logs拿分数和结果标题再拼出这些标签即可。URL 里的sid要做合法性校验查不到记录就跳回答题页防止别人随便改参数看任意结果。6.3 答题数据导出 CSV给内部分析留一条后路管理端还有一个常被忽略的需求导出全部答题记录。心理测试源码 v1.0 通常没做可视化报表但给一个 CSV 导出接口Excel 里自己想怎么透视都行。?php // admin/export.php session_start(); if (empty($_SESSION[admin_logged])) { header(Location: login.php); exit; } require __DIR__ . /../config/database.php; header(Content-Type: text/csv; charsetUTF-8); header(Content-Disposition: attachment; filenameanswers_ . date(Ymd_His) . .csv); $out fopen(php://output, w); fputcsv($out, [答题时间, 总分, 结果标题, 结果内容]); $stmt $pdo-query( SELECT a.created_at, a.total_score, r.title, r.content FROM answer_logs a LEFT JOIN results r ON r.id a.result_id ORDER BY a.id DESC ); foreach ($stmt-fetchAll(PDO::FETCH_ASSOC) as $row) { fputcsv($out, $row); } fclose($out); exit;导出接口别忘了设置Content-Type和Content-Disposition否则浏览器会把 CSV 内容直接显示在页面上。中文乱码问题也很常见Excel 打开 CSV 时默认按 GBK 解析办法是输出前加一个\xEF\xBB\xBF的 BOM 头或者让用户改用 WPS 打开。我习惯在文件开头加 BOM因为大多数用户拿到的就是 Excel。这些年我养成了一个改版习惯每次改完题库、计分规则或结果分档都按“提交一次 → 查数据库 answer_logs → 看结果页 → 导一次 CSV”这四步走一遍全链路。任何一环不对就停在那里修不往下堆新功能。这套心理测试源码 v1.0 看起来简单但把闭环做扎实、把边界想清楚它就能稳定承载一套真实上线的小型测评服务。希望帮到你。本文还有配套的精品资源点击获取