survey/public/survey.html
2025-10-30 00:03:09 +08:00

2257 lines
79 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学科能力测评问卷</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
color: #333;
margin: 0;
padding: 0;
line-height: 1.6;
min-height: 100vh;
}
.container {
width: 100%;
background: transparent;
overflow: hidden;
min-height: 100vh;
}
.header {
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
color: white;
padding: 80px 40px 60px;
position: relative;
overflow: hidden;
text-align: center;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") repeat;
}
.header-content {
position: relative;
z-index: 2;
max-width: 600px;
margin: 0 auto;
}
.header h1 {
font-size: 42px;
font-weight: 700;
margin-bottom: 16px;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
font-size: 18px;
opacity: 0.9;
line-height: 1.6;
margin-bottom: 0;
}
.config-section {
padding: 0;
background: #ffffff;
margin-top: -60px;
position: relative;
z-index: 10;
}
.unified-config {
width: 100%;
max-width: 1000px;
margin: 0 auto;
background: white;
border-radius: 24px 24px 0 0;
box-shadow: 0 -10px 40px rgba(255, 126, 95, 0.15);
overflow: hidden;
animation: slideUp 0.8s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.config-intro {
background: linear-gradient(135deg, rgba(255, 126, 95, 0.1) 0%, rgba(254, 180, 123, 0.1) 100%);
padding: 40px;
text-align: center;
border-bottom: 1px solid rgba(255, 126, 95, 0.1);
}
.intro-icon {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
.config-intro h2 {
font-size: 28px;
font-weight: 600;
color: #2d3748;
margin-bottom: 12px;
letter-spacing: -0.5px;
}
.config-intro p {
font-size: 16px;
color: #718096;
line-height: 1.6;
max-width: 600px;
margin: 0 auto;
}
.form-container {
padding: 40px;
}
.form-section {
margin-bottom: 40px;
}
.form-section:last-child {
margin-bottom: 0;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 24px;
}
.section-icon {
font-size: 24px;
margin-right: 12px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 12px;
}
.section-header h3 {
font-size: 20px;
font-weight: 600;
color: #2d3748;
margin: 0;
}
.section-divider {
flex: 1;
height: 1px;
background: linear-gradient(90deg, #e2e8f0, transparent);
margin-left: 20px;
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.form-field {
position: relative;
}
.form-field.full-width {
grid-column: 1 / -1;
}
.field-label {
display: flex;
align-items: center;
margin-bottom: 8px;
font-weight: 500;
}
.label-text {
color: #4a5568;
font-size: 14px;
}
.required {
color: #e53e3e;
margin-left: 4px;
font-size: 12px;
}
.optional {
color: #a0aec0;
margin-left: 8px;
font-size: 12px;
background: #f7fafc;
padding: 2px 8px;
border-radius: 12px;
}
.modern-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background: white;
}
.modern-input:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
transform: translateY(-1px);
}
.modern-input::placeholder {
color: #a0aec0;
}
.select-wrapper {
position: relative;
}
.modern-select {
width: 100%;
padding: 12px 40px 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 16px;
background: white;
appearance: none;
cursor: pointer;
transition: all 0.3s ease;
}
.modern-select:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
transform: translateY(-1px);
}
.select-arrow {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
color: #a0aec0;
font-size: 12px;
pointer-events: none;
transition: transform 0.3s ease;
}
.modern-select:focus + .select-arrow {
transform: translateY(-50%) rotate(180deg);
}
.field-hint {
margin-top: 6px;
font-size: 13px;
color: #718096;
}
.rules-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
.rule-card {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
padding: 24px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.rule-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ff7e5f, #feb47b);
}
.rule-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-color: #cbd5e0;
}
.rule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.rule-type {
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.rule-type.basic {
background: #c6f6d5;
color: #22543d;
}
.rule-type.advanced {
background: #fed7aa;
color: #7c2d12;
}
.rule-type.competition {
background: #fed7e2;
color: #702459;
}
.rule-points {
font-size: 12px;
color: #718096;
background: white;
padding: 4px 8px;
border-radius: 8px;
}
.rule-input-area {
margin-bottom: 12px;
}
.rule-number-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 18px;
font-weight: 600;
text-align: center;
transition: all 0.3s ease;
background: white;
}
.rule-number-input:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
transform: scale(1.02);
}
.rule-calculation {
text-align: center;
margin-top: 8px;
font-size: 14px;
color: #4a5568;
font-weight: 500;
}
.rule-status {
text-align: center;
font-size: 12px;
}
.status-text {
color: #718096;
}
.status-text.success {
color: #38a169;
}
.status-text.error {
color: #e53e3e;
}
.config-summary {
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
padding: 40px;
border-top: 1px solid #e2e8f0;
}
.summary-content {
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
border: 1px solid #e2e8f0;
}
.summary-stats {
display: flex;
align-items: center;
gap: 32px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 36px;
font-weight: 700;
line-height: 1;
margin-bottom: 8px;
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 14px;
color: #718096;
font-weight: 500;
}
.stat-divider {
width: 2px;
height: 50px;
background: linear-gradient(180deg, #e2e8f0, transparent);
border-radius: 1px;
}
.start-button {
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
color: white;
border: none;
padding: 18px 36px;
border-radius: 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 15px rgba(255, 126, 95, 0.3);
position: relative;
overflow: hidden;
}
.start-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.6s ease;
}
.start-button:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(255, 126, 95, 0.4);
}
.start-button:hover:not(:disabled)::before {
left: 100%;
}
.start-button:disabled {
background: #e2e8f0;
color: #a0aec0;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.start-button:disabled::before {
display: none;
}
.button-arrow {
transition: transform 0.3s ease;
font-size: 18px;
}
.start-button:hover:not(:disabled) .button-arrow {
transform: translateX(6px);
}
.rule-item {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 10px;
border-left: 4px solid #ff7e5f;
}
.rule-title {
font-weight: 600;
color: #2d5a3d;
margin-bottom: 5px;
}
.rule-details {
font-size: 14px;
color: #666;
}
.rules-config {
display: flex;
flex-direction: column;
gap: 20px;
}
.rule-input-group {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
}
.rule-input-group:hover {
border-color: #ff7e5f;
box-shadow: 0 4px 15px rgba(255, 126, 95, 0.1);
}
.rule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.rule-label {
font-weight: 600;
color: #2d5a3d;
font-size: 16px;
}
.rule-score {
background: linear-gradient(135deg, #fff8f3, #ffffff);
color: #ff7e5f;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
border: 1px solid #ff7e5f;
}
.rule-input-wrapper {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.rule-input {
flex: 1;
padding: 12px 15px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
transition: all 0.3s ease;
background: white;
}
.rule-input:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
}
.input-unit {
color: #666;
font-weight: 500;
font-size: 14px;
}
.rule-calculation {
color: #666;
font-size: 14px;
padding: 8px 12px;
background: white;
border-radius: 6px;
border-left: 4px solid #4CAF50;
}
.rule-summary {
margin-top: 20px;
padding: 20px;
background: linear-gradient(135deg, #e3f2fd, #f8f9fa);
border-radius: 12px;
border: 2px solid #2196F3;
display: flex;
justify-content: space-between;
align-items: center;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.summary-label {
font-size: 14px;
color: #666;
font-weight: 500;
}
.summary-value {
font-size: 20px;
font-weight: 700;
color: #2196F3;
}
.tag-filter {
margin: 15px 0;
}
.tag-filter label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.tag-input {
width: 100%;
padding: 10px;
border: 2px solid #e9ecef;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s;
}
.tag-input:focus {
outline: none;
border-color: #ff7e5f;
}
.tag-select {
width: 100%;
padding: 12px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
background: white;
transition: all 0.3s;
min-height: 50px;
margin-bottom: 15px;
}
.tag-select:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
}
.tag-select option {
padding: 8px 12px;
}
.student-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-weight: 600;
color: #2d5a3d;
margin-bottom: 8px;
font-size: 14px;
}
.form-input {
padding: 12px 15px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s;
background: white;
}
.form-input:focus {
outline: none;
border-color: #ff7e5f;
box-shadow: 0 0 0 3px rgba(255, 126, 95, 0.1);
}
.form-input:valid {
border-color: #4CAF50;
}
.form-input:invalid:not(:placeholder-shown) {
border-color: #f44336;
}
.selected-tags {
background: #f8f9fa;
padding: 12px;
border-radius: 6px;
border-left: 4px solid #ff7e5f;
margin-top: 10px;
}
.selected-tags #selectedTagsList {
color: #2d5a3d;
font-weight: 500;
}
.start-btn {
background: linear-gradient(135deg, #ff7e5f, #feb47b);
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: block;
margin: 20px auto;
}
.start-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 126, 95, 0.4);
}
.start-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.survey-content {
padding: 30px;
display: none;
}
.progress-bar {
background: #e9ecef;
height: 8px;
border-radius: 4px;
margin-bottom: 30px;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(90deg, #ff7e5f, #feb47b);
height: 100%;
border-radius: 4px;
transition: width 0.3s ease;
width: 0%;
}
.question-card {
background: white;
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
}
.question-card:hover {
border-color: #ff7e5f;
box-shadow: 0 4px 20px rgba(255, 126, 95, 0.15);
}
.question-header {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.question-number {
background: linear-gradient(135deg, #ff7e5f, #feb47b);
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 15px;
}
.question-type {
background: linear-gradient(135deg, #fff8f3, #ffffff);
color: #ff7e5f;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
margin-left: auto;
border: 1px solid #ff7e5f;
}
.question-type.基础题 {
background: #e8f5e8;
color: #2e7d32;
}
.question-type.进阶题 {
background: #fff3e0;
color: #f57c00;
}
.question-type.竞赛题 {
background: #fce4ec;
color: #c2185b;
}
.question-text {
font-size: 18px;
color: #333;
line-height: 1.6;
margin-bottom: 20px;
font-weight: 500;
}
.options-list {
list-style: none;
}
.option-item {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
}
.option-item:hover {
background: linear-gradient(135deg, #fff8f3, #ffffff);
border-color: #ff7e5f;
transform: translateY(-1px);
}
.option-item.selected {
background: linear-gradient(135deg, #fff8f3, #ffffff);
border-color: #ff7e5f;
}
.option-item input[type="radio"] {
margin-right: 12px;
transform: scale(1.2);
}
.option-text {
flex: 1;
font-size: 16px;
color: #333;
}
.submit-btn {
background: linear-gradient(135deg, #4CAF50, #66BB6A);
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: block;
margin: 30px auto;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
}
.submit-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.results-section {
padding: 30px;
display: none;
}
.score-card {
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 50%, #ff7e5f 100%);
color: white;
padding: 30px;
border-radius: 12px;
text-align: center;
margin-bottom: 30px;
}
.score-value {
font-size: 48px;
font-weight: 700;
margin-bottom: 10px;
}
.score-label {
font-size: 18px;
opacity: 0.9;
}
.results-details {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
}
.result-item {
background: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 10px;
border-left: 4px solid #ff7e5f;
}
.loading {
text-align: center;
padding: 50px;
color: #666;
}
.error {
background: #ffebee;
color: #c62828;
padding: 20px;
border-radius: 8px;
margin: 20px;
border-left: 4px solid #f44336;
}
.view-report-btn {
background: linear-gradient(135deg, #FF6B6B, #4ECDC4);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.view-report-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(78, 205, 196, 0.4);
}
.report-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 12px;
width: 90%;
max-width: 900px;
max-height: 85vh;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal-header {
background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 50%, #ff7e5f 100%);
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
}
.close-modal {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s;
}
.close-modal:hover {
background: rgba(255, 255, 255, 0.2);
}
.modal-body {
padding: 20px;
max-height: calc(85vh - 80px);
overflow-y: auto;
}
.report-actions {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.copy-btn, .download-btn {
background: linear-gradient(135deg, #4CAF50, #66BB6A);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.copy-btn:hover, .download-btn:hover {
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3);
}
.download-btn {
background: linear-gradient(135deg, #2196F3, #42A5F5);
}
.download-btn:hover {
box-shadow: 0 3px 10px rgba(33, 150, 243, 0.3);
}
.report-content {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
white-space: pre-wrap;
overflow-x: auto;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.report-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 12px;
}
.report-table th,
.report-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
vertical-align: top;
}
.report-table th {
background: #f2f2f2;
font-weight: bold;
}
.report-table tr:nth-child(even) {
background: #f9f9f9;
}
@media (max-width: 768px) {
.header {
padding: 40px 20px 30px;
}
.header h1 {
font-size: 32px;
}
.header p {
font-size: 16px;
}
.config-section {
margin-top: -40px;
}
.unified-config {
border-radius: 20px 20px 0 0;
}
.config-intro {
padding: 30px 20px;
}
.intro-icon {
font-size: 40px;
}
.config-intro h2 {
font-size: 24px;
}
.config-intro p {
font-size: 14px;
}
.form-container {
padding: 30px 20px;
}
.form-section {
margin-bottom: 30px;
}
.section-header {
flex-direction: column;
align-items: flex-start;
margin-bottom: 20px;
}
.section-icon {
margin-bottom: 12px;
}
.section-divider {
display: none;
}
.form-row {
grid-template-columns: 1fr;
gap: 16px;
}
.rules-grid {
grid-template-columns: 1fr;
gap: 16px;
}
.rule-card {
padding: 20px;
}
.rule-number-input {
font-size: 16px;
}
.config-summary {
padding: 30px 20px;
}
.summary-content {
flex-direction: column;
gap: 24px;
padding: 24px;
}
.summary-stats {
justify-content: center;
}
.stat-value {
font-size: 28px;
}
.start-button {
width: 100%;
justify-content: center;
}
}
@media (max-width: 480px) {
.header {
padding: 30px 15px 20px;
}
.header h1 {
font-size: 28px;
}
.header p {
font-size: 14px;
}
.config-section {
margin-top: -30px;
}
.unified-config {
border-radius: 16px 16px 0 0;
}
.config-intro {
padding: 25px 15px;
}
.intro-icon {
font-size: 36px;
margin-bottom: 12px;
}
.config-intro h2 {
font-size: 22px;
}
.form-container {
padding: 25px 15px;
}
.rule-card {
padding: 16px;
}
.config-summary {
padding: 25px 15px;
}
.summary-content {
padding: 20px;
}
.stat-value {
font-size: 24px;
}
}
@media (max-width: 768px) {
.container {
margin: 10px;
border-radius: 10px;
}
.header, .survey-content, .results-section {
padding: 20px;
}
.question-text {
font-size: 16px;
}
.option-text {
font-size: 14px;
}
.score-value {
font-size: 36px;
}
.modal-content {
width: 95%;
max-height: 90vh;
}
.modal-header {
padding: 15px;
}
.modal-header h3 {
font-size: 16px;
}
.modal-body {
padding: 15px;
max-height: calc(90vh - 60px);
}
.report-actions {
flex-direction: column;
}
.copy-btn, .download-btn {
width: 100%;
}
.report-content {
font-size: 12px;
padding: 15px;
}
}
.footer {
padding: 20px;
text-align: center;
color: #8b5a2b;
font-size: 14px;
background: #fef6f0;
border-top: 1px solid #ffe4d6;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-content">
<h1>🎯 学科能力测评问卷</h1>
<p>基于科学的题目分类和标签筛选系统,精准评估您的学科能力</p>
</div>
</div>
<div class="config-section" id="configSection">
<div class="unified-config">
<div class="config-intro">
<div class="intro-icon">📝</div>
<h2>开始测评</h2>
<p>填写基本信息,配置答题规则,即可开始您的学科能力测评之旅</p>
</div>
<div class="form-container">
<!-- 学员信息区域 -->
<div class="form-section">
<div class="section-header">
<div class="section-icon">👤</div>
<h3>学员信息</h3>
<div class="section-divider"></div>
</div>
<div class="form-row">
<div class="form-field">
<label for="studentName" class="field-label">
<span class="label-text">姓名</span>
<span class="required">*</span>
</label>
<input type="text" id="studentName" class="modern-input" placeholder="请输入您的姓名" required>
</div>
<div class="form-field">
<label for="studentSchool" class="field-label">
<span class="label-text">学校</span>
<span class="required">*</span>
</label>
<input type="text" id="studentSchool" class="modern-input" placeholder="请输入您的学校" required>
</div>
<div class="form-field">
<label for="studentGrade" class="field-label">
<span class="label-text">年级</span>
<span class="required">*</span>
</label>
<input type="text" id="studentGrade" class="modern-input" placeholder="请输入您的年级" required>
</div>
</div>
</div>
<!-- 题目筛选区域 -->
<div class="form-section">
<div class="section-header">
<div class="section-icon">🏷️</div>
<h3>题目筛选</h3>
<div class="section-divider"></div>
</div>
<div class="form-row">
<div class="form-field full-width">
<label for="gradeFilter" class="field-label">
<span class="label-text">选择题目标签</span>
<span class="optional">可选</span>
</label>
<div class="select-wrapper">
<select id="gradeFilter" class="modern-select">
<option value="">选择年级/册次标签...</option>
</select>
<div class="select-arrow"></div>
</div>
<div class="field-hint">
<span id="selectedTagsList">未选择标签,将使用全部题目</span>
</div>
</div>
</div>
</div>
<!-- 抽题规则区域 -->
<div class="form-section">
<div class="section-header">
<div class="section-icon">📋</div>
<h3>抽题规则</h3>
<div class="section-divider"></div>
</div>
<div class="rules-grid">
<div class="rule-card">
<div class="rule-header">
<div class="rule-type basic">基础题</div>
<div class="rule-points">5分/题</div>
</div>
<div class="rule-input-area">
<input type="number" id="basicQuestions" class="rule-number-input" min="0" max="50" value="10" placeholder="0">
<div class="rule-calculation">
<span id="basicScore">50</span>
</div>
</div>
<div class="rule-status" id="basicStatus">
<span class="status-text">可用题目加载中...</span>
</div>
</div>
<div class="rule-card">
<div class="rule-header">
<div class="rule-type advanced">进阶题</div>
<div class="rule-points">10分/题</div>
</div>
<div class="rule-input-area">
<input type="number" id="advancedQuestions" class="rule-number-input" min="0" max="20" value="2" placeholder="0">
<div class="rule-calculation">
<span id="advancedScore">20</span>
</div>
</div>
<div class="rule-status" id="advancedStatus">
<span class="status-text">可用题目加载中...</span>
</div>
</div>
<div class="rule-card">
<div class="rule-header">
<div class="rule-type competition">竞赛题</div>
<div class="rule-points">15分/题</div>
</div>
<div class="rule-input-area">
<input type="number" id="competitionQuestions" class="rule-number-input" min="0" max="15" value="2" placeholder="0">
<div class="rule-calculation">
<span id="competitionScore">30</span>
</div>
</div>
<div class="rule-status" id="competitionStatus">
<span class="status-text">可用题目加载中...</span>
</div>
</div>
</div>
</div>
</div>
<!-- 配置总结 -->
<div class="config-summary">
<div class="summary-content">
<div class="summary-stats">
<div class="stat-item">
<div class="stat-value" id="summaryTotalQuestions">14</div>
<div class="stat-label">总题数</div>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<div class="stat-value" id="summaryTotalScore">100</div>
<div class="stat-label">总分数</div>
</div>
</div>
<button class="start-button" id="startSurvey">
<span class="button-text">开始答题</span>
<span class="button-arrow"></span>
</button>
</div>
</div>
</div>
</div>
<div class="survey-content" id="surveyContent">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div id="questionsContainer"></div>
<button class="submit-btn" id="submitBtn" disabled>提交问卷</button>
</div>
<div class="results-section" id="resultsSection">
<div class="score-card">
<div class="score-value" id="scoreValue">0</div>
<div class="score-label">总分 (100分制)</div>
<button class="view-report-btn" id="viewReportBtn" style="margin-top: 20px;">📊 查看测评报告</button>
</div>
<div class="results-details" id="resultsDetails"></div>
<div class="report-modal" id="reportModal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>📊 考试答题情况分析报告</h3>
<button class="close-modal" id="closeModal">×</button>
</div>
<div class="modal-body">
<div class="report-actions">
<button class="copy-btn" id="copyReportBtn">📋 复制报告</button>
<button class="download-btn" id="downloadReportBtn">💾 下载报告</button>
</div>
<div class="report-content" id="reportContent"></div>
</div>
</div>
</div>
</div>
<div class="footer">
<p>© 2024 尚逸基石学科能力测评系统</p>
</div>
</div>
<script>
class SurveySystem {
constructor() {
this.questions = {};
this.tags = {};
this.filteredQuestions = {};
this.currentQuestions = [];
this.answers = {};
this.currentRules = {
"基础题": 10,
"进阶题": 2,
"竞赛题": 2
};
this.selectedTag = '';
this.init();
}
async init() {
try {
await this.loadQuestions();
await this.loadTags();
this.initializeTagSelectors();
this.bindEvents();
this.updateRulesFromInputs();
this.updateConfigDisplay();
} catch (error) {
this.showError('系统初始化失败: ' + error.message);
}
}
async loadQuestions() {
try {
const response = await fetch('/api/questions');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.questions = await response.json();
console.log('题库加载成功:', {
基础题: this.questions['基础题']?.length || 0,
进阶题: this.questions['进阶题']?.length || 0,
竞赛题: this.questions['竞赛题']?.length || 0
});
} catch (error) {
throw new Error('题库加载失败: ' + error.message);
}
}
async loadTags() {
try {
const response = await fetch('/api/tags');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.tags = await response.json();
console.log('标签数据加载成功:', this.tags.total_unique_tags, '个标签');
} catch (error) {
console.warn('标签数据加载失败,将使用备用方案:', error.message);
this.generateTagsFromQuestions();
}
}
generateTagsFromQuestions() {
const allTags = new Set();
Object.values(this.questions).flat().forEach(question => {
const tags = question['题目标签'] || question['标签'] || '';
if (tags) {
tags.split(/[\s,]+/).forEach(tag => {
if (tag.trim()) allTags.add(tag.trim());
});
}
});
this.tags = {
tags: Array.from(allTags).sort(),
tag_counts: {},
total_unique_tags: allTags.size
};
}
initializeTagSelectors() {
const gradeTags = this.tags.tags.filter(tag =>
tag.includes('年级') || tag.includes('册')
);
this.populateSelect('gradeFilter', gradeTags);
}
populateSelect(selectId, tags) {
const select = document.getElementById(selectId);
select.innerHTML = '<option value="">选择年级/册次标签...</option>';
tags.forEach(tag => {
const option = document.createElement('option');
option.value = tag;
option.textContent = tag;
select.appendChild(option);
});
}
bindEvents() {
document.getElementById('startSurvey').addEventListener('click', () => this.startSurvey());
document.getElementById('submitBtn').addEventListener('click', () => this.submitSurvey());
// 年级标签下拉菜单事件
document.getElementById('gradeFilter').addEventListener('change', () => this.updateSelectedTags());
// 新的抽题规则输入事件
document.getElementById('basicQuestions').addEventListener('input', () => this.updateRulesFromInputs());
document.getElementById('advancedQuestions').addEventListener('input', () => this.updateRulesFromInputs());
document.getElementById('competitionQuestions').addEventListener('input', () => this.updateRulesFromInputs());
// 测评报告相关事件
document.getElementById('viewReportBtn').addEventListener('click', () => this.showReportModal());
document.getElementById('closeModal').addEventListener('click', () => this.hideReportModal());
document.getElementById('copyReportBtn').addEventListener('click', () => this.copyReport());
document.getElementById('downloadReportBtn').addEventListener('click', () => this.downloadReport());
// 点击模态框外部关闭
document.getElementById('reportModal').addEventListener('click', (e) => {
if (e.target.id === 'reportModal') {
this.hideReportModal();
}
});
}
updateSelectedTags() {
const select = document.getElementById('gradeFilter');
this.selectedTag = select.value || '';
this.updateSelectedTagsDisplay();
this.updateConfigDisplay();
}
updateSelectedTagsDisplay() {
const container = document.getElementById('selectedTagsList');
if (this.selectedTag) {
container.innerHTML = this.selectedTag;
} else {
container.innerHTML = '无';
}
}
updateRulesFromInputs() {
const basicQuestions = parseInt(document.getElementById('basicQuestions').value) || 0;
const advancedQuestions = parseInt(document.getElementById('advancedQuestions').value) || 0;
const competitionQuestions = parseInt(document.getElementById('competitionQuestions').value) || 0;
this.currentRules = {
"基础题": basicQuestions,
"进阶题": advancedQuestions,
"竞赛题": competitionQuestions
};
this.updateRuleCalculations();
this.updateConfigDisplay();
}
updateRuleCalculations() {
const basicQuestions = parseInt(document.getElementById('basicQuestions').value) || 0;
const advancedQuestions = parseInt(document.getElementById('advancedQuestions').value) || 0;
const competitionQuestions = parseInt(document.getElementById('competitionQuestions').value) || 0;
// 更新各项计算
document.getElementById('basicScore').textContent = basicQuestions * 5;
document.getElementById('advancedScore').textContent = advancedQuestions * 10;
document.getElementById('competitionScore').textContent = competitionQuestions * 15;
// 更新总计
const totalQuestions = basicQuestions + advancedQuestions + competitionQuestions;
const totalScore = (basicQuestions * 5) + (advancedQuestions * 10) + (competitionQuestions * 15);
document.getElementById('summaryTotalQuestions').textContent = totalQuestions;
document.getElementById('summaryTotalScore').textContent = totalScore;
}
filterQuestionsByTags(selectedTag) {
if (!selectedTag) {
return this.questions;
}
const filtered = {
"基础题": [],
"进阶题": [],
"竞赛题": []
};
Object.keys(this.questions).forEach(type => {
filtered[type] = this.questions[type].filter(question => {
const questionTags = question['题目标签'] || question['标签'] || '';
if (!questionTags) return false;
// 正确分割标签并检查是否包含选中的标签
const tagList = questionTags.split(/[\s,]+/);
return tagList.includes(selectedTag);
});
});
return filtered;
}
selectRandomQuestions(questionsByType, rules) {
const selected = [];
Object.keys(rules).forEach(type => {
const count = rules[type];
const availableQuestions = questionsByType[type] || [];
if (availableQuestions.length < count) {
console.warn(`${type}可用题目不足 (${availableQuestions.length}/${count})`);
}
// 随机选择题目
const shuffled = [...availableQuestions].sort(() => 0.5 - Math.random());
const selectedForType = shuffled.slice(0, Math.min(count, availableQuestions.length));
selectedForType.forEach(question => {
selected.push({
...question,
questionType: type,
questionId: `${type}_${question['序号']}`
});
});
});
// 随机打乱题目顺序
return selected.sort(() => 0.5 - Math.random());
}
updateConfigDisplay() {
this.filteredQuestions = this.filterQuestionsByTags(this.selectedTag);
// 更新输入框的可选性(显示当前可用题目数量)
const basicAvailable = this.filteredQuestions['基础题']?.length || 0;
const advancedAvailable = this.filteredQuestions['进阶题']?.length || 0;
const competitionAvailable = this.filteredQuestions['竞赛题']?.length || 0;
// 更新输入框的最大值和可用性显示
const basicInput = document.getElementById('basicQuestions');
const advancedInput = document.getElementById('advancedQuestions');
const competitionInput = document.getElementById('competitionQuestions');
basicInput.max = basicAvailable;
advancedInput.max = advancedAvailable;
competitionInput.max = competitionAvailable;
// 检查当前设置是否超出可用题目数量
const basicQuestions = parseInt(basicInput.value) || 0;
const advancedQuestions = parseInt(advancedInput.value) || 0;
const competitionQuestions = parseInt(competitionInput.value) || 0;
// 添加可用性警告样式
this.updateInputAvailability(basicInput, basicQuestions, basicAvailable);
this.updateInputAvailability(advancedInput, advancedQuestions, advancedAvailable);
this.updateInputAvailability(competitionInput, competitionQuestions, competitionAvailable);
// 检查是否有足够的题目开始答题
const canStart = basicQuestions <= basicAvailable &&
advancedQuestions <= advancedAvailable &&
competitionQuestions <= competitionAvailable &&
(basicQuestions + advancedQuestions + competitionQuestions) > 0;
const startBtn = document.getElementById('startSurvey');
startBtn.disabled = !canStart;
if (!canStart) {
if ((basicQuestions + advancedQuestions + competitionQuestions) === 0) {
startBtn.querySelector('.button-text').textContent = '请设置题目数量';
} else {
startBtn.querySelector('.button-text').textContent = '题目不足,无法开始';
}
startBtn.disabled = true;
} else {
startBtn.querySelector('.button-text').textContent = '开始答题';
startBtn.disabled = false;
}
}
updateInputAvailability(input, requested, available) {
const ruleCard = input.closest('.rule-card');
const statusElement = ruleCard.querySelector('.status-text');
if (requested > available) {
input.style.borderColor = '#e53e3e';
statusElement.className = 'status-text error';
statusElement.textContent = `题目不足:仅${available}题可用`;
} else if (requested === 0) {
input.style.borderColor = '#e2e8f0';
statusElement.className = 'status-text';
statusElement.textContent = `${available}题可用`;
} else {
input.style.borderColor = '#48bb78';
statusElement.className = 'status-text success';
statusElement.textContent = `✓ 已选择${requested}题,共${available}题可用`;
}
}
async startSurvey() {
try {
// 验证学员信息
const studentName = document.getElementById('studentName').value.trim();
const studentSchool = document.getElementById('studentSchool').value.trim();
const studentGrade = document.getElementById('studentGrade').value.trim();
if (!studentName || !studentSchool || !studentGrade) {
this.showError('请填写完整的学员信息(姓名、学校、年级)');
return;
}
// 检查题目是否充足
const canStart = Object.keys(this.currentRules).every(type => {
const needed = this.currentRules[type];
const available = this.filteredQuestions[type]?.length || 0;
return available >= needed;
});
if (!canStart) {
this.showError('题目不足,无法开始答题,请调整配置');
return;
}
// 禁用按钮并显示加载状态
const startBtn = document.getElementById('startSurvey');
const originalText = startBtn.querySelector('.button-text').textContent;
startBtn.querySelector('.button-text').textContent = '正在创建答题会话...';
startBtn.disabled = true;
// 调用后端API创建会话
const response = await fetch('/api/create-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: studentName,
school: studentSchool,
grade: studentGrade,
selectedTag: this.selectedTag,
questionsConfig: this.currentRules
})
});
if (!response.ok) {
throw new Error(`创建会话失败: ${response.status}`);
}
const result = await response.json();
if (result.success) {
// 保存会话信息
this.sessionId = result.sessionId;
this.studentId = result.studentId;
// 跳转到答题页面
window.location.href = `/quiz/${result.sessionId}`;
} else {
throw new Error('创建会话失败');
}
} catch (error) {
console.error('开始答题失败:', error);
this.showError('开始答题失败: ' + error.message);
// 恢复按钮状态
const startBtn = document.getElementById('startSurvey');
startBtn.querySelector('.button-text').textContent = '开始答题';
startBtn.disabled = false;
}
}
renderQuestions() {
const container = document.getElementById('questionsContainer');
container.innerHTML = '';
this.currentQuestions.forEach((question, index) => {
const questionCard = document.createElement('div');
questionCard.className = 'question-card';
questionCard.innerHTML = `
<div class="question-header">
<div class="question-number">${index + 1}</div>
<div class="question-type ${question.questionType}">${question.questionType}</div>
</div>
<div class="question-text">${question['题干']}</div>
<ul class="options-list">
${this.renderOptions(question, index)}
</ul>
`;
container.appendChild(questionCard);
});
// 绑定选项事件
this.bindOptionEvents();
}
renderOptions(question, questionIndex) {
const options = [];
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
// 通过遍历所有键来找到选项
for (let i = 0; i < labels.length; i++) {
let optionText = null;
// 遍历问题的所有键,寻找匹配的选项
for (const key in question) {
// 检查键是否包含当前选项字母(忽略空格数量)
if (key.replace(/\s+/g, '').includes(`选项${labels[i]}`)) {
optionText = question[key];
break;
}
}
if (optionText && optionText.trim()) {
options.push(`
<li class="option-item" data-question="${questionIndex}" data-option="${labels[i]}">
<input type="radio" name="question_${questionIndex}" value="${labels[i]}" id="option_${questionIndex}_${i}">
<label for="option_${questionIndex}_${i}" class="option-text">${labels[i]}. ${optionText}</label>
</li>
`);
}
}
return options.join('');
}
bindOptionEvents() {
document.querySelectorAll('.option-item').forEach(item => {
item.addEventListener('click', function() {
const questionIndex = this.dataset.question;
const option = this.dataset.option;
const radio = this.querySelector('input[type="radio"]');
// 清除同一题目的其他选中状态
document.querySelectorAll(`.option-item[data-question="${questionIndex}"]`).forEach(otherItem => {
otherItem.classList.remove('selected');
});
// 设置当前选中状态
this.classList.add('selected');
radio.checked = true;
// 记录答案
window.survey.answers[questionIndex] = option;
window.survey.checkCompletion();
});
});
}
updateProgress() {
const total = this.currentQuestions.length;
const answered = Object.keys(this.answers).length;
const percentage = total > 0 ? (answered / total) * 100 : 0;
document.getElementById('progressFill').style.width = percentage + '%';
}
checkCompletion() {
this.updateProgress();
const total = this.currentQuestions.length;
const answered = Object.keys(this.answers).length;
document.getElementById('submitBtn').disabled = answered !== total;
}
submitSurvey() {
try {
const results = this.calculateResults();
this.displayResults(results);
document.getElementById('surveyContent').style.display = 'none';
document.getElementById('resultsSection').style.display = 'block';
} catch (error) {
this.showError('提交失败: ' + error.message);
}
}
calculateResults() {
let totalScore = 0;
let correctCount = 0;
const results = [];
this.currentQuestions.forEach((question, index) => {
const userAnswer = this.answers[index];
const correctAnswer = question['答案'];
const score = parseInt(question['分数']) || 0;
const isCorrect = userAnswer === correctAnswer;
if (isCorrect) {
totalScore += score;
correctCount++;
}
results.push({
questionNumber: index + 1,
questionText: question['题干'],
userAnswer: userAnswer || '未作答',
correctAnswer: correctAnswer,
isCorrect: isCorrect,
score: score,
questionType: question.questionType
});
});
return {
totalScore: totalScore,
correctCount: correctCount,
totalQuestions: this.currentQuestions.length,
percentage: Math.round((totalScore / 100) * 100),
results: results
};
}
displayResults(results) {
document.getElementById('scoreValue').textContent = results.totalScore + '分';
let detailsHTML = `
<h3>答题详情</h3>
<p>正确: ${results.correctCount}/${results.totalQuestions} 题</p>
<hr style="margin: 15px 0;">
`;
results.results.forEach(result => {
const statusClass = result.isCorrect ? '✅ 正确' : '❌ 错误';
detailsHTML += `
<div class="result-item">
<strong>第${result.questionNumber}题 (${result.questionType})</strong> - ${statusClass}<br>
<small>题目: ${result.questionText}</small><br>
<small>你的答案: ${result.userAnswer} | 正确答案: ${result.correctAnswer}</small><br>
<small>得分: ${result.isCorrect ? result.score : 0}分</small>
</div>
`;
});
document.getElementById('resultsDetails').innerHTML = detailsHTML;
this.currentResults = results;
}
showReportModal() {
const reportText = this.generateReportText();
document.getElementById('reportContent').innerHTML = this.formatReportAsTable(reportText);
document.getElementById('reportModal').style.display = 'flex';
}
hideReportModal() {
document.getElementById('reportModal').style.display = 'none';
}
generateReportText() {
if (!this.currentResults) return '';
const timestamp = new Date().toLocaleString('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
let report = `# 考试答题情况分析\n\n`;
report += `测评时间: ${timestamp}\n`;
report += `总分: ${this.currentResults.totalScore}\n`;
report += `正确率: ${this.currentResults.correctCount}/${this.currentResults.totalQuestions} (${Math.round((this.currentResults.correctCount/this.currentResults.totalQuestions)*100)}%)\n\n`;
// 添加一些基本信息(可以后续扩展为实际的用户信息表单)
report += `| 题目 | 题型 | 用户答案 | 正确答案 | 是否正确 |\n`;
report += `|------|------|----------|----------|----------|\n`;
report += `| 姓名 | 填空题 | 学员 | 无标准答案 | 无法判断 |\n`;
report += `| 学校 | 填空题 | 学校名称 | 无标准答案 | 无法判断 |\n`;
report += `| 年级 | 填空题 | ${this.selectedTag || '未选择'} | 无标准答案 | 无法判断 |\n`;
// 添加答题情况
this.currentResults.results.forEach(result => {
const status = result.isCorrect ? '正确' : '错误';
report += `| ${result.questionText} | ${result.questionType} | ${result.userAnswer || '未作答'} | ${result.correctAnswer} | ${status} |\n`;
});
return report;
}
formatReportAsTable(reportText) {
const lines = reportText.split('\n');
let html = '';
let inTable = false;
lines.forEach(line => {
if (line.startsWith('# ')) {
html += `<h2>${line.substring(2)}</h2>\n`;
} else if (line.startsWith('## ')) {
html += `<h3>${line.substring(3)}</h3>\n`;
} else if (line.startsWith('|') && line.includes('---')) {
// 表格头
html += `<table class="report-table"><thead><tr>\n`;
const headers = line.split('|').filter(cell => cell.trim()).map(cell => cell.trim());
headers.forEach(header => {
html += `<th>${header}</th>\n`;
});
html += `</tr></thead><tbody>\n`;
inTable = true;
} else if (line.startsWith('|') && inTable) {
// 表格行
html += `<tr>\n`;
const cells = line.split('|').filter(cell => cell !== '').map(cell => cell.trim());
cells.forEach(cell => {
html += `<td>${cell}</td>\n`;
});
html += `</tr>\n`;
} else if (line.trim() === '' && inTable) {
html += `</tbody></table>\n`;
inTable = false;
} else if (line.trim() !== '') {
html += `<p>${line}</p>\n`;
}
});
if (inTable) {
html += `</tbody></table>\n`;
}
return html;
}
copyReport() {
const reportText = this.generateReportText();
navigator.clipboard.writeText(reportText).then(() => {
this.showNotification('报告已复制到剪贴板!');
}).catch(err => {
console.error('复制失败:', err);
this.showNotification('复制失败,请手动复制', 'error');
});
}
downloadReport() {
const reportText = this.generateReportText();
const now = new Date();
const timestamp = new Date(now.toLocaleString("en-US", {timeZone: "Asia/Shanghai"}))
.toISOString().slice(0, 19).replace(/[:-]/g, '');
const filename = `测评报告_${timestamp}.txt`;
const blob = new Blob([reportText], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showNotification(`报告已下载: ${filename}`);
}
showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? 'linear-gradient(135deg, #4CAF50, #66BB6A)' : 'linear-gradient(135deg, #f44336, #e57373)'};
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-size: 14px;
font-weight: 500;
animation: slideIn 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error';
errorDiv.textContent = message;
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.config-section'));
setTimeout(() => {
errorDiv.remove();
}, 5000);
}
}
// 初始化系统
window.survey = new SurveySystem();
</script>
</body>
</html>