fix_login_register

This commit is contained in:
superlishunqin 2025-04-30 16:53:08 +08:00
parent 423730c50a
commit 389e8f0bf8
6 changed files with 2271 additions and 135 deletions

View File

@ -1,72 +1,376 @@
/* 图书表单页面样式 */
/* ========== 基础样式 ========== */
.book-form-container {
padding: 30px;
max-width: 1400px;
margin: 0 auto;
background-color: #f8f9fa;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
}
/* ========== 页头样式 ========== */
.page-header-wrapper {
margin-bottom: 30px;
background: linear-gradient(135deg, #ffffff 0%, #f0f4f8 100%);
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.actions {
.header-title-section {
display: flex;
flex-direction: column;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #2c3e50;
margin: 0;
display: flex;
align-items: center;
}
.page-title i {
margin-right: 12px;
color: #3498db;
font-size: 24px;
}
.subtitle {
margin: 5px 0 0 0;
color: #7f8c8d;
font-size: 16px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.btn-icon-text {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 6px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-icon-text i {
font-size: 14px;
}
/* 进度条样式 */
.form-progress {
min-width: 180px;
}
.progress {
height: 8px;
border-radius: 4px;
background-color: #e9ecef;
margin-bottom: 5px;
overflow: hidden;
}
.progress-bar {
background: linear-gradient(45deg, #3498db, #2ecc71);
transition: width 0.5s ease;
}
.progress-text {
font-size: 12px;
color: #6c757d;
text-align: right;
display: block;
}
/* ========== 表单卡片样式 ========== */
.form-card {
margin-bottom: 25px;
border: none;
border-radius: 10px;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.05);
background-color: #ffffff;
transition: all 0.3s ease;
overflow: hidden;
}
.form-card:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.card-header {
padding: 16px 20px;
background: linear-gradient(90deg, rgba(248,249,250,1) 0%, rgba(255,255,255,1) 100%);
border-bottom: 1px solid #edf2f7;
display: flex;
align-items: center;
}
.card-header-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background-color: rgba(52, 152, 219, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.card-header-icon i {
color: #3498db;
font-size: 16px;
}
.card-header-title {
font-weight: 600;
color: #2c3e50;
font-size: 16px;
}
.card-body {
padding: 25px;
}
.form-section {
padding: 0;
}
/* ========== 表单元素样式 ========== */
.form-label {
font-weight: 500;
color: #34495e;
margin-bottom: 8px;
display: flex;
align-items: center;
}
.label-icon {
color: #3498db;
margin-right: 8px;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
position: relative;
}
.custom-input, .custom-select, .custom-textarea {
height: calc(2.8rem + 2px);
padding: 0.75rem 1rem;
border: 1px solid #e0e6ed;
border-radius: 8px;
font-size: 15px;
transition: all 0.3s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
}
.custom-input:focus, .custom-select:focus, .custom-textarea:focus {
border-color: #3498db;
box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
outline: none;
}
.custom-textarea {
height: auto;
min-height: 200px;
resize: vertical;
line-height: 1.6;
}
.input-group-text {
background-color: #f8f9fa;
border: 1px solid #e0e6ed;
border-radius: 8px;
color: #6c757d;
}
.form-text {
margin-top: 6px;
font-size: 13px;
color: #6c757d;
}
/* 浮动标签效果 */
.form-group.focused .floating-label {
transform: translateY(-22px) scale(0.85);
color: #3498db;
opacity: 1;
}
.floating-label {
position: absolute;
pointer-events: none;
left: 1rem;
top: 0.75rem;
transition: 0.2s ease all;
color: #95a5a6;
opacity: 0.8;
}
.floating-input:focus + .floating-label,
.floating-input:not(:placeholder-shown) + .floating-label {
transform: translateY(-22px) scale(0.85);
color: #3498db;
opacity: 1;
}
.floating-input {
padding-top: 1.1rem;
padding-bottom: 0.4rem;
}
/* 数字输入组 */
.input-number-group {
display: flex;
width: 100%;
}
.input-number-group input {
text-align: center;
border-radius: 0;
border-left: none;
border-right: none;
}
.input-number-group button {
border-radius: 8px 0 0 8px;
}
.input-number-group button:last-child {
border-radius: 0 8px 8px 0;
}
/* 价格滑块 */
.custom-range {
-webkit-appearance: none;
width: 100%;
height: 6px;
border-radius: 3px;
background: #e0e6ed;
outline: none;
margin: 10px 0;
}
.custom-range::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3498db;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: background 0.3s ease;
}
.custom-range::-webkit-slider-thumb:hover {
background: #2980b9;
}
/* 标签输入 */
.tag-input-container {
display: flex;
gap: 10px;
}
.book-form {
margin-bottom: 30px;
.tag-input {
flex-grow: 1;
}
.card {
margin-bottom: 20px;
border: 1px solid rgba(0,0,0,0.125);
border-radius: 0.25rem;
.add-tag-btn {
padding: 0.5rem 0.75rem;
border-radius: 8px;
}
.card-header {
padding: 0.75rem 1.25rem;
background-color: rgba(0,0,0,0.03);
border-bottom: 1px solid rgba(0,0,0,0.125);
font-weight: 600;
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.card-body {
padding: 1.25rem;
.tag-item {
display: inline-flex;
align-items: center;
background-color: rgba(52, 152, 219, 0.1);
color: #3498db;
padding: 6px 12px;
border-radius: 20px;
font-size: 14px;
transition: all 0.3s ease;
}
/* 必填项标记 */
.required {
color: #dc3545;
margin-left: 2px;
.tag-item:hover {
background-color: rgba(52, 152, 219, 0.2);
}
/* 封面预览区域 */
.tag-item .remove-tag {
margin-left: 8px;
cursor: pointer;
font-size: 12px;
color: #3498db;
}
.tag-item .remove-tag:hover {
color: #e74c3c;
}
/* 文本计数 */
.text-count {
font-size: 12px;
color: #7f8c8d;
}
.text-count.text-danger {
color: #e74c3c;
}
/* ========== 封面上传区域 ========== */
.cover-preview-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
gap: 20px;
}
.cover-preview {
width: 100%;
max-width: 200px;
height: 280px;
border: 1px dashed #ccc;
border-radius: 4px;
height: 320px;
border: 2px dashed #e0e6ed;
border-radius: 10px;
overflow: hidden;
background-color: #f8f9fa;
margin-bottom: 10px;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.cover-preview.dragover {
border-color: #3498db;
background-color: rgba(52, 152, 219, 0.05);
}
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
object-fit: contain;
}
.no-cover-placeholder {
@ -76,33 +380,442 @@
flex-direction: column;
justify-content: center;
align-items: center;
color: #aaa;
color: #95a5a6;
padding: 20px;
text-align: center;
}
.no-cover-placeholder i {
font-size: 48px;
margin-bottom: 10px;
font-size: 64px;
margin-bottom: 20px;
color: #d0d0d0;
}
.upload-container {
.placeholder-tip {
font-size: 13px;
margin-top: 10px;
color: #7f8c8d;
}
.upload-options {
width: 100%;
max-width: 200px;
padding: 0 20px;
}
/* 提交按钮容器 */
.upload-btn-group {
display: flex;
gap: 10px;
}
.custom-upload-btn {
flex-grow: 1;
padding: 10px 20px;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
background-color: #3498db;
border-color: #3498db;
color: white;
}
.custom-upload-btn:hover {
background-color: #2980b9;
border-color: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3);
}
.btn-icon {
width: 42px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
/* ========== 表单提交区域 ========== */
.form-submit-container {
margin-top: 30px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.action-buttons {
display: flex;
flex-direction: column;
gap: 15px;
}
.secondary-buttons {
display: flex;
gap: 10px;
}
.submit-btn {
padding: 15px;
border-radius: 8px;
font-weight: 600;
background: linear-gradient(45deg, #3498db, #2980b9);
border: none;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.submit-btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.4);
}
.submit-btn:active {
transform: translateY(-1px);
}
.reset-btn {
padding: 12px;
border-radius: 8px;
font-weight: 500;
}
.form-tip {
margin-top: 15px;
font-size: 13px;
color: #7f8c8d;
text-align: center;
}
.form-tip i {
color: #3498db;
margin-right: 5px;
}
/* 必填项标记 */
.required {
color: #e74c3c;
margin-left: 4px;
font-weight: bold;
}
/* 无效输入状态 */
.is-invalid {
border-color: #e74c3c !important;
}
.invalid-feedback {
display: block;
color: #e74c3c;
font-size: 13px;
margin-top: 5px;
}
/* ========== 模态框样式 ========== */
.modal-content {
border: none;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.modal-header {
background-color: #f8f9fa;
border-bottom: 1px solid #edf2f7;
padding: 15px 20px;
}
.modal-title {
font-weight: 600;
color: #2c3e50;
}
.modal-body {
padding: 25px;
}
.modal-footer {
border-top: 1px solid #edf2f7;
padding: 15px 20px;
justify-content: space-between;
}
/* 裁剪模态框 */
.img-container {
max-height: 500px;
overflow: hidden;
}
#cropperImage {
display: block;
max-width: 100%;
}
.cropper-controls {
display: flex;
gap: 10px;
}
/* 图书预览模态框 */
.book-preview-container {
padding: 10px;
}
.book-preview-cover {
width: 100%;
height: 300px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
background-color: #f8f9fa;
margin-bottom: 20px;
}
.book-preview-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.book-preview-details {
padding: 10px 0;
}
.book-preview-details h3 {
margin: 0 0 5px 0;
font-weight: 600;
color: #2c3e50;
}
.book-author {
color: #7f8c8d;
font-size: 16px;
margin-bottom: 20px;
}
.book-info-section {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.book-info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 13px;
color: #7f8c8d;
margin-bottom: 3px;
}
.info-value {
font-weight: 500;
color: #34495e;
}
.book-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
}
.preview-tag {
display: inline-block;
background-color: rgba(52, 152, 219, 0.1);
color: #3498db;
padding: 5px 10px;
border-radius: 20px;
font-size: 13px;
}
.book-description {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
}
.book-description h4 {
margin-top: 0;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
}
.book-description p {
color: #34495e;
line-height: 1.6;
margin: 0;
}
/* ========== 自定义通知样式 ========== */
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
max-width: 350px;
}
.custom-notification {
background: white;
border-radius: 10px;
padding: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
animation-duration: 0.5s;
border-left: 4px solid #3498db;
}
.notification-success {
border-color: #2ecc71;
}
.notification-error {
border-color: #e74c3c;
}
.notification-warning {
border-color: #f39c12;
}
.notification-info {
border-color: #3498db;
}
.notification-icon {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
}
.notification-success .notification-icon i {
color: #2ecc71;
}
.notification-error .notification-icon i {
color: #e74c3c;
}
.notification-warning .notification-icon i {
color: #f39c12;
}
.notification-info .notification-icon i {
color: #3498db;
}
.notification-content {
flex-grow: 1;
}
.notification-content p {
margin: 0;
font-size: 14px;
color: #2c3e50;
}
.notification-close {
background: none;
border: none;
color: #bdc3c7;
cursor: pointer;
padding: 5px;
transition: color 0.3s ease;
}
.notification-close:hover {
color: #7f8c8d;
}
/* ========== 动画效果 ========== */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(52, 152, 219, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}
.pulse {
animation: pulse 2s infinite;
}
.pulse-icon {
animation: pulse 2s infinite;
}
/* ========== 响应式样式 ========== */
@media (max-width: 1200px) {
.book-form-container {
padding: 20px;
}
.cover-preview {
height: 280px;
}
}
@media (max-width: 992px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.actions {
.header-actions {
width: 100%;
justify-content: space-between;
}
.book-info-section {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.book-form-container {
padding: 15px 10px;
}
.page-title {
font-size: 24px;
}
.subtitle {
font-size: 14px;
}
.card-body {
padding: 20px 15px;
}
.cover-preview {
height: 240px;
}
.secondary-buttons {
flex-direction: column;
}
.form-row {
margin-right: -5px;
margin-left: -5px;
}
.form-group {
padding-right: 5px;
padding-left: 5px;
}
}

394
app/static/css/login.css Normal file
View File

@ -0,0 +1,394 @@
/* login.css - 登录页面专用样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
:root {
--primary-color: #4a89dc;
--primary-hover: #3b78c4;
--secondary-color: #5cb85c;
--text-color: #333;
--light-text: #666;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #ddd;
--error-color: #e74c3c;
--success-color: #2ecc71;
}
body.dark-mode {
--primary-color: #5a9aed;
--primary-hover: #4a89dc;
--secondary-color: #6bc76b;
--text-color: #f1f1f1;
--light-text: #aaa;
--bg-color: #1a1a1a;
--card-bg: #2c2c2c;
--border-color: #444;
}
body {
background-color: var(--bg-color);
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
min-height: 100vh;
color: var(--text-color);
transition: all 0.3s ease;
}
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
cursor: pointer;
padding: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.overlay {
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
.main-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 20px;
}
.login-container {
background-color: var(--card-bg);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
width: 450px;
padding: 35px;
position: relative;
overflow: hidden;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.logo {
text-align: center;
margin-bottom: 25px;
position: relative;
}
.logo img {
width: 90px;
height: 90px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 5px;
background-color: #fff;
transition: transform 0.3s ease;
}
h1 {
text-align: center;
color: var(--text-color);
margin-bottom: 10px;
font-weight: 600;
font-size: 28px;
}
.subtitle {
text-align: center;
color: var(--light-text);
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-color);
font-weight: 500;
font-size: 14px;
}
.input-with-icon {
position: relative;
}
.input-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--light-text);
}
.form-control {
width: 100%;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px 0 45px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.2);
outline: none;
}
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: var(--light-text);
}
.validation-message {
margin-top: 6px;
font-size: 12px;
color: var(--error-color);
display: none;
}
.validation-message.show {
display: block;
animation: shake 0.5s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.remember-forgot {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.custom-checkbox {
position: relative;
padding-left: 30px;
cursor: pointer;
font-size: 14px;
user-select: none;
color: var(--light-text);
}
.custom-checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 18px;
width: 18px;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 3px;
transition: all 0.2s ease;
}
.custom-checkbox:hover input ~ .checkmark {
border-color: var(--primary-color);
}
.custom-checkbox input:checked ~ .checkmark {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.custom-checkbox input:checked ~ .checkmark:after {
display: block;
}
.custom-checkbox .checkmark:after {
left: 6px;
top: 2px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.forgot-password a {
color: var(--primary-color);
text-decoration: none;
font-size: 14px;
transition: color 0.3s ease;
}
.forgot-password a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.btn-login {
width: 100%;
height: 48px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-login:hover {
background-color: var(--primary-hover);
}
.btn-login:active {
transform: scale(0.98);
}
.btn-login .loading {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.btn-login.loading-state {
color: transparent;
}
.btn-login.loading-state .loading {
display: block;
}
.signup {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: var(--light-text);
}
.signup a {
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.signup a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.features {
display: flex;
justify-content: center;
margin-top: 25px;
gap: 30px;
}
.feature-item {
text-align: center;
font-size: 12px;
color: var(--light-text);
display: flex;
flex-direction: column;
align-items: center;
}
.feature-icon {
margin-bottom: 5px;
font-size: 18px;
}
footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
}
footer a {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
}
.alert {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
color: #721c24;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
@media (max-width: 576px) {
.login-container {
width: 100%;
padding: 25px;
border-radius: 0;
}
.theme-toggle {
top: 10px;
}
.logo img {
width: 70px;
height: 70px;
}
h1 {
font-size: 22px;
}
.main-container {
padding: 0;
}
}

342
app/static/css/register.css Normal file
View File

@ -0,0 +1,342 @@
/* register.css - 注册页面专用样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
:root {
--primary-color: #4a89dc;
--primary-hover: #3b78c4;
--secondary-color: #5cb85c;
--text-color: #333;
--light-text: #666;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #ddd;
--error-color: #e74c3c;
--success-color: #2ecc71;
}
body.dark-mode {
--primary-color: #5a9aed;
--primary-hover: #4a89dc;
--secondary-color: #6bc76b;
--text-color: #f1f1f1;
--light-text: #aaa;
--bg-color: #1a1a1a;
--card-bg: #2c2c2c;
--border-color: #444;
}
body {
background-color: var(--bg-color);
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
min-height: 100vh;
color: var(--text-color);
transition: all 0.3s ease;
}
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
cursor: pointer;
padding: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.overlay {
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
.main-container {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 20px;
}
.login-container {
background-color: var(--card-bg);
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
width: 450px;
padding: 35px;
position: relative;
overflow: hidden;
animation: fadeIn 0.5s ease;
}
.register-container {
width: 500px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.logo {
text-align: center;
margin-bottom: 25px;
position: relative;
}
.logo img {
width: 90px;
height: 90px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 5px;
background-color: #fff;
transition: transform 0.3s ease;
}
h1 {
text-align: center;
color: var(--text-color);
margin-bottom: 10px;
font-weight: 600;
font-size: 28px;
}
.subtitle {
text-align: center;
color: var(--light-text);
margin-bottom: 30px;
font-size: 14px;
}
.form-group {
margin-bottom: 22px;
position: relative;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-color);
font-weight: 500;
font-size: 14px;
}
.input-with-icon {
position: relative;
}
.input-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--light-text);
}
.form-control {
width: 100%;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px 0 45px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(74, 137, 220, 0.2);
outline: none;
}
.password-toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: var(--light-text);
}
.validation-message {
margin-top: 6px;
font-size: 12px;
color: var(--error-color);
display: none;
}
.validation-message.show {
display: block;
animation: shake 0.5s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
.btn-login {
width: 100%;
height: 48px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn-login:hover {
background-color: var(--primary-hover);
}
.btn-login:active {
transform: scale(0.98);
}
.btn-login .loading {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.btn-login.loading-state {
color: transparent;
}
.btn-login.loading-state .loading {
display: block;
}
.signup {
text-align: center;
margin-top: 25px;
font-size: 14px;
color: var(--light-text);
}
.signup a {
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.signup a:hover {
color: var(--primary-hover);
text-decoration: underline;
}
.alert {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
color: #721c24;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
}
.verification-code-container {
display: flex;
gap: 10px;
}
.verification-input {
flex: 1;
height: 48px;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0 15px;
font-size: 15px;
transition: all 0.3s ease;
background-color: var(--card-bg);
color: var(--text-color);
}
.send-code-btn {
padding: 0 15px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
transition: all 0.3s ease;
}
.send-code-btn:hover {
background-color: var(--primary-hover);
}
.send-code-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
}
footer a {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
}
@media (max-width: 576px) {
.login-container, .register-container {
width: 100%;
padding: 25px;
border-radius: 0;
}
.theme-toggle {
top: 10px;
}
.logo img {
width: 70px;
height: 70px;
}
h1 {
font-size: 22px;
}
.main-container {
padding: 0;
}
.verification-code-container {
flex-direction: column;
}
}

View File

@ -4,152 +4,839 @@
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/book-form.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css">
{% endblock %}
{% block content %}
<div class="book-form-container">
<div class="page-header">
<h1>添加新图书</h1>
<a href="{{ url_for('book.book_list') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> 返回列表
</a>
<div class="book-form-container animate__animated animate__fadeIn">
<!-- 顶部导航和标题区域 -->
<div class="page-header-wrapper">
<div class="page-header">
<div class="header-title-section">
<h1 class="page-title"><i class="fas fa-book-medical pulse-icon"></i> 添加新图书</h1>
<p class="subtitle">创建新书籍记录并添加到系统库存</p>
</div>
<div class="header-actions">
<a href="{{ url_for('book.book_list') }}" class="btn btn-light btn-icon-text">
<i class="fas fa-arrow-left"></i>
<span>返回列表</span>
</a>
<div class="form-progress">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" id="formProgress"></div>
</div>
<span class="progress-text" id="progressText">完成 0%</span>
</div>
</div>
</div>
</div>
<form method="POST" enctype="multipart/form-data" class="book-form">
<!-- 主表单区域 -->
<form method="POST" enctype="multipart/form-data" class="book-form" id="bookForm">
<div class="form-row">
<div class="col-md-8">
<div class="card">
<div class="card-header">基本信息</div>
<!-- 左侧表单区域 -->
<div class="col-lg-8">
<!-- 基本信息卡片 -->
<div class="card form-card">
<div class="card-header">
<div class="card-header-icon">
<i class="fas fa-info-circle"></i>
</div>
<div class="card-header-title">基本信息</div>
</div>
<div class="card-body">
<div class="form-row">
<div class="form-group col-md-12">
<label for="title">书名 <span class="required">*</span></label>
<input type="text" class="form-control" id="title" name="title" required>
<div class="form-section">
<div class="form-row">
<div class="form-group col-md-12">
<label for="title" class="form-label">
<i class="fas fa-heading label-icon"></i>书名
<span class="required">*</span>
</label>
<input type="text" class="form-control custom-input floating-input" id="title" name="title" required>
<span class="floating-label">请输入完整图书名称</span>
<div class="form-text">完整准确的书名有助于读者查找</div>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="author">作者 <span class="required">*</span></label>
<input type="text" class="form-control" id="author" name="author" required>
<div class="form-row">
<div class="form-group col-md-6">
<label for="author" class="form-label">
<i class="fas fa-user-edit label-icon"></i>作者
<span class="required">*</span>
</label>
<input type="text" class="form-control custom-input floating-input" id="author" name="author" required>
<span class="floating-label">作者姓名</span>
</div>
<div class="form-group col-md-6">
<label for="publisher" class="form-label">
<i class="fas fa-building label-icon"></i>出版社
</label>
<input type="text" class="form-control custom-input floating-input" id="publisher" name="publisher">
<span class="floating-label">出版社名称</span>
</div>
</div>
<div class="form-group col-md-6">
<label for="publisher">出版社</label>
<input type="text" class="form-control" id="publisher" name="publisher">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="isbn">ISBN</label>
<input type="text" class="form-control" id="isbn" name="isbn">
</div>
<div class="form-group col-md-6">
<label for="publish_year">出版年份</label>
<input type="text" class="form-control" id="publish_year" name="publish_year">
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="category_id">分类</label>
<select class="form-control" id="category_id" name="category_id">
<option value="">未分类</option>
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-md-6">
<label for="tags">标签</label>
<input type="text" class="form-control" id="tags" name="tags" placeholder="多个标签用逗号分隔">
<div class="form-row">
<div class="form-group col-md-6">
<label for="isbn" class="form-label">
<i class="fas fa-barcode label-icon"></i>ISBN
</label>
<div class="input-group">
<input type="text" class="form-control custom-input floating-input" id="isbn" name="isbn">
<span class="floating-label">国际标准图书编号</span>
<div class="input-group-append">
<button class="btn btn-primary isbn-lookup-btn" type="button" id="isbnLookup">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="form-text">输入ISBN并点击查询按钮自动填充图书信息</div>
</div>
<div class="form-group col-md-6">
<label for="publish_year" class="form-label">
<i class="fas fa-calendar-alt label-icon"></i>出版年份
</label>
<input type="text" class="form-control custom-input floating-input" id="publish_year" name="publish_year">
<span class="floating-label">2023</span>
</div>
</div>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">图书简介</div>
<!-- 分类和标签卡片 -->
<div class="card form-card">
<div class="card-header">
<div class="card-header-icon">
<i class="fas fa-tags"></i>
</div>
<div class="card-header-title">分类与标签</div>
</div>
<div class="card-body">
<div class="form-group">
<textarea class="form-control" id="description" name="description" rows="8" placeholder="请输入图书简介"></textarea>
<div class="form-section">
<div class="form-row">
<div class="form-group col-md-6">
<label for="category_id" class="form-label">
<i class="fas fa-folder label-icon"></i>图书分类
</label>
<select class="form-control custom-select select2" id="category_id" name="category_id">
<option value="">选择分类...</option>
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}
</select>
<div class="form-text">为图书选择合适的分类以便于管理和查找</div>
</div>
<div class="form-group col-md-6">
<label for="tags" class="form-label">
<i class="fas fa-tag label-icon"></i>标签
</label>
<div class="tag-input-container">
<input type="text" class="form-control custom-input tag-input" id="tagInput" placeholder="输入标签后按回车添加">
<input type="hidden" id="tags" name="tags">
<button type="button" class="btn btn-sm btn-primary add-tag-btn">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="tags-container" id="tagsContainer"></div>
<div class="form-text">添加多个标签以提高图书的检索率</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图书简介卡片 -->
<div class="card form-card">
<div class="card-header">
<div class="card-header-icon">
<i class="fas fa-align-left"></i>
</div>
<div class="card-header-title">图书简介</div>
</div>
<div class="card-body">
<div class="form-section">
<div class="form-group">
<label for="description" class="form-label">
<i class="fas fa-file-alt label-icon"></i>内容简介
</label>
<textarea class="form-control custom-textarea" id="description" name="description" rows="8" placeholder="请输入图书的简要介绍..."></textarea>
<div class="form-text">
<div class="d-flex justify-content-between">
<span>简要描述图书的内容、特点和主要观点</span>
<span class="text-count"><span id="charCount">0</span>/2000</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">封面图片</div>
<!-- 右侧表单区域 -->
<div class="col-lg-4">
<!-- 封面图片卡片 -->
<div class="card form-card">
<div class="card-header">
<div class="card-header-icon">
<i class="fas fa-image"></i>
</div>
<div class="card-header-title">封面图片</div>
</div>
<div class="card-body">
<div class="cover-preview-container">
<div class="cover-preview" id="coverPreview">
<div class="no-cover-placeholder">
<i class="fas fa-image"></i>
<i class="fas fa-book-open"></i>
<span>暂无封面</span>
<p class="placeholder-tip">点击上传或拖放图片至此处</p>
</div>
</div>
<div class="upload-container">
<label for="cover" class="btn btn-outline-primary btn-block">
<i class="fas fa-upload"></i> 上传封面
</label>
<div class="upload-options">
<div class="upload-btn-group">
<label for="cover" class="btn btn-primary custom-upload-btn">
<i class="fas fa-upload"></i> 上传图片
</label>
<button type="button" class="btn btn-outline-secondary btn-icon" id="removeCover">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<input type="file" id="cover" name="cover" class="form-control-file" accept="image/*" style="display:none;">
</div>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">库存和价格</div>
<div class="card-body">
<div class="form-group">
<label for="stock">库存数量</label>
<input type="number" class="form-control" id="stock" name="stock" min="0" value="0">
</div>
<div class="form-group">
<label for="price">价格</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">¥</span>
<div class="form-text text-center mt-2">
<small>推荐尺寸: 500×700px (竖版封面)</small><br>
<small>支持格式: JPG, PNG, WebP</small>
</div>
<input type="number" class="form-control" id="price" name="price" step="0.01" min="0">
</div>
</div>
</div>
</div>
<!-- 库存和价格卡片 -->
<div class="card form-card">
<div class="card-header">
<div class="card-header-icon">
<i class="fas fa-cubes"></i>
</div>
<div class="card-header-title">库存和价格</div>
</div>
<div class="card-body">
<div class="form-section">
<div class="form-group">
<label for="stock" class="form-label">
<i class="fas fa-layer-group label-icon"></i>库存数量
</label>
<div class="input-number-group">
<button type="button" class="btn btn-outline-secondary input-number-decrement">
<i class="fas fa-minus"></i>
</button>
<input type="number" class="form-control custom-input text-center" id="stock" name="stock" min="0" value="0">
<button type="button" class="btn btn-outline-secondary input-number-increment">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="price" class="form-label">
<i class="fas fa-yen-sign label-icon"></i>价格
</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">¥</span>
</div>
<input type="number" class="form-control custom-input" id="price" name="price" step="0.01" min="0" placeholder="0.00">
</div>
<div class="price-slider mt-3">
<input type="range" class="custom-range" id="priceRange" min="0" max="500" step="0.5" value="0">
<div class="d-flex justify-content-between">
<small>¥0</small>
<small>¥250</small>
<small>¥500</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 提交按钮区域 -->
<div class="form-submit-container">
<button type="submit" class="btn btn-primary btn-lg btn-block">
<i class="fas fa-save"></i> 保存图书
</button>
<div class="action-buttons">
<button type="submit" class="btn btn-primary btn-lg btn-block submit-btn pulse">
<i class="fas fa-save"></i> 保存图书
</button>
<div class="secondary-buttons">
<button type="button" class="btn btn-outline-secondary btn-block" id="previewBtn">
<i class="fas fa-eye"></i> 预览
</button>
<button type="reset" class="btn btn-outline-danger btn-block reset-btn">
<i class="fas fa-undo"></i> 重置
</button>
</div>
</div>
<div class="form-tip">
<i class="fas fa-info-circle"></i>
<span><span class="required">*</span> 的字段为必填项</span>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- 图片裁剪模态框 -->
<div class="modal fade" id="cropperModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">调整封面图片</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="cropperImage" src="" alt="图片预览">
</div>
</div>
<div class="modal-footer">
<div class="cropper-controls">
<div class="btn-group mr-2">
<button type="button" class="btn btn-outline-secondary" id="rotateLeft">
<i class="fas fa-undo"></i>
</button>
<button type="button" class="btn btn-outline-secondary" id="rotateRight">
<i class="fas fa-redo"></i>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" id="zoomIn">
<i class="fas fa-search-plus"></i>
</button>
<button type="button" class="btn btn-outline-secondary" id="zoomOut">
<i class="fas fa-search-minus"></i>
</button>
</div>
</div>
<div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="cropImage">应用裁剪</button>
</div>
</div>
</div>
</div>
</div>
<!-- 图书预览模态框 -->
<div class="modal fade" id="previewModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">图书预览</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="book-preview-container">
<div class="row">
<div class="col-md-4">
<div class="book-preview-cover" id="previewCover">
<div class="no-cover-placeholder">
<i class="fas fa-book-open"></i>
<span>暂无封面</span>
</div>
</div>
</div>
<div class="col-md-8">
<div class="book-preview-details">
<h3 id="previewTitle">书名加载中...</h3>
<p class="book-author" id="previewAuthor">作者加载中...</p>
<div class="book-info-section">
<div class="book-info-item">
<span class="info-label">出版社:</span>
<span class="info-value" id="previewPublisher">-</span>
</div>
<div class="book-info-item">
<span class="info-label">ISBN:</span>
<span class="info-value" id="previewISBN">-</span>
</div>
<div class="book-info-item">
<span class="info-label">出版年份:</span>
<span class="info-value" id="previewYear">-</span>
</div>
<div class="book-info-item">
<span class="info-label">分类:</span>
<span class="info-value" id="previewCategory">-</span>
</div>
<div class="book-info-item">
<span class="info-label">价格:</span>
<span class="info-value" id="previewPrice">-</span>
</div>
<div class="book-info-item">
<span class="info-label">库存:</span>
<span class="info-value" id="previewStock">-</span>
</div>
</div>
<div class="book-tags" id="previewTags"></div>
</div>
</div>
</div>
<div class="book-description" id="previewDescription">
<h4>图书简介</h4>
<p>简介加载中...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" id="continueEditBtn">继续编辑</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
<script>
$(document).ready(function() {
// 封面预览
$('#cover').change(function() {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
$('#coverPreview').html(`<img src="${e.target.result}" class="cover-image">`);
}
reader.readAsDataURL(file);
// 初始化Select2
$('.select2').select2({
placeholder: "选择分类...",
allowClear: true,
theme: "classic"
});
// 浮动标签效果
$('.floating-input').on('focus blur', function(e) {
$(this).parents('.form-group').toggleClass('focused', (e.type === 'focus' || this.value.length > 0));
}).trigger('blur');
// 表单进度条
updateFormProgress();
$('input, textarea, select').on('change keyup', function() {
updateFormProgress();
});
function updateFormProgress() {
const requiredFields = $('[required]');
const filledFields = requiredFields.filter(function() {
return $(this).val() !== '';
});
const otherFields = $('input:not([required]), textarea:not([required]), select:not([required])').not('[type="file"]');
const filledOtherFields = otherFields.filter(function() {
return $(this).val() !== '';
});
let requiredWeight = 70; // 必填字段权重70%
let otherWeight = 30; // 非必填字段权重30%
let requiredProgress = requiredFields.length ? (filledFields.length / requiredFields.length) * requiredWeight : requiredWeight;
let otherProgress = otherFields.length ? (filledOtherFields.length / otherFields.length) * otherWeight : 0;
let totalProgress = Math.floor(requiredProgress + otherProgress);
$('#formProgress').css('width', totalProgress + '%').attr('aria-valuenow', totalProgress);
$('#progressText').text('完成 ' + totalProgress + '%');
if (totalProgress >= 100) {
$('.submit-btn').addClass('pulse');
} else {
$('#coverPreview').html(`
$('.submit-btn').removeClass('pulse');
}
}
// 封面图片处理
let cropper;
let coverBlob;
const coverPreview = $('#coverPreview');
const coverInput = $('#cover');
// 文件选择处理
coverInput.on('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
// 打开模态框并初始化裁剪
$('#cropperImage').attr('src', e.target.result);
$('#cropperModal').modal('show');
// 模态框显示后初始化Cropper
$('#cropperModal').on('shown.bs.modal', function() {
if (cropper) {
cropper.destroy();
}
cropper = new Cropper(document.getElementById('cropperImage'), {
aspectRatio: 5 / 7,
viewMode: 2,
responsive: true,
guides: true,
background: true
});
});
};
reader.readAsDataURL(file);
});
// 裁剪控制
$('#rotateLeft').on('click', function() {
cropper.rotate(-90);
});
$('#rotateRight').on('click', function() {
cropper.rotate(90);
});
$('#zoomIn').on('click', function() {
cropper.zoom(0.1);
});
$('#zoomOut').on('click', function() {
cropper.zoom(-0.1);
});
// 应用裁剪
$('#cropImage').on('click', function() {
const canvas = cropper.getCroppedCanvas({
width: 500,
height: 700,
fillColor: '#fff',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
});
canvas.toBlob(function(blob) {
const url = URL.createObjectURL(blob);
coverPreview.html(`<img src="${url}" class="cover-image" alt="图书封面">`);
coverBlob = blob;
// 模拟File对象
const fileList = new DataTransfer();
const file = new File([blob], "cover.jpg", {type: "image/jpeg"});
fileList.items.add(file);
document.getElementById('cover').files = fileList.files;
$('#cropperModal').modal('hide');
}, 'image/jpeg', 0.95);
});
// 移除封面
$('#removeCover').on('click', function() {
coverPreview.html(`
<div class="no-cover-placeholder">
<i class="fas fa-book-open"></i>
<span>暂无封面</span>
<p class="placeholder-tip">点击上传或拖放图片至此处</p>
</div>
`);
coverInput.val('');
coverBlob = null;
});
// 拖放上传功能
coverPreview.on('dragover', function(e) {
e.preventDefault();
$(this).addClass('dragover');
}).on('dragleave drop', function(e) {
e.preventDefault();
$(this).removeClass('dragover');
}).on('drop', function(e) {
e.preventDefault();
const file = e.originalEvent.dataTransfer.files[0];
if (file && file.type.match('image.*')) {
coverInput[0].files = e.originalEvent.dataTransfer.files;
coverInput.trigger('change');
}
}).on('click', function() {
if (!$(this).find('img').length) {
coverInput.click();
}
});
// 标签处理
const tagInput = $('#tagInput');
const tagsContainer = $('#tagsContainer');
const tagsHiddenInput = $('#tags');
let tags = [];
function renderTags() {
tagsContainer.empty();
tags.forEach(tag => {
tagsContainer.append(`
<span class="tag-item">
${tag}
<i class="fas fa-times remove-tag" data-tag="${tag}"></i>
</span>
`);
});
tagsHiddenInput.val(tags.join(','));
}
function addTag() {
const tag = tagInput.val().trim();
if (tag && !tags.includes(tag)) {
tags.push(tag);
renderTags();
tagInput.val('').focus();
}
}
tagInput.on('keydown', function(e) {
if (e.key === 'Enter' || e.key === ',') {
e.preventDefault();
addTag();
}
});
$('.add-tag-btn').on('click', addTag);
$(document).on('click', '.remove-tag', function() {
const tagToRemove = $(this).data('tag');
tags = tags.filter(t => t !== tagToRemove);
renderTags();
});
// 数字输入控制
$('.input-number-decrement').on('click', function() {
const input = $(this).siblings('input');
const value = parseInt(input.val());
if (value > parseInt(input.attr('min'))) {
input.val(value - 1).trigger('change');
}
});
$('.input-number-increment').on('click', function() {
const input = $(this).siblings('input');
const value = parseInt(input.val());
input.val(value + 1).trigger('change');
});
// 价格滑块联动
$('#priceRange').on('input', function() {
$('#price').val($(this).val());
});
$('#price').on('input', function() {
const value = parseFloat($(this).val()) || 0;
$('#priceRange').val(Math.min(value, 500));
});
// 字符计数
$('#description').on('input', function() {
const count = $(this).val().length;
$('#charCount').text(count);
if (count > 2000) {
$('#charCount').addClass('text-danger');
} else {
$('#charCount').removeClass('text-danger');
}
});
// ISBN查询模拟
$('#isbnLookup').on('click', function() {
const isbn = $('#isbn').val().trim();
if (!isbn) return;
$(this).html('<i class="fas fa-spinner fa-spin"></i>');
// 模拟API查询延迟
setTimeout(() => {
// 模拟查到的数据
if (isbn === '9787020002207') {
$('#title').val('红楼梦').trigger('blur');
$('#author').val('曹雪芹').trigger('blur');
$('#publisher').val('人民文学出版社').trigger('blur');
$('#publish_year').val('1996').trigger('blur');
$('#category_id').val('1').trigger('change');
tags = ['中国文学', '古典', '名著'];
renderTags();
$('#description').val('《红楼梦》是中国古代章回体长篇小说中国古典四大名著之一通行本共120回一般认为前80回是清代作家曹雪芹所著后40回作者有争议。小说以贾、史、王、薛四大家族的兴衰为背景以贾府的家庭琐事、闺阁闲情为脉络以贾宝玉、林黛玉、薛宝钗的爱情婚姻悲剧为主线刻画了以贾宝玉和金陵十二钗为中心的正邪两赋有情人的人性美和悲剧美。').trigger('input');
$('#price').val('59.70').trigger('input');
// 显示成功通知
showNotification('ISBN查询成功', 'success');
} else {
showNotification('未找到相关图书信息', 'warning');
}
$(this).html('<i class="fas fa-search"></i>');
}, 1500);
});
// 表单预览
$('#previewBtn').on('click', function() {
// 填充预览内容
$('#previewTitle').text($('#title').val() || '未填写标题');
$('#previewAuthor').text($('#author').val() || '未填写作者');
$('#previewPublisher').text($('#publisher').val() || '-');
$('#previewISBN').text($('#isbn').val() || '-');
$('#previewYear').text($('#publish_year').val() || '-');
// 获取分类文本
const categoryId = $('#category_id').val();
const categoryText = categoryId ? $('#category_id option:selected').text() : '-';
$('#previewCategory').text(categoryText);
// 价格和库存
const price = parseFloat($('#price').val());
$('#previewPrice').text(price ? '¥' + price.toFixed(2) : '-');
$('#previewStock').text($('#stock').val() || '0');
// 标签
const previewTags = $('#previewTags');
previewTags.empty();
tags.forEach(tag => {
previewTags.append(`<span class="preview-tag">${tag}</span>`);
});
// 描述
const description = $('#description').val();
if (description) {
$('#previewDescription').html(`
<h4>图书简介</h4>
<p>${description.replace(/\n/g, '<br>')}</p>
`);
} else {
$('#previewDescription').html(`
<h4>图书简介</h4>
<p class="text-muted">未填写图书简介</p>
`);
}
// 封面
if ($('#coverPreview img').length) {
const coverSrc = $('#coverPreview img').attr('src');
$('#previewCover').html(`<img src="${coverSrc}" class="img-fluid" alt="封面预览">`);
} else {
$('#previewCover').html(`
<div class="no-cover-placeholder">
<i class="fas fa-image"></i>
<i class="fas fa-book-open"></i>
<span>暂无封面</span>
</div>
`);
}
// 显示预览模态框
$('#previewModal').modal('show');
});
// 表单验证
$('#bookForm').on('submit', function(e) {
let isValid = true;
$('[required]').each(function() {
if (!$(this).val().trim()) {
isValid = false;
$(this).addClass('is-invalid');
// 添加错误提示
if (!$(this).next('.invalid-feedback').length) {
$(this).after(`<div class="invalid-feedback">此字段不能为空</div>`);
}
} else {
$(this).removeClass('is-invalid').next('.invalid-feedback').remove();
}
});
if (!isValid) {
e.preventDefault();
// 滚动到第一个错误字段
$('html, body').animate({
scrollTop: $('.is-invalid:first').offset().top - 100
}, 500);
showNotification('请填写所有必填字段', 'error');
} else {
showNotification('表单提交中...', 'info');
}
});
// 表单重置
$('.reset-btn').on('click', function() {
if (confirm('确定要重置表单吗?所有已填写的内容将被清空。')) {
$('#bookForm')[0].reset();
$('.floating-input').trigger('blur');
$('#coverPreview').html(`
<div class="no-cover-placeholder">
<i class="fas fa-book-open"></i>
<span>暂无封面</span>
<p class="placeholder-tip">点击上传或拖放图片至此处</p>
</div>
`);
tags = [];
renderTags();
updateFormProgress();
$('.select2').val(null).trigger('change');
$('#charCount').text('0');
showNotification('表单已重置', 'info');
}
});
// 输入时移除错误样式
$('input, textarea, select').on('input change', function() {
$(this).removeClass('is-invalid').next('.invalid-feedback').remove();
});
// 通知提示函数
function showNotification(message, type) {
// 创建通知元素
const notification = $(`
<div class="custom-notification notification-${type} animate__animated animate__fadeInRight">
<div class="notification-icon">
<i class="fas ${getIconForType(type)}"></i>
</div>
<div class="notification-content">
<p>${message}</p>
</div>
<button class="notification-close">
<i class="fas fa-times"></i>
</button>
</div>
`);
// 添加到页面
if ($('.notification-container').length === 0) {
$('body').append('<div class="notification-container"></div>');
}
$('.notification-container').append(notification);
// 自动关闭
setTimeout(() => {
notification.removeClass('animate__fadeInRight').addClass('animate__fadeOutRight');
setTimeout(() => {
notification.remove();
}, 500);
}, 5000);
// 点击关闭
notification.find('.notification-close').on('click', function() {
notification.removeClass('animate__fadeInRight').addClass('animate__fadeOutRight');
setTimeout(() => {
notification.remove();
}, 500);
});
}
function getIconForType(type) {
switch(type) {
case 'success': return 'fa-check-circle';
case 'warning': return 'fa-exclamation-triangle';
case 'error': return 'fa-times-circle';
case 'info':
default: return 'fa-info-circle';
}
}
});
</script>
{% endblock %}

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录 - 图书管理系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
</head>
<body>
<div class="overlay"></div>
@ -84,9 +84,9 @@
</div>
<footer>
<p>© 2025 图书管理系统 - 版权所有</p>
<p>© 施琦图书管理系统 - 版权所有</p>
</footer>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
</html>

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册 - 图书管理系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/register.css') }}">
</head>
<body>
<div class="overlay"></div>
@ -85,7 +85,7 @@
</div>
<footer>
<p>© 2025 图书管理系统 - 版权所有</p>
<p>© 施琦图书管理系统 - 版权所有</p>
</footer>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>