|
|
@@ -0,0 +1,10627 @@
|
|
|
+<!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;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
|
+ background: #f0f2f5;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 顶部工具栏 ========== */
|
|
|
+ .top-bar {
|
|
|
+ height: 64px;
|
|
|
+ background: white;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 0 24px;
|
|
|
+ position: relative;
|
|
|
+ z-index: 100;
|
|
|
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.06);
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-bar-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-menu-wrapper {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-btn {
|
|
|
+ height: 36px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ background: #f9fafb;
|
|
|
+ cursor: pointer;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-btn:hover {
|
|
|
+ background: #fff;
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 4px 10px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-img {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ object-fit: contain;
|
|
|
+ }
|
|
|
+
|
|
|
+ .logo-caret {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown {
|
|
|
+ position: absolute;
|
|
|
+ top: 44px;
|
|
|
+ left: 0;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ box-shadow: 0 12px 30px rgba(0,0,0,0.12);
|
|
|
+ border-radius: 12px;
|
|
|
+ min-width: 160px;
|
|
|
+ padding: 6px 0;
|
|
|
+ display: none;
|
|
|
+ z-index: 200;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown.active {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #111827;
|
|
|
+ transition: all 0.15s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown-item.danger {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown-item.danger:hover {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #b91c1c;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-dropdown-sep {
|
|
|
+ height: 1px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ margin: 4px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .course-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: text;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .course-title:hover {
|
|
|
+ border-color: #e5e7eb;
|
|
|
+ background: #f9fafb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .course-title:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .auto-save {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .save-dot {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #10b981;
|
|
|
+ animation: pulse 2s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse {
|
|
|
+ 0%, 100% { opacity: 1; }
|
|
|
+ 50% { opacity: 0.5; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-bar-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-btn {
|
|
|
+ height: 40px;
|
|
|
+ padding: 0 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-secondary {
|
|
|
+ background: white;
|
|
|
+ color: #374151;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-secondary:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+ border-color: #d1d5db;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 主容器 ========== */
|
|
|
+ .main-container {
|
|
|
+ height: calc(100vh - 64px);
|
|
|
+ display: flex;
|
|
|
+ position: relative;
|
|
|
+ padding: 16px;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 左侧容器 ========== */
|
|
|
+ .left-container {
|
|
|
+ display: flex;
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ position: relative;
|
|
|
+ z-index: 50;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .primary-menu {
|
|
|
+ width: 100px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-right: 1px solid #e5e7eb;
|
|
|
+ padding: 16px 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item {
|
|
|
+ width: 84px;
|
|
|
+ padding: 12px 8px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item:hover {
|
|
|
+ background: #f3f4f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item.active {
|
|
|
+ background: #eef3ff;
|
|
|
+ box-shadow: 0 2px 8px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item.active::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ left: -8px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 4px;
|
|
|
+ height: 32px;
|
|
|
+ background: #285cf5;
|
|
|
+ border-radius: 0 2px 2px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-icon {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ color: #6b7280;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item.active .menu-icon {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-label {
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #6b7280;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.2;
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-item.active .menu-label {
|
|
|
+ color: #285cf5;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 二级菜单 - AI对话 ========== */
|
|
|
+ .secondary-panel {
|
|
|
+ width: 420px;
|
|
|
+ background: white;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-panel.collapsed {
|
|
|
+ width: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-panel.hidden {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-header {
|
|
|
+ padding: 20px 20px 16px;
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-badge {
|
|
|
+ background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
|
|
|
+ color: white;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .collapse-btn {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .collapse-btn:hover {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 输入框容器 */
|
|
|
+ .chat-input-container {
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: white;
|
|
|
+ order: 1;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-input-container.bottom {
|
|
|
+ order: 3;
|
|
|
+ border-bottom: none;
|
|
|
+ border-top: 1px solid #f0f1f3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .secondary-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ order: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-messages {
|
|
|
+ min-height: 600px;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-user {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-user .message-content {
|
|
|
+ background: #eef3ff;
|
|
|
+ border: 1.5px solid #285cf5;
|
|
|
+ color: #111827;
|
|
|
+ border-radius: 16px 16px 4px 16px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ max-width: 85%;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-ai {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-ai .message-content {
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ color: #374151;
|
|
|
+ border-radius: 16px 16px 16px 4px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ max-width: 85%;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+ white-space: pre-line;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-input-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 12px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-input-wrapper:focus-within {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-textarea-container {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 72px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-textarea {
|
|
|
+ width: 100%;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ outline: none;
|
|
|
+ resize: none;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ line-height: 1.5;
|
|
|
+ padding: 0;
|
|
|
+ min-height: 72px;
|
|
|
+ max-height: 180px;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-textarea::placeholder {
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-textarea:disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-bottom-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-file-btn {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-file-btn:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-file-btn svg {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-file-btn:hover svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #9ca3af;
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-text.active {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #285cf5;
|
|
|
+ animation: pulse-dot 1.5s ease-in-out infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse-dot {
|
|
|
+ 0%, 100% { opacity: 1; transform: scale(1); }
|
|
|
+ 50% { opacity: 0.5; transform: scale(1.2); }
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: none;
|
|
|
+ background: #285cf5;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn.generating {
|
|
|
+ background: #1f4ad6;
|
|
|
+ cursor: not-allowed;
|
|
|
+ box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn.generating svg {
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes spin {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ transform: scale(1.05);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.25);
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn.generating:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ transform: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.16);
|
|
|
+ }
|
|
|
+
|
|
|
+ .send-btn svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 中央编辑区 ========== */
|
|
|
+ .center-area {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: white;
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 元素工具栏 */
|
|
|
+ .element-toolbar {
|
|
|
+ min-height: 60px;
|
|
|
+ background: white;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 24px;
|
|
|
+ gap: 10px;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-toolbar.visible {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar-section {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding-right: 16px;
|
|
|
+ border-right: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar-section:last-child {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar-btn {
|
|
|
+ height: 38px;
|
|
|
+ padding: 0 14px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ background: #f9fafb;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #4b5563;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 7px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar-btn:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .toolbar-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Slides编辑区 */
|
|
|
+ .slides-area {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fafbfc;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slides-area > .slide-canvas {
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ background: white;
|
|
|
+ box-shadow: none;
|
|
|
+ border-radius: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slides-area > .slide-canvas .slide-placeholder {
|
|
|
+ text-align: center;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-placeholder-icon {
|
|
|
+ font-size: 56px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-placeholder-text {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-placeholder-hint {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 可编辑元素 */
|
|
|
+ .slide-element {
|
|
|
+ position: absolute;
|
|
|
+ border: 2px solid transparent;
|
|
|
+ cursor: move;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-element:hover {
|
|
|
+ border-color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-element.selected {
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 0 0 1px #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-element.selected .resize-handle {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resize-handle {
|
|
|
+ position: absolute;
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ background: #285cf5;
|
|
|
+ border: 2px solid white;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resize-handle.nw { top: -4px; left: -4px; cursor: nw-resize; }
|
|
|
+ .resize-handle.ne { top: -4px; right: -4px; cursor: ne-resize; }
|
|
|
+ .resize-handle.sw { bottom: -4px; left: -4px; cursor: sw-resize; }
|
|
|
+ .resize-handle.se { bottom: -4px; right: -4px; cursor: se-resize; }
|
|
|
+
|
|
|
+ .text-element {
|
|
|
+ padding: 12px;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1.5;
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-element {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 下拉菜单 */
|
|
|
+ .dropdown {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dropdown-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 0;
|
|
|
+ margin-top: 4px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+ padding: 8px;
|
|
|
+ min-width: 160px;
|
|
|
+ display: none;
|
|
|
+ z-index: 1000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dropdown.active .dropdown-menu {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dropdown-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dropdown-item:hover {
|
|
|
+ background: #f3f4f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dropdown-item svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 底部大纲 */
|
|
|
+ .bottom-outline {
|
|
|
+ height: 150px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-top: 1px solid #f0f1f3;
|
|
|
+ padding: 16px 24px;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .bottom-outline.visible {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-track {
|
|
|
+ display: flex;
|
|
|
+ gap: 14px;
|
|
|
+ height: 100%;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item-wrapper {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14px;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item-wrapper:has(.page-menu.active) {
|
|
|
+ z-index: 10000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item {
|
|
|
+ width: 190px;
|
|
|
+ height: 110px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: 2px solid #e5e7eb;
|
|
|
+ background: white;
|
|
|
+ cursor: grab;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:active {
|
|
|
+ cursor: grabbing;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item.dragging {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: grabbing;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item.drag-over {
|
|
|
+ border-color: #285cf5;
|
|
|
+ border-style: dashed;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 8px rgba(40, 92, 245, 0.15);
|
|
|
+ z-index: 100;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item.active {
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:has(.page-menu.active) {
|
|
|
+ z-index: 10000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item .page-number {
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ left: 10px;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #6b7280;
|
|
|
+ background: white;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 6px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 页面操作菜单 */
|
|
|
+ .page-menu {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 8px;
|
|
|
+ right: 8px;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ z-index: 10000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:hover .page-menu {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu.active {
|
|
|
+ z-index: 10001;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu:hover {
|
|
|
+ background: #285cf5;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu:hover svg {
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-dropdown {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 100%;
|
|
|
+ right: 0;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
|
|
+ padding: 8px;
|
|
|
+ min-width: 120px;
|
|
|
+ display: none;
|
|
|
+ z-index: 10002;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu.active .page-menu-dropdown {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item:hover svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item.danger:hover {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item.danger:hover svg {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-menu-item svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 页面间添加按钮 */
|
|
|
+ .add-page-between {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px dashed #d1d5db;
|
|
|
+ background: white;
|
|
|
+ cursor: pointer;
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 20px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item-wrapper:hover .add-page-between {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-page-between:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ border-style: solid;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 页面模板二级菜单 ========== */
|
|
|
+ .template-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 14px;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-card {
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 14px;
|
|
|
+ padding: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-card:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-card:hover .template-preview {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-preview {
|
|
|
+ width: 100%;
|
|
|
+ height: 100px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ position: relative;
|
|
|
+ opacity: 0.9;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-preview svg {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-card:hover .template-preview svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .template-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+.upload-ppt-card {
|
|
|
+ grid-column: 1 / -1;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 14px;
|
|
|
+ border-radius: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-ppt-card:hover {
|
|
|
+ background: #f6f8ff;
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-ppt-card svg {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ color: #d1d5db;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-ppt-card:hover svg {
|
|
|
+ color: #285cf5;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-ppt-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ /* ========== 图片悬浮菜单 ========== */
|
|
|
+ .image-hover-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
|
+ padding: 8px;
|
|
|
+ display: none;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+ z-index: 1000;
|
|
|
+ min-width: 160px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-element.has-image:hover .image-hover-menu {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-menu-item {
|
|
|
+ padding: 12px 14px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-menu-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-menu-item svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 搜索/生成浮窗 ========== */
|
|
|
+ .floating-modal {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 2000;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-modal.active {
|
|
|
+ display: flex;
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 32px;
|
|
|
+ max-width: 600px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 80vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.95);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-modal.active .floating-content {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-close {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ background: #f3f4f6;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-close:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input-group {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input {
|
|
|
+ width: 100%;
|
|
|
+ padding: 14px 16px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: #fafbfc;
|
|
|
+ transition: all 0.2s;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== AI应用中心浮窗 ========== */
|
|
|
+ .app-center-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 32px;
|
|
|
+ max-width: 900px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 85vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.95);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-modal.active .app-center-content {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-filters {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select {
|
|
|
+ height: 40px;
|
|
|
+ padding: 0 36px 0 14px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #111827;
|
|
|
+ background: #fafbfc;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ outline: none;
|
|
|
+ appearance: none;
|
|
|
+ font-weight: 500;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: right 12px center;
|
|
|
+ background-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background-color: #f6f8ff;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23285cf5' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select:focus {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background-color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-select option {
|
|
|
+ padding: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-card {
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 14px;
|
|
|
+ padding: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-card:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-card.selected {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-card.selected::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ background: #285cf5;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
|
|
|
+ background-size: 14px;
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-cover {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 16/9;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ border: 1px solid #dbe6ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-cover svg {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-info {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-description {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-meta {
|
|
|
+ display: flex;
|
|
|
+ gap: 6px;
|
|
|
+ margin-top: 8px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-tag {
|
|
|
+ font-size: 11px;
|
|
|
+ padding: 3px 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding-top: 20px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-count {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-count span {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn {
|
|
|
+ padding: 10px 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn-cancel {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn-cancel:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn-confirm {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn-confirm:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-center-btn-confirm:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== AI创建应用浮窗 ========== */
|
|
|
+ .ai-create-app-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 32px;
|
|
|
+ max-width: 1000px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 85vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.95);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-modal.active .ai-create-app-content {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-create-main {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 360px;
|
|
|
+ gap: 24px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .app-canvas-preview {
|
|
|
+ border: 2px dashed #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ background: #fafbfc;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 14px;
|
|
|
+ overflow: auto;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ background: #fafbfc;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-messages {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 16px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-message {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ max-width: 85%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-message.user {
|
|
|
+ align-self: flex-end;
|
|
|
+ flex-direction: row-reverse;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-avatar {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #285cf5;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: white;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-message.user .chat-avatar {
|
|
|
+ background: #3b82f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-bubble {
|
|
|
+ background: white;
|
|
|
+ padding: 10px 14px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: #111827;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-message.user .chat-bubble {
|
|
|
+ background: #3b82f6;
|
|
|
+ color: white;
|
|
|
+ border-color: #3b82f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-input-area {
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ padding: 12px;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-input-wrapper {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-input {
|
|
|
+ flex: 1;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-family: inherit;
|
|
|
+ resize: none;
|
|
|
+ min-height: 40px;
|
|
|
+ max-height: 100px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-send-btn {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-send-btn:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-send-btn:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-chat-send-btn svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-create-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ padding-top: 20px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-results {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-result-item {
|
|
|
+ aspect-ratio: 16/9;
|
|
|
+ background: #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: 2px solid transparent;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-result-item:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-result-item img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-btn {
|
|
|
+ flex: 1;
|
|
|
+ padding: 14px 24px;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-btn-secondary {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-btn-secondary:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-btn-primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-btn-primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .generate-preview {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 16/9;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 20px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .generate-preview img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ .generate-loading {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .generate-loading.active {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-spinner {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border: 3px solid #f3f4f6;
|
|
|
+ border-top-color: #285cf5;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 右侧面板 */
|
|
|
+ .right-panel {
|
|
|
+ width: 320px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ z-index: 50;
|
|
|
+ overflow: hidden;
|
|
|
+ height: calc(100vh - 32px);
|
|
|
+ max-height: calc(100vh - 32px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-panel.collapsed {
|
|
|
+ width: 60px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-panel.collapsed .menu-content {
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-panel.collapsed .collapse-text,
|
|
|
+ .right-panel.collapsed .outline-panel {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-panel {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-top: 1px solid #f0f1f3;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-toolbar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px 12px;
|
|
|
+ gap: 8px;
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-toolbar-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-btn {
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 10px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ background: #f9fafb;
|
|
|
+ color: #374151;
|
|
|
+ font-size: 12px;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-btn:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 8px 10px 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group {
|
|
|
+ background: #f9fafb;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 6px 8px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group-title {
|
|
|
+ max-width: 160px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-items {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+ padding-top: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: grab;
|
|
|
+ transition: all 0.15s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item.dragging {
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-index {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #1f4ad6;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 700;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-title {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #111827;
|
|
|
+ font-weight: 600;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ max-width: 140px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-type {
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #4b5563;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 600;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-handle {
|
|
|
+ color: #9ca3af;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: opacity 0.15s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-item:hover .outline-handle {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-drag-handle {
|
|
|
+ color: #9ca3af;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: grab;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: opacity 0.15s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group:hover .outline-drag-handle {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-delete-btn {
|
|
|
+ color: #9ca3af;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 4px;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-delete-btn:hover {
|
|
|
+ color: #ef4444;
|
|
|
+ background: #fef2f2;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-group-input {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border: 1.5px solid #285cf5;
|
|
|
+ border-radius: 8px;
|
|
|
+ outline: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
|
|
|
+ min-width: 120px;
|
|
|
+ max-width: 220px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .outline-empty {
|
|
|
+ padding: 12px;
|
|
|
+ text-align: center;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 13px;
|
|
|
+ border: 1px dashed #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 交互网页样式 ========== */
|
|
|
+ /* 网页配置表单 */
|
|
|
+ .web-config-form {
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-group {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 44px;
|
|
|
+ padding: 0 14px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: white;
|
|
|
+ transition: all 0.2s;
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-input:focus {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-input::placeholder {
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-hint {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 文件上传区域 */
|
|
|
+ #uploadFilePanel .form-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-upload-area {
|
|
|
+ border: 2px dashed #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: #fafbfc;
|
|
|
+ padding: 40px 20px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 10px;
|
|
|
+ min-height: 190px; /* 对齐粘贴代码区域高度 */
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-upload-area:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-upload-area.dragover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ border-style: solid;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-upload-area svg {
|
|
|
+ color: #9ca3af;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-hint {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 状态提示 */
|
|
|
+ .web-status {
|
|
|
+ margin-top: 16px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: #f9fafb;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status .status-icon {
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status .status-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.loading .status-icon {
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.success {
|
|
|
+ background: #f0fdf4;
|
|
|
+ border: 1px solid #86efac;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.success .status-icon {
|
|
|
+ color: #22c55e;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.success .status-text {
|
|
|
+ color: #16a34a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.error {
|
|
|
+ background: #fef2f2;
|
|
|
+ border: 1px solid #fecaca;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.error .status-icon {
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-status.error .status-text {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes spin {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 配置操作按钮 */
|
|
|
+ .web-config-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn-secondary {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn-secondary:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn-primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn-primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-btn-primary:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn,
|
|
|
+ .crawl-btn {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn .btn-status-icon,
|
|
|
+ .crawl-btn .btn-status-icon {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn .btn-status-icon svg,
|
|
|
+ .crawl-btn .btn-status-icon svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ stroke: currentColor;
|
|
|
+ fill: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn.status-loading,
|
|
|
+ .crawl-btn.status-loading {
|
|
|
+ opacity: 0.9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn.status-loading .btn-status-icon svg,
|
|
|
+ .crawl-btn.status-loading .btn-status-icon svg {
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn.status-success,
|
|
|
+ .crawl-btn.status-success {
|
|
|
+ background: #f0fdf4;
|
|
|
+ color: #16a34a;
|
|
|
+ border: 1px solid #86efac;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-btn.status-error,
|
|
|
+ .crawl-btn.status-error {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #dc2626;
|
|
|
+ border: 1px solid #fecaca;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Upload Tabs */
|
|
|
+ .upload-tabs {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+ .upload-tab {
|
|
|
+ padding: 10px 16px;
|
|
|
+ border: none;
|
|
|
+ background-color: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: -1px;
|
|
|
+ border-bottom: 2px solid transparent;
|
|
|
+ }
|
|
|
+ .upload-tab.active {
|
|
|
+ color: #285cf5;
|
|
|
+ border-bottom-color: #285cf5;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .code-textarea {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 190px; /* Increased height to match file upload area */
|
|
|
+ border: 1px solid #d1d5db;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 12px;
|
|
|
+ font-family: monospace;
|
|
|
+ font-size: 13px;
|
|
|
+ resize: vertical;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+ }
|
|
|
+ .code-textarea:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+ .file-name-display {
|
|
|
+ margin-top: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #4b5563;
|
|
|
+ background-color: #f9fafb;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ display: none; /* Hidden by default */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 网页中心浮窗 */
|
|
|
+ .web-center-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 32px;
|
|
|
+ max-width: 900px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 85vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.95);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .floating-modal.active .web-center-content {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-filters {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-card {
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 14px;
|
|
|
+ padding: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-card:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-card.selected {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-card.selected::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ background: #285cf5;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
|
|
|
+ background-size: 14px;
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 16/9;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ border: 1px solid #dbe6ff;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview svg {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-info {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-description {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-meta {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-tag {
|
|
|
+ padding: 3px 8px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-center-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-top: 20px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 网页详情浮窗 */
|
|
|
+ .web-detail-modal {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 10001;
|
|
|
+ animation: fadeIn 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-modal.active {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ width: 90%;
|
|
|
+ max-width: 1200px;
|
|
|
+ height: 85vh;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 360px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview-area {
|
|
|
+ background: #f9fafb;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-right: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview-header {
|
|
|
+ padding: 20px 24px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-fullscreen-btn {
|
|
|
+ padding: 8px 12px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-size: 13px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-fullscreen-btn:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview-iframe {
|
|
|
+ flex: 1;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-preview-iframe iframe {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-info-area {
|
|
|
+ background: white;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-header {
|
|
|
+ padding: 24px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-name {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-meta-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-meta-item svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-section {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-section-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-section-content {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-footer {
|
|
|
+ padding: 20px 24px;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-btn-secondary {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-btn-secondary:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-btn-primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .web-detail-btn-primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 创建入口弹窗 ========== */
|
|
|
+ .modal-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 1000;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: opacity 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-overlay.active {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: all;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-modal {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 20px;
|
|
|
+ width: 360px;
|
|
|
+ box-shadow: 0 16px 40px rgba(0,0,0,0.16);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-body {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-btn {
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 14px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ background: #f9fafb;
|
|
|
+ color: #374151;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-btn:hover {
|
|
|
+ background: #fff;
|
|
|
+ border-color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-btn.primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: #fff;
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.24);
|
|
|
+ }
|
|
|
+
|
|
|
+ .settings-btn.primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-modal {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 22px;
|
|
|
+ width: 360px;
|
|
|
+ box-shadow: 0 16px 40px rgba(0,0,0,0.16);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #4b5563;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* PPT解析浮窗 */
|
|
|
+ .ppt-parse-overlay {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ backdrop-filter: blur(2px);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 1200;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: opacity 0.25s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-overlay.active {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: all;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-card {
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ width: 360px;
|
|
|
+ box-shadow: 0 16px 40px rgba(0,0,0,0.16);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 14px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-icon {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ border-radius: 14px;
|
|
|
+ margin: 0 auto;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-icon svg {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ stroke: currentColor;
|
|
|
+ fill: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-icon.success {
|
|
|
+ background: #f0fdf4;
|
|
|
+ color: #16a34a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-icon.error {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-btn.primary {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-btn.primary:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-btn.secondary {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #4b5563;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ppt-parse-btn.secondary:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-modal {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 40px;
|
|
|
+ max-width: 900px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 85vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.9);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-overlay.active .create-modal {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-header {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-header h2 {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-header p {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-options {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card {
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card.featured {
|
|
|
+ background: linear-gradient(135deg, #eef3ff 0%, #f6f8ff 100%);
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card.featured::before {
|
|
|
+ content: '推荐';
|
|
|
+ position: absolute;
|
|
|
+ top: 12px;
|
|
|
+ right: 12px;
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-icon {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ border-radius: 14px;
|
|
|
+ background: white;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-icon svg {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card.featured .create-icon svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card h3 {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-card p {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ right: 20px;
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: none;
|
|
|
+ background: #f3f4f6;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 发布弹窗 ========== */
|
|
|
+ .publish-modal {
|
|
|
+ background: white;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 28px;
|
|
|
+ max-width: 780px;
|
|
|
+ width: 90%;
|
|
|
+ max-height: 85vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: scale(0.9);
|
|
|
+ transition: transform 0.3s;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-overlay.active .publish-modal {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-header {
|
|
|
+ margin-bottom: 18px;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-title {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-course-name {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 10px 16px;
|
|
|
+ border: 1.5px solid transparent;
|
|
|
+ border-radius: 10px;
|
|
|
+ cursor: text;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: inline-block;
|
|
|
+ min-width: 120px;
|
|
|
+ max-width: 500px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-course-name:hover {
|
|
|
+ background: #fafbfc;
|
|
|
+ border-color: #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-course-name-input {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ padding: 10px 16px;
|
|
|
+ border: 1.5px solid #285cf5;
|
|
|
+ border-radius: 10px;
|
|
|
+ outline: none;
|
|
|
+ box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
|
|
|
+ min-width: 120px;
|
|
|
+ max-width: 500px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-content {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 260px;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-form {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-section {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-wrapper {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 16/9;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 2px dashed #d1d5db;
|
|
|
+ background: #fafbfc;
|
|
|
+ cursor: default;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-wrapper:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-wrapper img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-placeholder {
|
|
|
+ color: #9ca3af;
|
|
|
+ text-align: center;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-placeholder svg {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-placeholder p {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 课程封面悬浮菜单 */
|
|
|
+ .publish-cover-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
|
+ padding: 8px;
|
|
|
+ display: none;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+ z-index: 1000;
|
|
|
+ min-width: 160px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-wrapper:hover .publish-cover-menu {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-menu-item {
|
|
|
+ padding: 12px 14px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-menu-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-menu-item svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-actions {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 12px;
|
|
|
+ right: 12px;
|
|
|
+ display: none;
|
|
|
+ gap: 8px;
|
|
|
+ z-index: 999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-cover-wrapper:hover .publish-cover-actions {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-action-btn {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-action-btn:hover {
|
|
|
+ background: white;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-action-btn svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-action-btn:hover svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-action-btn.delete:hover svg {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-label .required {
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-select {
|
|
|
+ height: 44px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: white;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ outline: none;
|
|
|
+ appearance: none;
|
|
|
+ background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: right 16px center;
|
|
|
+ padding-right: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-select:hover {
|
|
|
+ border-color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-select:focus {
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 0 0 3px rgba(40, 92, 245, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .visibility-options {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 14px 16px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option:hover {
|
|
|
+ border-color: #d1d5db;
|
|
|
+ background: #fafbfc;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option.selected {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option input[type="radio"] {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-custom {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border: 2px solid #d1d5db;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 12px;
|
|
|
+ position: relative;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option.selected .radio-custom {
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-custom::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%) scale(0);
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #285cf5;
|
|
|
+ transition: transform 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-option.selected .radio-custom::after {
|
|
|
+ transform: translate(-50%, -50%) scale(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-content {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .radio-description {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-top: 18px;
|
|
|
+ padding-top: 14px;
|
|
|
+ border-top: 1px solid #f0f1f3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: none;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn-cancel {
|
|
|
+ background: white;
|
|
|
+ color: #6b7280;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn-cancel:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+ border-color: #d1d5db;
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn-confirm {
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(40, 92, 245, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn-confirm:hover {
|
|
|
+ background: #1f4ad6;
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.3);
|
|
|
+ transform: translateY(-1px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .publish-btn-confirm svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ========== 互动工具样式 ========== */
|
|
|
+ .tools-intro {
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin: 16px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tools-intro-image {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tools-intro-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tools-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ padding: 0 20px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-card {
|
|
|
+ background: white;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 14px 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-card:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-icon {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #f9fafb;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-card:hover .tool-icon {
|
|
|
+ background: #eef3ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-icon svg {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-card:hover .tool-icon svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 面板内容包装器 */
|
|
|
+ .panel-content-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 工具配置视图 */
|
|
|
+ .tools-config-view {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: white;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tools-config-view.hidden {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 通用hidden类 */
|
|
|
+ .hidden {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-header {
|
|
|
+ padding: 20px 20px 16px;
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-back-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ background: #f9fafb;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-back-btn:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-tool-name {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-section {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-switch {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 14px 16px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-switch-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #374151;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .switch-toggle {
|
|
|
+ position: relative;
|
|
|
+ width: 48px;
|
|
|
+ height: 28px;
|
|
|
+ background: #e5e7eb;
|
|
|
+ border-radius: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .switch-toggle.active {
|
|
|
+ background: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .switch-toggle::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 3px;
|
|
|
+ left: 3px;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 50%;
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .switch-toggle.active::after {
|
|
|
+ left: 23px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-mode-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-checkbox {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border: 2px solid #d1d5db;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item.active .mode-checkbox {
|
|
|
+ background: #285cf5;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-checkbox svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ color: white;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item.active .mode-checkbox svg {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-radio {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border: 2px solid #d1d5db;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item.active .mode-radio {
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-radio-dot {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: transparent;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-item.active .mode-radio-dot {
|
|
|
+ background: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mode-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 填空符按钮 */
|
|
|
+ .question-input-wrapper {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-blank-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 8px;
|
|
|
+ bottom: 8px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: white;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-blank-btn svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-blank-btn:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 图片上传按钮 */
|
|
|
+ .upload-image-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 8px;
|
|
|
+ bottom: 8px;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: white;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-image-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-image-btn:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 带图片按钮的输入框容器 */
|
|
|
+ .input-with-image {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-with-image textarea,
|
|
|
+ .input-with-image .option-input {
|
|
|
+ padding-right: 48px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 图片预览 */
|
|
|
+ .image-preview {
|
|
|
+ position: relative;
|
|
|
+ margin-top: 12px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ max-width: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-preview img {
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .remove-image-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ right: 8px;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: none;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ color: white;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .remove-image-btn:hover {
|
|
|
+ background: rgba(220, 38, 38, 0.9);
|
|
|
+ }
|
|
|
+
|
|
|
+ .remove-image-btn svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 排序项样式 */
|
|
|
+ .sort-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ position: relative;
|
|
|
+ padding: 6px 8px;
|
|
|
+ border-radius: 10px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ border-color: #285cf5;
|
|
|
+ box-shadow: 0 4px 12px rgba(40, 92, 245, 0.16);
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-item:hover .option-actions {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-item-static {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ position: relative;
|
|
|
+ padding: 4px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-item-static:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-item-static:hover .option-actions {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-number {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background: #285cf5;
|
|
|
+ color: white;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-display {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 10px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ background: #eef3ff;
|
|
|
+ border: 2px solid #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item.draggable {
|
|
|
+ cursor: move;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item.draggable:hover {
|
|
|
+ background: #dbe6ff;
|
|
|
+ transform: scale(1.05);
|
|
|
+ box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item.dragging {
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item.drag-over {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #cddcff;
|
|
|
+ box-shadow: 0 6px 14px rgba(40, 92, 245, 0.22);
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-drag-handle {
|
|
|
+ position: absolute;
|
|
|
+ top: 2px;
|
|
|
+ right: 2px;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ color: #285cf5;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-order-item.draggable:hover .sort-drag-handle {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-drag-handle svg {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sort-arrow {
|
|
|
+ color: #285cf5;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 工具顶部栏 */
|
|
|
+ .tool-header {
|
|
|
+ height: 60px;
|
|
|
+ background: white;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 24px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-selector {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px 16px;
|
|
|
+ background: #fafbfc;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-btn:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ color: #6b7280;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-btn svg:last-child {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-dropdown {
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 0;
|
|
|
+ margin-top: 8px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
|
+ padding: 8px;
|
|
|
+ min-width: 200px;
|
|
|
+ display: none;
|
|
|
+ z-index: 1000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-selector.active .tool-type-dropdown {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item:hover {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item:hover svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item.active {
|
|
|
+ background: #eef3ff;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-type-item.active svg {
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 工具编辑区域 */
|
|
|
+ .tool-edit-container {
|
|
|
+ display: none;
|
|
|
+ flex-direction: column;
|
|
|
+ background: white;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-edit-container.active {
|
|
|
+ display: flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-edit-area {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 40px;
|
|
|
+ background: #fafbfc;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tool-canvas {
|
|
|
+ width: 960px;
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+ background: white;
|
|
|
+ border-radius: 16px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+ padding: 40px;
|
|
|
+ min-height: 500px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-item {
|
|
|
+ margin-bottom: 32px;
|
|
|
+ padding-bottom: 32px;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-number {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-type-toggle {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toggle-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mini-toggle {
|
|
|
+ width: 40px;
|
|
|
+ height: 22px;
|
|
|
+ background: #e5e7eb;
|
|
|
+ border-radius: 11px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mini-toggle.active {
|
|
|
+ background: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mini-toggle::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 2px;
|
|
|
+ left: 2px;
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 50%;
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mini-toggle.active::after {
|
|
|
+ left: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-input {
|
|
|
+ width: 100%;
|
|
|
+ padding: 14px 16px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: #fafbfc;
|
|
|
+ transition: all 0.2s;
|
|
|
+ font-family: inherit;
|
|
|
+ resize: vertical;
|
|
|
+ min-height: 80px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ background: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .options-list {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ position: relative;
|
|
|
+ padding: 4px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-item:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-item:hover .option-actions {
|
|
|
+ opacity: 1;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-action-btn {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-action-btn:hover {
|
|
|
+ background: #e5e7eb;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-action-btn.delete:hover {
|
|
|
+ background: #fee2e2;
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-action-btn svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-drag-handle {
|
|
|
+ cursor: grab;
|
|
|
+ color: #d1d5db;
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-item:hover .option-drag-handle {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-drag-handle:active {
|
|
|
+ cursor: grabbing;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-drag-handle svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-checkbox {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ border: 2px solid #d1d5db;
|
|
|
+ border-radius: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-checkbox.checked {
|
|
|
+ background: #285cf5;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-checkbox svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ color: white;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-checkbox.checked svg {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-input {
|
|
|
+ flex: 1;
|
|
|
+ padding: 10px 14px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: white;
|
|
|
+ transition: all 0.2s;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ margin-top: 8px;
|
|
|
+ margin-left: 34px;
|
|
|
+ border: 1.5px dashed #d1d5db;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: transparent;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn-with-text {
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 14px;
|
|
|
+ margin-top: 8px;
|
|
|
+ margin-left: 66px;
|
|
|
+ border: 1.5px dashed #d1d5db;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: transparent;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ white-space: nowrap;
|
|
|
+ align-self: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn-with-text svg {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-option-btn-with-text:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-left: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-action-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border: none;
|
|
|
+ background: #f9fafb;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ color: #6b7280;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-action-btn:hover {
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-action-btn.delete:hover {
|
|
|
+ background: #fee2e2;
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-action-btn svg {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .explanation-section {
|
|
|
+ margin-top: 16px;
|
|
|
+ padding: 16px;
|
|
|
+ background: #f9fafb;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .explanation-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .explanation-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-gen-btn {
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ background: linear-gradient(135deg, #285cf5 0%, #1f4ad6 100%);
|
|
|
+ color: white;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ai-gen-btn:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 2px 8px rgba(40, 92, 245, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .explanation-textarea {
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ background: white;
|
|
|
+ resize: vertical;
|
|
|
+ min-height: 60px;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .explanation-textarea:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn {
|
|
|
+ margin-top: 24px;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border: 2px dashed #d1d5db;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: transparent;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn svg {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn-with-text {
|
|
|
+ margin-top: 24px;
|
|
|
+ height: 44px;
|
|
|
+ padding: 0 18px;
|
|
|
+ border: 2px dashed #d1d5db;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: transparent;
|
|
|
+ color: #6b7280;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ white-space: nowrap;
|
|
|
+ align-self: flex-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn-with-text svg {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-question-btn-with-text:hover {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #f6f8ff;
|
|
|
+ transform: scale(1.02);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 抽认卡特殊布局 */
|
|
|
+ .flashcard-layout {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 24px;
|
|
|
+ margin-bottom: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .flashcard-side {
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ min-height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .flashcard-side-label {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .flashcard-content {
|
|
|
+ width: 100%;
|
|
|
+ padding: 14px;
|
|
|
+ border: 1.5px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #111827;
|
|
|
+ background: white;
|
|
|
+ resize: vertical;
|
|
|
+ min-height: 120px;
|
|
|
+ font-family: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .flashcard-content:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #285cf5;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 资源弹窗与列表 */
|
|
|
+ .resource-upload-list {
|
|
|
+ margin-top: 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-file-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #fafbfc;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-file-name {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-file-name span {
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-remove-btn {
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ color: #9ca3af;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border-radius: 8px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-remove-btn:hover {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #dc2626;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-chip {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: #f3f4f6;
|
|
|
+ color: #4b5563;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-tabs {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ min-height: 60px;
|
|
|
+ padding: 0 16px;
|
|
|
+ border-bottom: 1px solid #f0f1f3;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px 12px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-tab {
|
|
|
+ padding: 8px 14px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ background: #fafbfc;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #4b5563;
|
|
|
+ transition: all 0.2s;
|
|
|
+ height: 36px;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .resource-tab.active {
|
|
|
+ border-color: #285cf5;
|
|
|
+ color: #285cf5;
|
|
|
+ background: #eef3ff;
|
|
|
+ box-shadow: 0 2px 8px rgba(40, 92, 245, 0.12);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 滚动条 */
|
|
|
+ ::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-webkit-scrollbar-thumb {
|
|
|
+ background: #e0e2e5;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #c5c8cc;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <!-- 顶部工具栏 -->
|
|
|
+ <div class="top-bar">
|
|
|
+ <div class="top-bar-left">
|
|
|
+ <div class="logo-menu-wrapper">
|
|
|
+ <button class="logo-btn" id="topMenuToggle" onclick="toggleTopMenu(event)">
|
|
|
+ <img src="/var/folders/6_/1f501dn50cqbnjhxhfyqnbcm0000gn/T/cocorobo_icon_rgb_blue_png_1769396590215.png" alt="Coco" class="logo-img">
|
|
|
+ <svg class="logo-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="6 9 12 15 18 9"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="top-dropdown" id="topDropdown">
|
|
|
+ <div class="top-dropdown-item" onclick="handleTopMenuAction('back')">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M15 18l-6-6 6-6"/>
|
|
|
+ </svg>
|
|
|
+ 返回列表
|
|
|
+ </div>
|
|
|
+ <div class="top-dropdown-item" onclick="handleTopMenuAction('settings')">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="3"/>
|
|
|
+ <path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09a1.65 1.65 0 00-1-1.51 1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09a1.65 1.65 0 001.51-1 1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06a1.65 1.65 0 001.82.33h.09A1.65 1.65 0 0010.91 3V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51h.09a1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82v.09a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>
|
|
|
+ </svg>
|
|
|
+ 设置
|
|
|
+ </div>
|
|
|
+ <div class="top-dropdown-item" onclick="handleTopMenuAction('template')">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="7"/>
|
|
|
+ <rect x="14" y="3" width="7" height="7"/>
|
|
|
+ <rect x="14" y="14" width="7" height="7"/>
|
|
|
+ <rect x="3" y="14" width="7" height="7"/>
|
|
|
+ </svg>
|
|
|
+ 导入模板
|
|
|
+ </div>
|
|
|
+ <div class="top-dropdown-item" onclick="handleTopMenuAction('duplicate')">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
|
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
|
+ </svg>
|
|
|
+ 另存为副本
|
|
|
+ </div>
|
|
|
+ <div class="top-dropdown-sep"></div>
|
|
|
+ <div class="top-dropdown-item danger" onclick="handleTopMenuAction('delete')">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ 删除
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="course-title" contenteditable="true">新建课程</div>
|
|
|
+ <div class="auto-save">
|
|
|
+ <span class="save-dot"></span>
|
|
|
+ <span id="saveStatus">未保存</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="top-bar-right">
|
|
|
+ <button class="top-btn btn-secondary">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
|
+ <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
|
+ </svg>
|
|
|
+ 预览
|
|
|
+ </button>
|
|
|
+ <button class="top-btn btn-secondary">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/>
|
|
|
+ <polyline points="17 21 17 13 7 13 7 21"/>
|
|
|
+ <polyline points="7 3 7 8 15 8"/>
|
|
|
+ </svg>
|
|
|
+ 保存
|
|
|
+ </button>
|
|
|
+ <button class="top-btn btn-primary" onclick="showPublishModal()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <polyline points="12 6 12 12 16 14"/>
|
|
|
+ </svg>
|
|
|
+ 发布
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主容器 -->
|
|
|
+ <div class="main-container">
|
|
|
+ <!-- 左侧容器 -->
|
|
|
+ <div class="left-container">
|
|
|
+ <!-- 一级菜单 -->
|
|
|
+ <div class="primary-menu">
|
|
|
+ <div class="menu-item active" data-panel="ai">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">Coco AI</span>
|
|
|
+ </div>
|
|
|
+ <div class="menu-item" data-panel="pages">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
|
+ <polyline points="14 2 14 8 20 8"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">页面</span>
|
|
|
+ </div>
|
|
|
+ <div class="menu-item" data-panel="tools">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="3"/>
|
|
|
+ <path d="M12 1v6m0 6v6M5.64 5.64l4.24 4.24m4.24 4.24l4.24 4.24M1 12h6m6 0h6M5.64 18.36l4.24-4.24m4.24-4.24l4.24-4.24"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">互动工具</span>
|
|
|
+ </div>
|
|
|
+ <div class="menu-item" data-panel="apps">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="7"/>
|
|
|
+ <rect x="14" y="3" width="7" height="7"/>
|
|
|
+ <rect x="14" y="14" width="7" height="7"/>
|
|
|
+ <rect x="3" y="14" width="7" height="7"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">AI应用</span>
|
|
|
+ </div>
|
|
|
+ <div class="menu-item" data-panel="web">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <path d="M2 12h20"/>
|
|
|
+ <path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">交互网页</span>
|
|
|
+ </div>
|
|
|
+ <div class="menu-item" data-panel="resources">
|
|
|
+ <svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
|
|
|
+ </svg>
|
|
|
+ <span class="menu-label">多媒体</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - AI对话 -->
|
|
|
+ <div class="secondary-panel" id="aiPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">Coco AI</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chat-input-container" id="chatInputContainer">
|
|
|
+ <div class="chat-input-wrapper">
|
|
|
+ <div class="chat-textarea-container">
|
|
|
+ <textarea
|
|
|
+ class="chat-textarea"
|
|
|
+ id="chatInput"
|
|
|
+ placeholder="例如:创建一节45分钟的四年级教学内容为水的三态变化的课程"
|
|
|
+ rows="3"
|
|
|
+ oninput="autoResize(this)"
|
|
|
+ >创建一节45分钟的四年级教学内容为水的三态变化的课程</textarea>
|
|
|
+ </div>
|
|
|
+ <div class="chat-bottom-row">
|
|
|
+ <div style="display: flex; align-items: center; gap: 8px;">
|
|
|
+ <button class="upload-file-btn" title="上传参考资料" id="uploadBtn">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="status-text" id="statusText">
|
|
|
+ <span class="status-dot"></span>
|
|
|
+ 生成中……
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <button class="send-btn" onclick="sendMessage()" id="sendBtn">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <line x1="22" y1="2" x2="11" y2="13"/>
|
|
|
+ <polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="secondary-content">
|
|
|
+ <div class="chat-messages" id="chatMessages">
|
|
|
+ <!-- 对话消息将显示在这里 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - 页面模板 -->
|
|
|
+ <div class="secondary-panel hidden" id="pagesPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">添加模板页面</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="secondary-content" style="overflow-y: auto;">
|
|
|
+ <div class="template-grid">
|
|
|
+ <!-- 标题页 -->
|
|
|
+ <div class="template-card" onclick="addPageFromTemplate('title')">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">标题页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图片页 -->
|
|
|
+ <div class="template-card" onclick="addPageFromTemplate('image')">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">图片页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 内容页 -->
|
|
|
+ <div class="template-card" onclick="addPageFromTemplate('content')">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
|
+ <polyline points="14 2 14 8 20 8"/>
|
|
|
+ <line x1="16" y1="13" x2="8" y2="13"/>
|
|
|
+ <line x1="16" y1="17" x2="8" y2="17"/>
|
|
|
+ <polyline points="10 9 9 9 8 9"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">内容页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 文图页(左文右图) -->
|
|
|
+ <div class="template-card" onclick="addPageFromTemplate('text-image')">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="18" rx="1"/>
|
|
|
+ <rect x="14" y="3" width="7" height="18" rx="1"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">文图页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 图文页(左图右文) -->
|
|
|
+ <div class="template-card" onclick="addPageFromTemplate('image-text')">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="18" rx="1"/>
|
|
|
+ <rect x="14" y="3" width="7" height="18" rx="1"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">图文页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 上传PPT -->
|
|
|
+ <div class="template-card upload-ppt-card" onclick="uploadPPT()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ <span class="upload-ppt-text">上传PPT</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - 互动工具 -->
|
|
|
+ <div class="secondary-panel hidden" id="toolsPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">添加互动工具</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 内容区域包装器 -->
|
|
|
+ <div class="panel-content-wrapper">
|
|
|
+ <!-- 工具列表视图 -->
|
|
|
+ <div class="secondary-content tools-list-view" id="toolsListView" style="overflow-y: auto;">
|
|
|
+ <!-- 插画式说明 -->
|
|
|
+ <div class="tools-intro">
|
|
|
+ <div class="tools-intro-image">
|
|
|
+ <svg width="120" height="80" viewBox="0 0 120 80" fill="none">
|
|
|
+ <rect x="10" y="15" width="100" height="50" rx="8" fill="#eef3ff" stroke="#285cf5" stroke-width="2"/>
|
|
|
+ <circle cx="30" cy="30" r="4" fill="#285cf5"/>
|
|
|
+ <rect x="40" y="27" width="60" height="6" rx="3" fill="#ffd9a8"/>
|
|
|
+ <circle cx="30" cy="45" r="4" fill="#d1d5db"/>
|
|
|
+ <rect x="40" y="42" width="45" height="6" rx="3" fill="#e5e7eb"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tools-intro-text">选择工具创建互动页面</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="tools-grid">
|
|
|
+ <!-- 选择 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('choice')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <path d="M12 16v-4m0-4h.01"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">选择</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 问答 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('qa')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">问答</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 投票 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('vote')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="9 11 12 14 22 4"/>
|
|
|
+ <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">投票</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 拍照 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('photo')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/>
|
|
|
+ <circle cx="12" cy="13" r="4"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">拍照</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 填空 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('fillblank')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="8" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="8" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="8" y1="18" x2="21" y2="18"/>
|
|
|
+ <line x1="3" y1="6" x2="3.01" y2="6"/>
|
|
|
+ <line x1="3" y1="12" x2="3.01" y2="12"/>
|
|
|
+ <line x1="3" y1="18" x2="3.01" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">填空</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 排序 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('sort')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="4" y1="6" x2="20" y2="6"/>
|
|
|
+ <line x1="4" y1="12" x2="20" y2="12"/>
|
|
|
+ <line x1="4" y1="18" x2="20" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">排序</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 白板 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('whiteboard')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 19l7-7 3 3-7 7-3-3z"/>
|
|
|
+ <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>
|
|
|
+ <path d="M2 2l7.586 7.586"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">白板</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 抽认卡 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('flashcard')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="2" y="4" width="20" height="16" rx="2"/>
|
|
|
+ <path d="M7 15h10M12 9v6"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">抽认卡</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- CocoPi -->
|
|
|
+ <div class="tool-card" onclick="selectTool('cocopi')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="16 18 22 12 16 6"/>
|
|
|
+ <polyline points="8 6 2 12 8 18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">CocoPi</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创作空间 -->
|
|
|
+ <div class="tool-card" onclick="selectTool('workspace')">
|
|
|
+ <div class="tool-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="tool-name">创作空间</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 工具配置视图 -->
|
|
|
+ <div class="secondary-content tools-config-view hidden" id="toolsConfigView">
|
|
|
+ <div class="config-header">
|
|
|
+ <button class="config-back-btn" onclick="backToToolsList()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="config-tool-name" id="currentToolName">选择</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置内容区域 -->
|
|
|
+ <div class="config-content" id="toolConfigContent">
|
|
|
+ <!-- 配置项将通过JavaScript动态生成 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div><!-- 关闭 panel-content-wrapper -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - AI应用 -->
|
|
|
+ <div class="secondary-panel hidden" id="appsPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">添加AI应用</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="secondary-content" style="overflow-y: auto;">
|
|
|
+ <div class="template-grid">
|
|
|
+ <!-- 应用中心 -->
|
|
|
+ <div class="template-card" onclick="openAIAppCenter()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">应用中心</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创建应用 -->
|
|
|
+ <div class="template-card" onclick="openCreateAppModal()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <line x1="12" y1="8" x2="12" y2="16"/>
|
|
|
+ <line x1="8" y1="12" x2="16" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">创建应用</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - 交互网页 -->
|
|
|
+ <div class="secondary-panel hidden" id="webPanel">
|
|
|
+ <!-- 列表视图 -->
|
|
|
+ <div id="webListView">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">添加交互网页</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="secondary-content" style="overflow-y: auto;">
|
|
|
+ <div class="template-grid">
|
|
|
+ <!-- 网页中心 -->
|
|
|
+ <div class="template-card" onclick="openWebCenter()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="2" y="3" width="20" height="14" rx="2"/>
|
|
|
+ <line x1="2" y1="7" x2="22" y2="7"/>
|
|
|
+ <circle cx="5" cy="5" r="0.5" fill="currentColor"/>
|
|
|
+ <circle cx="7" cy="5" r="0.5" fill="currentColor"/>
|
|
|
+ <circle cx="9" cy="5" r="0.5" fill="currentColor"/>
|
|
|
+ <path d="M8 11h8M8 14h5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">网页中心</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 上传网页 -->
|
|
|
+ <div class="template-card" onclick="uploadWebPage()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">上传网页</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 爬取网页 -->
|
|
|
+ <div class="template-card" onclick="crawlWebPage()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <path d="M2 12h20"/>
|
|
|
+ <path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>
|
|
|
+ <path d="M16 8l-4 4-4-4" stroke-width="1.5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">爬取网页</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置视图 -->
|
|
|
+ <div id="webConfigView" class="hidden">
|
|
|
+ <!-- 上传网页配置 -->
|
|
|
+ <div class="panel-content-wrapper hidden" id="uploadWebView">
|
|
|
+ <div class="config-header">
|
|
|
+ <button class="config-back-btn" onclick="backToWebList()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="config-tool-name">上传网页</span>
|
|
|
+ </div>
|
|
|
+ <div class="config-content">
|
|
|
+ <div class="web-config-form">
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">网页名称</label>
|
|
|
+ <input type="text" class="form-input" id="uploadWebName" placeholder="请输入网页名称" oninput="validateUploadForm()">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tabs -->
|
|
|
+ <div class="upload-tabs">
|
|
|
+ <button id="uploadFileTab" class="upload-tab active" onclick="switchUploadTab('file')">上传文件</button>
|
|
|
+ <button id="pasteCodeTab" class="upload-tab" onclick="switchUploadTab('code')">粘贴代码</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tab Panels -->
|
|
|
+ <div id="uploadFilePanel">
|
|
|
+ <div class="form-group">
|
|
|
+ <div class="file-upload-area" id="fileUploadArea" onclick="document.getElementById('fileInput').click()">
|
|
|
+ <input type="file" id="fileInput" accept=".html,.htm,.zip" style="display: none;" onchange="handleFileSelect(event)">
|
|
|
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ <p class="upload-text">点击或拖拽文件到此处上传</p>
|
|
|
+ <p class="upload-hint">支持 HTML、HTM、ZIP 格式</p>
|
|
|
+ </div>
|
|
|
+ <div id="fileNameDisplay" class="file-name-display"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="pasteCodePanel" class="hidden">
|
|
|
+ <div class="form-group">
|
|
|
+ <textarea id="pasteCodeTextarea" class="code-textarea" placeholder="请在此处粘贴完整的HTML代码" oninput="validateUploadForm()"></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="web-config-actions" style="padding: 0 30%;">
|
|
|
+ <button class="config-btn config-btn-primary status-btn" id="confirmCreateWebBtn" onclick="confirmCreateWebPage()" disabled>
|
|
|
+ <span class="btn-status-icon" id="uploadStatusIcon" aria-hidden="true"></span>
|
|
|
+ <span class="btn-status-text" id="uploadStatusText">等待上传...</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 爬取网页配置 -->
|
|
|
+ <div class="panel-content-wrapper hidden" id="crawlWebView">
|
|
|
+ <div class="config-header">
|
|
|
+ <button class="config-back-btn" onclick="backToWebList()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="config-tool-name">爬取网页</span>
|
|
|
+ </div>
|
|
|
+ <div class="config-content">
|
|
|
+ <div class="web-config-form">
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">网页名称</label>
|
|
|
+ <input type="text" class="form-input" id="crawlWebName" placeholder="请输入网页名称" oninput="validateCrawlUrl()">
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">网页链接</label>
|
|
|
+ <input type="url" class="form-input" id="crawlWebUrl" placeholder="请输入完整的网页URL地址" oninput="validateCrawlUrl()">
|
|
|
+ </div>
|
|
|
+ <div class="web-config-actions" style="padding: 0 30%;">
|
|
|
+ <button class="config-btn config-btn-primary crawl-btn" id="startCrawlBtn" onclick="startCrawlWeb()" disabled>
|
|
|
+ <span class="btn-status-icon" id="crawlStatusIcon" aria-hidden="true"></span>
|
|
|
+ <span class="btn-status-text" id="crawlStatusText">等待输入...</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二级菜单 - 资源 -->
|
|
|
+ <div class="secondary-panel hidden" id="resourcesPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title">添加多媒体</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleSecondaryPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="secondary-content" style="overflow-y: auto;">
|
|
|
+ <div class="template-grid">
|
|
|
+ <div class="template-card" onclick="openVideoSourceModal()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="4" width="18" height="16" rx="2" ry="2"/>
|
|
|
+ <polygon points="10 9 16 12 10 15 10 9"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">视频</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="template-card" onclick="openAudioUploadModal()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M9 18V5l12-2v13"/>
|
|
|
+ <circle cx="6" cy="18" r="3"/>
|
|
|
+ <circle cx="18" cy="16" r="3"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">音频</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="template-card" onclick="openDocumentUploadModal()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
|
+ <polyline points="14 2 14 8 20 8"/>
|
|
|
+ <line x1="8" y1="13" x2="16" y2="13"/>
|
|
|
+ <line x1="8" y1="17" x2="14" y2="17"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">文档</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="template-card" onclick="openCollectionModal()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="4" width="8" height="14" rx="2"/>
|
|
|
+ <rect x="13" y="4" width="8" height="14" rx="2"/>
|
|
|
+ <line x1="7" y1="9" x2="7" y2="13"/>
|
|
|
+ <line x1="17" y1="9" x2="17" y2="13"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">文档集</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 中央编辑区 -->
|
|
|
+ <div class="center-area">
|
|
|
+ <div class="element-toolbar" id="elementToolbar">
|
|
|
+ <!-- 插入工具 -->
|
|
|
+ <div class="toolbar-section">
|
|
|
+ <button class="toolbar-btn" onclick="insertText()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M4 7V4h16v3M9 20h6M12 4v16"/>
|
|
|
+ </svg>
|
|
|
+ 文本
|
|
|
+ </button>
|
|
|
+ <button class="toolbar-btn" onclick="insertImage()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ 图片
|
|
|
+ </button>
|
|
|
+ <button class="toolbar-btn" onclick="insertTable()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
|
+ <line x1="3" y1="9" x2="21" y2="9"/>
|
|
|
+ <line x1="3" y1="15" x2="21" y2="15"/>
|
|
|
+ <line x1="9" y1="3" x2="9" y2="21"/>
|
|
|
+ <line x1="15" y1="3" x2="15" y2="21"/>
|
|
|
+ </svg>
|
|
|
+ 表格
|
|
|
+ </button>
|
|
|
+ <div class="dropdown" id="shapeDropdown">
|
|
|
+ <button class="toolbar-btn" onclick="toggleDropdown('shapeDropdown')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ </svg>
|
|
|
+ 形状
|
|
|
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="6 9 12 15 18 9"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="dropdown-menu">
|
|
|
+ <div class="dropdown-item" onclick="insertShape('circle')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ </svg>
|
|
|
+ 圆形
|
|
|
+ </div>
|
|
|
+ <div class="dropdown-item" onclick="insertShape('rectangle')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
|
+ </svg>
|
|
|
+ 矩形
|
|
|
+ </div>
|
|
|
+ <div class="dropdown-item" onclick="insertShape('triangle')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2 L22 20 L2 20 Z"/>
|
|
|
+ </svg>
|
|
|
+ 三角形
|
|
|
+ </div>
|
|
|
+ <div class="dropdown-item" onclick="insertShape('arrow')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ <polyline points="12 5 19 12 12 19"/>
|
|
|
+ </svg>
|
|
|
+ 箭头
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 编辑工具(选中元素时显示) -->
|
|
|
+ <div class="toolbar-section" id="editTools" style="display: none;">
|
|
|
+ <button class="toolbar-btn">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
|
+ </svg>
|
|
|
+ 编辑
|
|
|
+ </button>
|
|
|
+ <button class="toolbar-btn" onclick="deleteElement()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ 删除
|
|
|
+ </button>
|
|
|
+ <button class="toolbar-btn">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="8" y="8" width="8" height="8"/>
|
|
|
+ <path d="M4 16c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2"/>
|
|
|
+ </svg>
|
|
|
+ 复制
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="slides-area">
|
|
|
+ <div class="slide-canvas" id="slideCanvas">
|
|
|
+ <div class="slide-placeholder" id="slidePlaceholder">
|
|
|
+ <div class="slide-placeholder-icon">✨</div>
|
|
|
+ <div class="slide-placeholder-text">使用AI生成或创建课程内容</div>
|
|
|
+ <div class="slide-placeholder-hint">描述您的需求,AI将为您创建完整课程</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 工具编辑区域 -->
|
|
|
+ <div class="tool-edit-container" id="toolEditContainer" style="display: none;">
|
|
|
+ <!-- 工具编辑内容将在这里动态生成 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="bottom-outline" id="bottomOutline">
|
|
|
+ <div class="outline-track" id="outlineTrack">
|
|
|
+ <!-- 页面大纲将在这里生成 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧面板 -->
|
|
|
+ <div class="right-panel collapsed" id="rightPanel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <span class="panel-title collapse-text">页面大纲</span>
|
|
|
+ <button class="collapse-btn" onclick="toggleRightPanel()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="15 18 9 12 15 6"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="outline-panel">
|
|
|
+ <div class="outline-toolbar">
|
|
|
+ <div class="outline-toolbar-left">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="4" width="18" height="16" rx="2"/>
|
|
|
+ <line x1="3" y1="10" x2="21" y2="10"/>
|
|
|
+ </svg>
|
|
|
+ <span>大纲</span>
|
|
|
+ </div>
|
|
|
+ <div class="outline-actions">
|
|
|
+ <button class="outline-btn" onclick="addOutlineGroup()">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ 分组
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="outline-list" id="rightOutlineList">
|
|
|
+ <div class="outline-empty">暂无页面,创建后将出现在此</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创建入口弹窗 -->
|
|
|
+ <div class="modal-overlay" id="createModal">
|
|
|
+ <div class="create-modal">
|
|
|
+ <button class="modal-close" onclick="hideCreateModal()">
|
|
|
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>创建新课程</h2>
|
|
|
+ <p>选择一种方式开始创建您的互动课件</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-options">
|
|
|
+ <div class="create-card featured" id="aiCreateCard" onclick="startAICreate()">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>从AI创建</h3>
|
|
|
+ <p>AI自动生成完整教学内容</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-card" id="pptCreateCard" onclick="startPptUploadFlow()" onmouseenter="unsetAiFeatured()">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>上传我的文件</h3>
|
|
|
+ <p>上传本地PPT文件并解析</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-card" onmouseenter="unsetAiFeatured()">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
|
|
|
+ <polyline points="9 22 9 12 15 12 15 22"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>从资源库导入</h3>
|
|
|
+ <p>选择已有的课程模板</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-card" onmouseenter="unsetAiFeatured()" onclick="createBlankCourse()">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
|
+ <polyline points="14 2 14 8 20 8"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>创建空白</h3>
|
|
|
+ <p>从零开始自定义</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 课程设置弹窗 -->
|
|
|
+ <div class="modal-overlay" id="courseSettingsModal">
|
|
|
+ <div class="settings-modal">
|
|
|
+ <div class="settings-header">
|
|
|
+ <div class="settings-title">课程设置</div>
|
|
|
+ <button class="modal-close" onclick="hideCourseSettings()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="settings-body">
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">学科</label>
|
|
|
+ <select class="form-select" id="settingsSubjectSelect">
|
|
|
+ <option value="">请选择学科</option>
|
|
|
+ <option value="chinese">语文</option>
|
|
|
+ <option value="math">数学</option>
|
|
|
+ <option value="english">英语</option>
|
|
|
+ <option value="physics">物理</option>
|
|
|
+ <option value="chemistry">化学</option>
|
|
|
+ <option value="biology">生物</option>
|
|
|
+ <option value="history">历史</option>
|
|
|
+ <option value="geography">地理</option>
|
|
|
+ <option value="politics">道德与法治</option>
|
|
|
+ <option value="science">科学</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">年级</label>
|
|
|
+ <select class="form-select" id="settingsGradeSelect">
|
|
|
+ <option value="">请选择年级</option>
|
|
|
+ <option value="1">一年级</option>
|
|
|
+ <option value="2">二年级</option>
|
|
|
+ <option value="3">三年级</option>
|
|
|
+ <option value="4">四年级</option>
|
|
|
+ <option value="5">五年级</option>
|
|
|
+ <option value="6">六年级</option>
|
|
|
+ <option value="7">七年级</option>
|
|
|
+ <option value="8">八年级</option>
|
|
|
+ <option value="9">九年级</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="settings-actions">
|
|
|
+ <button class="settings-btn" onclick="hideCourseSettings()">取消</button>
|
|
|
+ <button class="settings-btn primary" onclick="applyCourseSettings()">应用</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- PPT上传隐藏输入 -->
|
|
|
+ <input type="file" id="pptFileInput" accept=".ppt,.pptx" style="display: none;" onchange="handlePptFileSelected(event)">
|
|
|
+
|
|
|
+ <!-- PPT解析浮窗 -->
|
|
|
+ <div class="ppt-parse-overlay" id="pptParseOverlay">
|
|
|
+ <div class="ppt-parse-card">
|
|
|
+ <div class="ppt-parse-icon" id="pptParseIcon"></div>
|
|
|
+ <div class="ppt-parse-title" id="pptParseTitle">解析中...</div>
|
|
|
+ <div class="ppt-parse-desc" id="pptParseDesc">正在处理所选PPT文件</div>
|
|
|
+ <div class="ppt-parse-actions" id="pptParseActions">
|
|
|
+ <button class="ppt-parse-btn primary" onclick="closePptParseOverlay()">关闭</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 发布弹窗 -->
|
|
|
+ <div class="modal-overlay" id="publishModal">
|
|
|
+ <div class="publish-modal">
|
|
|
+ <button class="modal-close" onclick="hidePublishModal()">
|
|
|
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div class="publish-header">
|
|
|
+ <h2 class="publish-title">发布课程</h2>
|
|
|
+ <div class="publish-course-name" id="publishCourseNameDisplay" onclick="editCourseName()">
|
|
|
+ 水的三态变化
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="publish-content">
|
|
|
+ <!-- 左侧表单 -->
|
|
|
+ <form class="publish-form" onsubmit="confirmPublish(event)">
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">
|
|
|
+ 学科 <span class="required">*</span>
|
|
|
+ </label>
|
|
|
+ <select class="form-select" id="subjectSelect" required>
|
|
|
+ <option value="">请选择学科</option>
|
|
|
+ <option value="chinese">语文</option>
|
|
|
+ <option value="math">数学</option>
|
|
|
+ <option value="english">英语</option>
|
|
|
+ <option value="physics">物理</option>
|
|
|
+ <option value="chemistry">化学</option>
|
|
|
+ <option value="biology">生物</option>
|
|
|
+ <option value="history">历史</option>
|
|
|
+ <option value="geography">地理</option>
|
|
|
+ <option value="politics">道德与法治</option>
|
|
|
+ <option value="science">科学</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">
|
|
|
+ 年级 <span class="required">*</span>
|
|
|
+ </label>
|
|
|
+ <select class="form-select" id="classGradeSelect" required>
|
|
|
+ <option value="">请选择年级</option>
|
|
|
+ <option value="1">一年级</option>
|
|
|
+ <option value="2">二年级</option>
|
|
|
+ <option value="3">三年级</option>
|
|
|
+ <option value="4">四年级</option>
|
|
|
+ <option value="5">五年级</option>
|
|
|
+ <option value="6">六年级</option>
|
|
|
+ <option value="7">七年级</option>
|
|
|
+ <option value="8">八年级</option>
|
|
|
+ <option value="9">九年级</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">
|
|
|
+ 班级 <span class="required">*</span>
|
|
|
+ </label>
|
|
|
+ <select class="form-select" id="classSelect" required>
|
|
|
+ <option value="">请选择班级</option>
|
|
|
+ <option value="1">1班</option>
|
|
|
+ <option value="2">2班</option>
|
|
|
+ <option value="3">3班</option>
|
|
|
+ <option value="4">4班</option>
|
|
|
+ <option value="5">5班</option>
|
|
|
+ <option value="6">6班</option>
|
|
|
+ <option value="7">7班</option>
|
|
|
+ <option value="8">8班</option>
|
|
|
+ <option value="9">9班</option>
|
|
|
+ <option value="10">10班</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-group">
|
|
|
+ <label class="form-label">
|
|
|
+ 可见范围 <span class="required">*</span>
|
|
|
+ </label>
|
|
|
+ <div class="visibility-options">
|
|
|
+ <label class="radio-option selected" onclick="selectVisibility(this, 'students')">
|
|
|
+ <input type="radio" name="visibility" value="students" checked>
|
|
|
+ <div class="radio-custom"></div>
|
|
|
+ <div class="radio-content">
|
|
|
+ <div class="radio-label">仅发布学生可见</div>
|
|
|
+ <div class="radio-description">仅对发布的班级学生可见,其他人无法访问</div>
|
|
|
+ </div>
|
|
|
+ </label>
|
|
|
+ <label class="radio-option" onclick="selectVisibility(this, 'organization')">
|
|
|
+ <input type="radio" name="visibility" value="organization">
|
|
|
+ <div class="radio-custom"></div>
|
|
|
+ <div class="radio-content">
|
|
|
+ <div class="radio-label">组织可见</div>
|
|
|
+ <div class="radio-description">学校内所有师生均可查看和使用</div>
|
|
|
+ </div>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="publish-actions">
|
|
|
+ <button type="button" class="publish-btn publish-btn-cancel" onclick="hidePublishModal()">
|
|
|
+ 取消
|
|
|
+ </button>
|
|
|
+ <button type="submit" class="publish-btn publish-btn-confirm">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>
|
|
|
+ <polyline points="22 4 12 14.01 9 11.01"/>
|
|
|
+ </svg>
|
|
|
+ 确认发布
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+
|
|
|
+ <!-- 右侧封面 -->
|
|
|
+ <div class="publish-cover-section">
|
|
|
+ <label class="publish-cover-label">课程封面</label>
|
|
|
+ <div class="publish-cover-wrapper">
|
|
|
+ <div class="publish-cover-placeholder" id="coverPlaceholder">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ <p>悬浮选择上传方式</p>
|
|
|
+ </div>
|
|
|
+ <img id="courseCoverImage" style="display: none;">
|
|
|
+
|
|
|
+ <!-- 悬浮菜单 -->
|
|
|
+ <div class="publish-cover-menu">
|
|
|
+ <div class="publish-cover-menu-item" onclick="uploadCourseCoverLocal(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ 自本地上传
|
|
|
+ </div>
|
|
|
+ <div class="publish-cover-menu-item" onclick="searchWebImageForCover(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="11" cy="11" r="8"/>
|
|
|
+ <path d="M21 21l-4.35-4.35"/>
|
|
|
+ </svg>
|
|
|
+ 自网页搜索
|
|
|
+ </div>
|
|
|
+ <div class="publish-cover-menu-item" onclick="generateAIImageForCover(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ 自AI生成
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 已上传封面时显示的操作按钮 -->
|
|
|
+ <div class="publish-cover-actions">
|
|
|
+ <button class="cover-action-btn delete" type="button" onclick="event.stopPropagation(); deleteCourseCover()" title="删除封面">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 隐藏的文件上传输入框 -->
|
|
|
+ <input type="file" id="courseCoverInput" accept="image/*" style="display: none;" onchange="handleCoverUpload(event)">
|
|
|
+
|
|
|
+ <!-- 网页搜索图片浮窗 -->
|
|
|
+ <div class="floating-modal" id="searchImageModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">网页搜索图片</h3>
|
|
|
+ <button class="floating-close" onclick="closeSearchModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="search-input-group">
|
|
|
+ <label class="search-input-label">搜索关键词</label>
|
|
|
+ <input type="text" class="search-input" id="searchKeyword" placeholder="AI已为您生成关键词..." value="水的三态变化 实验">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="search-results" id="searchResults">
|
|
|
+ <!-- 搜索结果将在这里显示 -->
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
|
|
+ </div>
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
|
|
+ </div>
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
|
|
+ </div>
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
|
|
|
+ </div>
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
|
|
+ </div>
|
|
|
+ <div class="search-result-item" style="background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="floating-actions">
|
|
|
+ <button class="floating-btn floating-btn-secondary" onclick="closeSearchModal()">取消</button>
|
|
|
+ <button class="floating-btn floating-btn-primary" onclick="confirmSearchImage()">确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI生成图片浮窗 -->
|
|
|
+ <div class="floating-modal" id="generateImageModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">AI生成图片</h3>
|
|
|
+ <button class="floating-close" onclick="closeGenerateModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="search-input-group">
|
|
|
+ <label class="search-input-label">生成描述</label>
|
|
|
+ <input type="text" class="search-input" id="generatePrompt" placeholder="AI已为您生成描述..." value="水分子在不同温度下的三态变化示意图">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="generate-preview" id="generatePreview">
|
|
|
+ <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ <div class="generate-loading" id="generateLoading">
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
+ <div class="loading-text">AI生成中...</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="floating-actions">
|
|
|
+ <button class="floating-btn floating-btn-secondary" onclick="closeGenerateModal()">取消</button>
|
|
|
+ <button class="floating-btn floating-btn-primary" onclick="startGenerate()">生成</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI应用中心浮窗 -->
|
|
|
+ <div class="floating-modal" id="aiAppCenterModal">
|
|
|
+ <div class="app-center-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">应用中心</h3>
|
|
|
+ <button class="floating-close" onclick="closeAppCenter()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 筛选区 -->
|
|
|
+ <div class="app-filters">
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">应用来源</div>
|
|
|
+ <select class="filter-select" id="sourceFilter" onchange="filterApps()">
|
|
|
+ <option value="all">所有应用</option>
|
|
|
+ <option value="public">公开应用</option>
|
|
|
+ <option value="mine">我的应用</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">应用类型</div>
|
|
|
+ <select class="filter-select" id="typeFilter" onchange="filterApps()">
|
|
|
+ <option value="all">所有类型</option>
|
|
|
+ <option value="agent">智能体</option>
|
|
|
+ <option value="workflow">工作流</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">交互模式</div>
|
|
|
+ <select class="filter-select" id="modeFilter" onchange="filterApps()">
|
|
|
+ <option value="all">所有模式</option>
|
|
|
+ <option value="card">卡片式</option>
|
|
|
+ <option value="immersive">沉浸式</option>
|
|
|
+ <option value="chat">聊天式</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 应用列表 -->
|
|
|
+ <div class="app-grid" id="appGrid">
|
|
|
+ <!-- 应用卡片将在这里动态生成 -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部操作栏 -->
|
|
|
+ <div class="app-center-footer">
|
|
|
+ <div class="selected-count">已选择 <span id="selectedCountText">0</span> 个应用</div>
|
|
|
+ <div class="app-center-actions">
|
|
|
+ <button class="app-center-btn app-center-btn-confirm" id="confirmAddAppsBtn" onclick="confirmAddApps()" disabled>
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ 添加
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模板中心浮窗 -->
|
|
|
+ <div class="floating-modal" id="templateCenterModal">
|
|
|
+ <div class="app-center-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">模板中心</h3>
|
|
|
+ <button class="floating-close" onclick="closeTemplateCenter()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="app-filters">
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">来源</div>
|
|
|
+ <select class="filter-select" id="tplSourceFilter" onchange="filterTemplates()">
|
|
|
+ <option value="all">所有模板</option>
|
|
|
+ <option value="official">官方</option>
|
|
|
+ <option value="mine">我的</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">学科</div>
|
|
|
+ <select class="filter-select" id="tplSubjectFilter" onchange="filterTemplates()">
|
|
|
+ <option value="all">全部学科</option>
|
|
|
+ <option value="chinese">语文</option>
|
|
|
+ <option value="math">数学</option>
|
|
|
+ <option value="english">英语</option>
|
|
|
+ <option value="science">科学</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">年级</div>
|
|
|
+ <select class="filter-select" id="tplGradeFilter" onchange="filterTemplates()">
|
|
|
+ <option value="all">全部年级</option>
|
|
|
+ <option value="1">一年级</option>
|
|
|
+ <option value="2">二年级</option>
|
|
|
+ <option value="3">三年级</option>
|
|
|
+ <option value="4">四年级</option>
|
|
|
+ <option value="5">五年级</option>
|
|
|
+ <option value="6">六年级</option>
|
|
|
+ <option value="7">七年级</option>
|
|
|
+ <option value="8">八年级</option>
|
|
|
+ <option value="9">九年级</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="app-grid" id="templateGrid">
|
|
|
+ <!-- 模板卡片将在这里生成 -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="app-center-footer">
|
|
|
+ <div class="selected-count">已选择 <span id="selectedTemplateCount">0</span> 个模板</div>
|
|
|
+ <div class="app-center-actions">
|
|
|
+ <button class="app-center-btn app-center-btn-confirm" id="confirmApplyTemplateBtn" onclick="confirmApplyTemplates()" disabled>
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ 应用
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 删除课程确认弹窗 -->
|
|
|
+ <div class="modal-overlay" id="deleteCourseModal">
|
|
|
+ <div class="confirm-modal">
|
|
|
+ <div class="confirm-title">删除课程</div>
|
|
|
+ <div class="confirm-text">删除后不可恢复,课件及关联资源将移除,确认要删除当前课程吗?</div>
|
|
|
+ <div class="confirm-actions">
|
|
|
+ <button class="settings-btn" onclick="hideDeleteCourse()">取消</button>
|
|
|
+ <button class="settings-btn primary" onclick="confirmDeleteCourse()">删除</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 网页中心浮窗 -->
|
|
|
+ <div class="floating-modal" id="webCenterModal">
|
|
|
+ <div class="web-center-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">网页中心</h3>
|
|
|
+ <button class="floating-close" onclick="closeWebCenter()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 筛选区 -->
|
|
|
+ <div class="web-filters">
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">网页来源</div>
|
|
|
+ <select class="filter-select" id="webSourceFilter" onchange="filterWebs()">
|
|
|
+ <option value="all">所有网页</option>
|
|
|
+ <option value="public">公开网页</option>
|
|
|
+ <option value="mine">我的网页</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">学科分类</div>
|
|
|
+ <select class="filter-select" id="webSubjectFilter" onchange="filterWebs()">
|
|
|
+ <option value="all">所有学科</option>
|
|
|
+ <option value="语文">语文</option>
|
|
|
+ <option value="数学">数学</option>
|
|
|
+ <option value="英语">英语</option>
|
|
|
+ <option value="物理">物理</option>
|
|
|
+ <option value="化学">化学</option>
|
|
|
+ <option value="生物">生物</option>
|
|
|
+ <option value="历史">历史</option>
|
|
|
+ <option value="地理">地理</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <div class="filter-label">年级分类</div>
|
|
|
+ <select class="filter-select" id="webGradeFilter" onchange="filterWebs()">
|
|
|
+ <option value="all">所有年级</option>
|
|
|
+ <option value="小学">小学</option>
|
|
|
+ <option value="初中">初中</option>
|
|
|
+ <option value="高中">高中</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 网页列表 -->
|
|
|
+ <div class="web-grid" id="webGrid">
|
|
|
+ <!-- 网页卡片将在这里动态生成 -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部操作栏 -->
|
|
|
+ <div class="web-center-footer">
|
|
|
+ <div class="selected-count">已选择 <span id="webSelectedCountText">0</span> 个网页</div>
|
|
|
+ <div class="app-center-actions">
|
|
|
+ <button class="app-center-btn app-center-btn-confirm" id="confirmAddWebsBtn" onclick="confirmAddWebs()" disabled>
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ 添加
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 网页详情浮窗 -->
|
|
|
+ <div class="web-detail-modal" id="webDetailModal">
|
|
|
+ <div class="web-detail-content">
|
|
|
+ <!-- 左侧预览区 -->
|
|
|
+ <div class="web-preview-area">
|
|
|
+ <div class="web-preview-header">
|
|
|
+ <h4 class="web-preview-title" id="webDetailPreviewTitle">网页预览</h4>
|
|
|
+ <button class="web-fullscreen-btn" onclick="toggleWebPreviewFullscreen()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/>
|
|
|
+ </svg>
|
|
|
+ 全屏
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="web-preview-iframe" id="webDetailPreview">
|
|
|
+ <iframe src="" frameborder="0" id="webDetailIframe"></iframe>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧信息区 -->
|
|
|
+ <div class="web-info-area">
|
|
|
+ <div class="web-detail-header">
|
|
|
+ <h3 class="web-detail-name" id="webDetailName">网页名称</h3>
|
|
|
+ <div class="web-meta-item">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
|
|
+ </svg>
|
|
|
+ <span id="webDetailSubject">学科</span>
|
|
|
+ </div>
|
|
|
+ <div class="web-meta-item">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
|
+ </svg>
|
|
|
+ <span id="webDetailGrade">年级</span>
|
|
|
+ </div>
|
|
|
+ <div class="web-meta-item">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"/>
|
|
|
+ <polyline points="12 6 12 12 16 14"/>
|
|
|
+ </svg>
|
|
|
+ <span id="webDetailDuration">时长</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="web-detail-body">
|
|
|
+ <div class="web-detail-section">
|
|
|
+ <h4 class="web-detail-section-title">简介</h4>
|
|
|
+ <p class="web-detail-section-content" id="webDetailDescription">
|
|
|
+ 网页简介内容
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="web-detail-section">
|
|
|
+ <h4 class="web-detail-section-title">作者信息</h4>
|
|
|
+ <p class="web-detail-section-content" id="webDetailAuthor">
|
|
|
+ 作者名称
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="web-detail-section">
|
|
|
+ <h4 class="web-detail-section-title">创建时间</h4>
|
|
|
+ <p class="web-detail-section-content" id="webDetailTime">
|
|
|
+ 创建时间
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="web-detail-footer">
|
|
|
+ <button class="web-detail-btn web-detail-btn-secondary" onclick="closeWebDetail()">返回列表</button>
|
|
|
+ <button class="web-detail-btn web-detail-btn-primary" onclick="addWebFromDetail()">添加到课件</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 资源 - 视频来源弹窗 -->
|
|
|
+ <div class="floating-modal" id="videoSourceModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">选择视频来源</h3>
|
|
|
+ <button class="floating-close" onclick="closeVideoSourceModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="template-grid" style="grid-template-columns: repeat(2, 1fr);">
|
|
|
+ <div class="template-card" onclick="chooseLocalVideo()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 5v14"/>
|
|
|
+ <polyline points="5 12 12 5 19 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">本地上传</div>
|
|
|
+ </div>
|
|
|
+ <div class="template-card" onclick="chooseBilibiliVideo()">
|
|
|
+ <div class="template-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="4" width="18" height="14" rx="2"/>
|
|
|
+ <polyline points="10 9 16 12 10 15 10 9"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="template-name">Bilibili 检索</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 资源 - 音频上传弹窗 -->
|
|
|
+ <div class="floating-modal" id="audioUploadModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">上传音频</h3>
|
|
|
+ <button class="floating-close" onclick="closeAudioUploadModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="web-config-form">
|
|
|
+ <div class="file-upload-area" onclick="document.getElementById('audioFileInput').click()">
|
|
|
+ <input type="file" id="audioFileInput" accept="audio/*" style="display: none;" onchange="handleAudioFile(event)">
|
|
|
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ <p class="upload-text">点击或拖拽音频到此处上传</p>
|
|
|
+ <p class="upload-hint">支持.mp3、.wav等常见音频格式</p>
|
|
|
+ </div>
|
|
|
+ <div id="audioFileNameDisplay" class="file-name-display"></div>
|
|
|
+ <div class="resource-actions">
|
|
|
+ <button class="floating-btn floating-btn-secondary" onclick="closeAudioUploadModal()">取消</button>
|
|
|
+ <button class="floating-btn floating-btn-primary" id="confirmAudioUploadBtn" onclick="confirmAudioUpload()" disabled>确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 资源 - 文档上传弹窗 -->
|
|
|
+ <div class="floating-modal" id="documentUploadModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">上传文档</h3>
|
|
|
+ <button class="floating-close" onclick="closeDocumentUploadModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="web-config-form">
|
|
|
+ <div class="file-upload-area" onclick="document.getElementById('documentFileInput').click()">
|
|
|
+ <input type="file" id="documentFileInput" accept=".docx,.pdf" style="display: none;" onchange="handleDocumentFile(event)">
|
|
|
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ <p class="upload-text">点击或拖拽文档到此处上传</p>
|
|
|
+ <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
|
|
|
+ </div>
|
|
|
+ <div id="documentFileNameDisplay" class="file-name-display"></div>
|
|
|
+ <div class="resource-actions">
|
|
|
+ <button class="floating-btn floating-btn-secondary" onclick="closeDocumentUploadModal()">取消</button>
|
|
|
+ <button class="floating-btn floating-btn-primary" id="confirmDocumentUploadBtn" onclick="confirmDocumentUpload()" disabled>确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 资源 - 资源集合弹窗 -->
|
|
|
+ <div class="floating-modal" id="collectionModal">
|
|
|
+ <div class="floating-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">资源集合</h3>
|
|
|
+ <button class="floating-close" onclick="closeCollectionModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="web-config-form">
|
|
|
+ <div class="file-upload-area" onclick="document.getElementById('collectionFileInput').click()">
|
|
|
+ <input type="file" id="collectionFileInput" multiple style="display: none;" onchange="handleCollectionFiles(event)">
|
|
|
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ <p class="upload-text">点击或拖拽文件到此处上传</p>
|
|
|
+ <p class="upload-hint">支持 .docx、.doc、.pdf格式</p>
|
|
|
+ </div>
|
|
|
+ <div class="resource-upload-list" id="collectionFileList"></div>
|
|
|
+ <div class="resource-tip">最多添加5个文件</div>
|
|
|
+ <div class="resource-actions">
|
|
|
+ <button class="floating-btn floating-btn-secondary" onclick="closeCollectionModal()">取消</button>
|
|
|
+ <button class="floating-btn floating-btn-primary" id="confirmCollectionBtn" onclick="confirmCollection()" disabled>确定</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 创建应用方式选择弹窗 -->
|
|
|
+ <div class="modal-overlay" id="createAppMethodModal">
|
|
|
+ <div class="create-modal">
|
|
|
+ <button class="modal-close" onclick="hideCreateAppMethodModal()">
|
|
|
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>创建应用</h2>
|
|
|
+ <p>选择一种方式开始创建您的AI应用</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-options">
|
|
|
+ <div class="create-card featured" onclick="selectCreateMethod('ai')">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>自AI创建</h3>
|
|
|
+ <p>通过对话让AI为您生成应用</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="create-card" onclick="selectCreateMethod('blank')">
|
|
|
+ <div class="create-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <line x1="12" y1="8" x2="12" y2="16"/>
|
|
|
+ <line x1="8" y1="12" x2="16" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <h3>自空白创建</h3>
|
|
|
+ <p>从空白画布开始手动创建应用</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI创建应用浮窗 -->
|
|
|
+ <div class="floating-modal" id="aiCreateAppModal">
|
|
|
+ <div class="ai-create-app-content">
|
|
|
+ <div class="floating-header">
|
|
|
+ <h3 class="floating-title">AI创建应用</h3>
|
|
|
+ <button class="floating-close" onclick="closeAICreateModal()">
|
|
|
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="ai-create-main">
|
|
|
+ <!-- 左侧画布预览 -->
|
|
|
+ <div class="app-canvas-preview" id="appCanvasPreview">
|
|
|
+ <span>应用画布预览区</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧对话栏 -->
|
|
|
+ <div class="ai-chat-panel">
|
|
|
+ <div class="ai-chat-messages" id="aiChatMessages">
|
|
|
+ <!-- 对话消息将在这里显示 -->
|
|
|
+ </div>
|
|
|
+ <div class="ai-chat-input-area">
|
|
|
+ <div class="ai-chat-input-wrapper">
|
|
|
+ <textarea class="ai-chat-input" id="aiChatInput" placeholder="描述您想要的应用功能..." rows="2"></textarea>
|
|
|
+ <button class="ai-chat-send-btn" id="aiChatSendBtn" onclick="sendMessageToAI()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="22" y1="2" x2="11" y2="13"/>
|
|
|
+ <polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="ai-create-footer">
|
|
|
+ <button class="app-center-btn app-center-btn-cancel" onclick="closeAICreateModal()">取消</button>
|
|
|
+ <button class="app-center-btn app-center-btn-confirm" id="confirmCreateAppBtn" onclick="confirmAICreatedApp()">
|
|
|
+ 确认创建
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ let isEditMode = false;
|
|
|
+ let elementIdCounter = 0;
|
|
|
+ let selectedElement = null;
|
|
|
+ let currentTool = null; // 当前选中的工具
|
|
|
+ let isToolMode = false; // 是否处于工具编辑模式
|
|
|
+
|
|
|
+ // 工具配置数据
|
|
|
+ const toolConfigs = {
|
|
|
+ choice: {
|
|
|
+ name: '选择',
|
|
|
+ icon: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4m0-4h.01"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: false,
|
|
|
+ hasShowAnswer: true
|
|
|
+ },
|
|
|
+ qa: {
|
|
|
+ name: '问答',
|
|
|
+ icon: '<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: true
|
|
|
+ },
|
|
|
+ vote: {
|
|
|
+ name: '投票',
|
|
|
+ icon: '<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: false
|
|
|
+ },
|
|
|
+ photo: {
|
|
|
+ name: '拍照',
|
|
|
+ icon: '<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: true
|
|
|
+ },
|
|
|
+ fillblank: {
|
|
|
+ name: '填空',
|
|
|
+ icon: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: false
|
|
|
+ },
|
|
|
+ sort: {
|
|
|
+ name: '排序',
|
|
|
+ icon: '<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: false
|
|
|
+ },
|
|
|
+ whiteboard: {
|
|
|
+ name: '白板',
|
|
|
+ icon: '<path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: true
|
|
|
+ },
|
|
|
+ flashcard: {
|
|
|
+ name: '抽认卡',
|
|
|
+ icon: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7 15h10M12 9v6"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: false,
|
|
|
+ hasVoting: false,
|
|
|
+ hasLearningMode: true
|
|
|
+ },
|
|
|
+ cocopi: {
|
|
|
+ name: 'CocoPi',
|
|
|
+ icon: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: true
|
|
|
+ },
|
|
|
+ workspace: {
|
|
|
+ name: '创作空间',
|
|
|
+ icon: '<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>',
|
|
|
+ hasTime: true,
|
|
|
+ hasViewWork: true,
|
|
|
+ hasVoting: true
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 选择工具
|
|
|
+ function selectTool(toolType) {
|
|
|
+ currentTool = toolType;
|
|
|
+
|
|
|
+ // 切换到工具编辑模式
|
|
|
+ switchToToolMode(toolType);
|
|
|
+
|
|
|
+ // 显示配置面板
|
|
|
+ showToolConfig(toolType);
|
|
|
+
|
|
|
+ // 显示工具编辑区域
|
|
|
+ showToolEditArea(toolType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换到工具模式
|
|
|
+ function switchToToolMode(toolType) {
|
|
|
+ isToolMode = true;
|
|
|
+
|
|
|
+ // 隐藏普通元素工具栏
|
|
|
+ document.getElementById('elementToolbar').classList.remove('visible');
|
|
|
+ document.getElementById('elementToolbar').style.display = 'none';
|
|
|
+
|
|
|
+ // 显示底部大纲
|
|
|
+ document.getElementById('bottomOutline').classList.add('visible');
|
|
|
+
|
|
|
+ // 隐藏占位符
|
|
|
+ const placeholder = document.getElementById('slidePlaceholder');
|
|
|
+ if (placeholder) {
|
|
|
+ placeholder.style.display = 'none';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果还没有进入编辑模式,则进入
|
|
|
+ if (!isEditMode) {
|
|
|
+ isEditMode = true;
|
|
|
+ if (document.getElementById('outlineTrack').children.length === 0) {
|
|
|
+ generateSamplePages();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示工具配置面板
|
|
|
+ function showToolConfig(toolType) {
|
|
|
+ const config = toolConfigs[toolType];
|
|
|
+
|
|
|
+ // 切换视图
|
|
|
+ document.getElementById('toolsListView').classList.add('hidden');
|
|
|
+ document.getElementById('toolsConfigView').classList.remove('hidden');
|
|
|
+
|
|
|
+ // 更新配置标题
|
|
|
+ document.getElementById('currentToolName').textContent = config.name;
|
|
|
+
|
|
|
+ // 生成配置内容
|
|
|
+ const configContent = document.getElementById('toolConfigContent');
|
|
|
+ let html = '';
|
|
|
+
|
|
|
+ // 查看作业配置
|
|
|
+ if (config.hasViewWork) {
|
|
|
+ html += `
|
|
|
+ <div class="config-section">
|
|
|
+ <div class="config-switch" onclick="toggleSwitch('viewWork')">
|
|
|
+ <span class="config-switch-label">学生查看结果</span>
|
|
|
+ <div class="switch-toggle" id="viewWorkSwitch"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提交后显示答案配置(选择题专用)
|
|
|
+ if (config.hasShowAnswer) {
|
|
|
+ html += `
|
|
|
+ <div class="config-switch" onclick="toggleSwitch('showAnswer')">
|
|
|
+ <span class="config-switch-label">提交后显示答案</span>
|
|
|
+ <div class="switch-toggle" id="showAnswerSwitch"></div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 投票配置
|
|
|
+ if (config.hasVoting) {
|
|
|
+ html += `
|
|
|
+ <div class="config-switch" onclick="toggleSwitch('voting')">
|
|
|
+ <span class="config-switch-label">学生点赞</span>
|
|
|
+ <div class="switch-toggle" id="votingSwitch"></div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 学习模式配置(抽认卡专用)
|
|
|
+ if (config.hasLearningMode) {
|
|
|
+ html += `
|
|
|
+ <div class="config-section">
|
|
|
+ <label class="config-label">学习模式</label>
|
|
|
+ <div class="config-mode-list">
|
|
|
+ <div class="mode-item active" onclick="toggleModeRadio(this, 'random')">
|
|
|
+ <div class="mode-radio">
|
|
|
+ <div class="mode-radio-dot"></div>
|
|
|
+ </div>
|
|
|
+ <span class="mode-label">随机乱序</span>
|
|
|
+ </div>
|
|
|
+ <div class="mode-item" onclick="toggleModeRadio(this, 'autoread')">
|
|
|
+ <div class="mode-radio">
|
|
|
+ <div class="mode-radio-dot"></div>
|
|
|
+ </div>
|
|
|
+ <span class="mode-label">自动朗读</span>
|
|
|
+ </div>
|
|
|
+ <div class="mode-item" onclick="toggleModeRadio(this, 'flip')">
|
|
|
+ <div class="mode-radio">
|
|
|
+ <div class="mode-radio-dot"></div>
|
|
|
+ </div>
|
|
|
+ <span class="mode-label">正反对调</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ configContent.innerHTML = html;
|
|
|
+
|
|
|
+ // 隐藏顶部标题栏
|
|
|
+ const panelHeader = document.querySelector('#toolsPanel .panel-header');
|
|
|
+ if (panelHeader) {
|
|
|
+ panelHeader.style.display = 'none';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示工具编辑区域
|
|
|
+ function showToolEditArea(toolType) {
|
|
|
+ const toolEditContainer = document.getElementById('toolEditContainer');
|
|
|
+ const slideCanvas = document.getElementById('slideCanvas');
|
|
|
+ const config = toolConfigs[toolType];
|
|
|
+
|
|
|
+ // 隐藏slide-canvas,显示工具编辑容器
|
|
|
+ if (slideCanvas) {
|
|
|
+ slideCanvas.style.display = 'none';
|
|
|
+ }
|
|
|
+ if (toolEditContainer) {
|
|
|
+ toolEditContainer.style.display = 'flex';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建工具编辑区域HTML
|
|
|
+ let html = `
|
|
|
+ <div class="tool-header">
|
|
|
+ <div class="tool-type-selector" id="toolTypeSelector">
|
|
|
+ <button class="tool-type-btn" onclick="toggleToolTypeDropdown()">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ ${config.icon}
|
|
|
+ </svg>
|
|
|
+ <span>${config.name}</span>
|
|
|
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="6 9 12 15 18 9"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <div class="tool-type-dropdown">
|
|
|
+ ${Object.keys(toolConfigs).map(key => {
|
|
|
+ const cfg = toolConfigs[key];
|
|
|
+ return `
|
|
|
+ <div class="tool-type-item ${key === toolType ? 'active' : ''}" onclick="switchToolType('${key}')">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ ${cfg.icon}
|
|
|
+ </svg>
|
|
|
+ <span>${cfg.name}</span>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }).join('')}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="tool-edit-area">
|
|
|
+ <div class="tool-canvas" id="toolCanvas">
|
|
|
+ ${generateToolContent(toolType)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ toolEditContainer.innerHTML = html;
|
|
|
+
|
|
|
+ // 如果是排序工具,初始化拖动功能
|
|
|
+ if (toolType === 'sort') {
|
|
|
+ setTimeout(initSortDragAndDrop, 100);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成工具内容
|
|
|
+ function generateToolContent(toolType) {
|
|
|
+ switch(toolType) {
|
|
|
+ case 'choice':
|
|
|
+ return generateChoiceContent();
|
|
|
+ case 'qa':
|
|
|
+ return generateQAContent();
|
|
|
+ case 'vote':
|
|
|
+ return generateVoteContent();
|
|
|
+ case 'photo':
|
|
|
+ return generatePhotoContent();
|
|
|
+ case 'fillblank':
|
|
|
+ return generateFillBlankContent();
|
|
|
+ case 'sort':
|
|
|
+ return generateSortContent();
|
|
|
+ case 'whiteboard':
|
|
|
+ return generateWhiteboardContent();
|
|
|
+ case 'flashcard':
|
|
|
+ return generateFlashcardContent();
|
|
|
+ case 'cocopi':
|
|
|
+ return generateCocoPiContent();
|
|
|
+ case 'workspace':
|
|
|
+ return generateWorkspaceContent();
|
|
|
+ default:
|
|
|
+ return '<div>工具内容加载中...</div>';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成选择题内容
|
|
|
+ function generateChoiceContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">题目 1</span>
|
|
|
+ <div class="question-actions">
|
|
|
+ <button class="question-action-btn" onclick="copyQuestion(this)" title="复制题目">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
|
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="question-action-btn delete" onclick="deleteQuestion(this)" title="删除题目">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="question-input" placeholder="输入题目内容...">水在多少摄氏度会结冰?</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="options-list">
|
|
|
+ <div class="option-item">
|
|
|
+ <div class="option-drag-handle" title="拖动排序">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="option-checkbox checked" onclick="toggleOption(this)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <input type="text" class="option-input" placeholder="选项A" value="0°C">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="option-item">
|
|
|
+ <div class="option-drag-handle" title="拖动排序">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="option-checkbox" onclick="toggleOption(this)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <input type="text" class="option-input" placeholder="选项B" value="100°C">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="option-item">
|
|
|
+ <div class="option-drag-handle" title="拖动排序">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="option-checkbox" onclick="toggleOption(this)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <input type="text" class="option-input" placeholder="选项C" value="50°C">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>选项</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="explanation-section">
|
|
|
+ <div class="explanation-header">
|
|
|
+ <span class="explanation-label">解释说明</span>
|
|
|
+ <button class="ai-gen-btn" onclick="generateExplanation()">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ AI生成
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <textarea class="explanation-textarea" placeholder="为这道题目添加解释说明..."></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>题目</span>
|
|
|
+ </button>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成问答内容
|
|
|
+ function generateQAContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">题目 1</span>
|
|
|
+ </div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="question-input" placeholder="输入题目内容...">请描述水的三态变化过程</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="explanation-section">
|
|
|
+ <div class="explanation-header">
|
|
|
+ <span class="explanation-label">评价标准</span>
|
|
|
+ </div>
|
|
|
+ <textarea class="explanation-textarea" placeholder="设置评价标准,如:必须包括/避免..."></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成投票内容
|
|
|
+ function generateVoteContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">投票主题</span>
|
|
|
+ </div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="question-input" placeholder="输入投票主题...">你最喜欢哪种状态的水?</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="options-list">
|
|
|
+ <div class="option-item">
|
|
|
+ <input type="text" class="option-input" placeholder="选项1" value="固态(冰)">
|
|
|
+ </div>
|
|
|
+ <div class="option-item">
|
|
|
+ <input type="text" class="option-input" placeholder="选项2" value="液态(水)">
|
|
|
+ </div>
|
|
|
+ <div class="option-item">
|
|
|
+ <input type="text" class="option-input" placeholder="选项3" value="气态(水蒸气)">
|
|
|
+ </div>
|
|
|
+ <button class="add-option-btn-with-text" onclick="addOption(this)" title="添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>选项</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成拍照内容
|
|
|
+ function generatePhotoContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">拍照指引</span>
|
|
|
+ </div>
|
|
|
+ <textarea class="question-input" placeholder="输入拍照指引内容...">请拍摄家中水的不同状态</textarea>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成填空内容
|
|
|
+ function generateFillBlankContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">题目 1</span>
|
|
|
+ </div>
|
|
|
+ <div class="question-input-wrapper">
|
|
|
+ <textarea class="question-input" placeholder="输入题目内容,使用___表示填空...">水在___°C会结冰,在___°C会沸腾。</textarea>
|
|
|
+ <button class="add-blank-btn" onclick="insertBlank(this)" title="插入填空符">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12" stroke-dasharray="2,2"/>
|
|
|
+ </svg>
|
|
|
+ <span>填空符</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="explanation-section">
|
|
|
+ <div class="explanation-header">
|
|
|
+ <span class="explanation-label">参考答案</span>
|
|
|
+ </div>
|
|
|
+ <textarea class="explanation-textarea" placeholder="输入参考答案...">0, 100</textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>题目</span>
|
|
|
+ </button>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成排序内容
|
|
|
+ function generateSortContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">题目 1</span>
|
|
|
+ </div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="question-input" placeholder="输入题目内容...">将水的状态变化过程按顺序排列</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="options-list">
|
|
|
+ <div class="sort-item-static">
|
|
|
+ <div class="sort-number">1</div>
|
|
|
+ <input type="text" class="option-input" placeholder="片段1" value="冰融化">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="sort-item-static">
|
|
|
+ <div class="sort-number">2</div>
|
|
|
+ <input type="text" class="option-input" placeholder="片段2" value="水加热">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="sort-item-static">
|
|
|
+ <div class="sort-number">3</div>
|
|
|
+ <input type="text" class="option-input" placeholder="片段3" value="水沸腾">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-option-btn-with-text" onclick="addSortOption(this)" title="添加片段">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>片段</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="explanation-section">
|
|
|
+ <div class="explanation-header">
|
|
|
+ <span class="explanation-label">正确排序</span>
|
|
|
+ </div>
|
|
|
+ <div class="sort-order-display" id="sortOrderDisplay">
|
|
|
+ <div class="sort-order-item draggable" draggable="true" data-order="1">
|
|
|
+ <div class="sort-drag-handle">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <span>1</span>
|
|
|
+ </div>
|
|
|
+ <div class="sort-arrow">→</div>
|
|
|
+ <div class="sort-order-item draggable" draggable="true" data-order="2">
|
|
|
+ <div class="sort-drag-handle">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <span>2</span>
|
|
|
+ </div>
|
|
|
+ <div class="sort-arrow">→</div>
|
|
|
+ <div class="sort-order-item draggable" draggable="true" data-order="3">
|
|
|
+ <div class="sort-drag-handle">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <span>3</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-question-btn-with-text" onclick="addQuestion()" title="添加题目">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>题目</span>
|
|
|
+ </button>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成白板内容
|
|
|
+ function generateWhiteboardContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">白板主题</span>
|
|
|
+ </div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="question-input" placeholder="输入白板主题...">画出水的三态变化示意图</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成抽认卡内容
|
|
|
+ function generateFlashcardContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="flashcard-layout">
|
|
|
+ <div class="flashcard-side">
|
|
|
+ <div class="flashcard-side-label">正面</div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="flashcard-content" placeholder="输入正面内容...">水的固态叫什么?</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flashcard-side">
|
|
|
+ <div class="flashcard-side-label">背面</div>
|
|
|
+ <div class="input-with-image">
|
|
|
+ <textarea class="flashcard-content" placeholder="输入背面内容...">冰</textarea>
|
|
|
+ <button class="upload-image-btn" onclick="uploadImage(this)" title="上传图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="add-question-btn-with-text" onclick="addFlashcard()" title="添加卡片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ <span>卡片</span>
|
|
|
+ </button>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成CocoPi内容
|
|
|
+ function generateCocoPiContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">编程任务</span>
|
|
|
+ </div>
|
|
|
+ <textarea class="question-input" placeholder="输入编程任务描述...">使用温度传感器监测水的温度变化</textarea>
|
|
|
+ <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
|
|
|
+ <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
|
|
|
+ <polyline points="16 18 22 12 16 6"/>
|
|
|
+ <polyline points="8 6 2 12 8 18"/>
|
|
|
+ </svg>
|
|
|
+ <div>CocoPi编程界面占位</div>
|
|
|
+ <div style="font-size: 12px; margin-top: 8px;">点击加载CocoPi编程模块</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成创作空间内容
|
|
|
+ function generateWorkspaceContent() {
|
|
|
+ return `
|
|
|
+ <div class="question-item">
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-number">创作任务</span>
|
|
|
+ </div>
|
|
|
+ <textarea class="question-input" placeholder="输入创作任务描述...">创建一个介绍水循环的智能体</textarea>
|
|
|
+ <div style="margin-top: 20px; padding: 40px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280;">
|
|
|
+ <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="margin: 0 auto 16px;">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ <div>CocoFlow创作空间占位</div>
|
|
|
+ <div style="font-size: 12px; margin-top: 8px;">点击进入CocoFlow创作界面</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回工具列表
|
|
|
+ function backToToolsList() {
|
|
|
+ document.getElementById('toolsListView').classList.remove('hidden');
|
|
|
+ document.getElementById('toolsConfigView').classList.add('hidden');
|
|
|
+
|
|
|
+ // 显示顶部标题栏
|
|
|
+ const panelHeader = document.querySelector('#toolsPanel .panel-header');
|
|
|
+ if (panelHeader) {
|
|
|
+ panelHeader.style.display = 'flex';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复正常的slide显示
|
|
|
+ function restoreNormalView() {
|
|
|
+ const toolEditContainer = document.getElementById('toolEditContainer');
|
|
|
+ const slideCanvas = document.getElementById('slideCanvas');
|
|
|
+
|
|
|
+ if (toolEditContainer) {
|
|
|
+ toolEditContainer.style.display = 'none';
|
|
|
+ }
|
|
|
+ if (slideCanvas) {
|
|
|
+ slideCanvas.style.display = 'flex';
|
|
|
+ }
|
|
|
+
|
|
|
+ isToolMode = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 资源功能 ==========
|
|
|
+ let audioSelectedFile = null;
|
|
|
+ let documentSelectedFile = null;
|
|
|
+ let collectionFiles = [];
|
|
|
+
|
|
|
+ function openVideoSourceModal() {
|
|
|
+ document.getElementById('videoSourceModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeVideoSourceModal() {
|
|
|
+ document.getElementById('videoSourceModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function chooseLocalVideo() {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = 'video/*';
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const file = e.target.files[0];
|
|
|
+ if (file) {
|
|
|
+ closeVideoSourceModal();
|
|
|
+ addResourcePage('视频', file.name || '视频');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ function chooseBilibiliVideo() {
|
|
|
+ closeVideoSourceModal();
|
|
|
+ alert('已选择来自 Bilibili 的视频占位');
|
|
|
+ addResourcePage('视频', 'Bilibili 视频');
|
|
|
+ }
|
|
|
+
|
|
|
+ function openAudioUploadModal() {
|
|
|
+ document.getElementById('audioFileInput').value = '';
|
|
|
+ document.getElementById('audioFileNameDisplay').style.display = 'none';
|
|
|
+ document.getElementById('confirmAudioUploadBtn').disabled = true;
|
|
|
+ audioSelectedFile = null;
|
|
|
+ document.getElementById('audioUploadModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleAudioFile(event) {
|
|
|
+ audioSelectedFile = event.target.files[0] || null;
|
|
|
+ const display = document.getElementById('audioFileNameDisplay');
|
|
|
+ if (audioSelectedFile) {
|
|
|
+ display.textContent = audioSelectedFile.name;
|
|
|
+ display.style.display = 'block';
|
|
|
+ } else {
|
|
|
+ display.textContent = '';
|
|
|
+ display.style.display = 'none';
|
|
|
+ }
|
|
|
+ document.getElementById('confirmAudioUploadBtn').disabled = !audioSelectedFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmAudioUpload() {
|
|
|
+ if (!audioSelectedFile) return;
|
|
|
+ addResourcePage('音频', audioSelectedFile.name || '音频');
|
|
|
+ closeAudioUploadModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeAudioUploadModal() {
|
|
|
+ audioSelectedFile = null;
|
|
|
+ document.getElementById('audioUploadModal').classList.remove('active');
|
|
|
+ document.getElementById('confirmAudioUploadBtn').disabled = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function openDocumentUploadModal() {
|
|
|
+ document.getElementById('documentFileInput').value = '';
|
|
|
+ document.getElementById('documentFileNameDisplay').style.display = 'none';
|
|
|
+ document.getElementById('confirmDocumentUploadBtn').disabled = true;
|
|
|
+ documentSelectedFile = null;
|
|
|
+ document.getElementById('documentUploadModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDocumentFile(event) {
|
|
|
+ documentSelectedFile = event.target.files[0] || null;
|
|
|
+ const display = document.getElementById('documentFileNameDisplay');
|
|
|
+ if (documentSelectedFile) {
|
|
|
+ display.textContent = documentSelectedFile.name;
|
|
|
+ display.style.display = 'block';
|
|
|
+ } else {
|
|
|
+ display.textContent = '';
|
|
|
+ display.style.display = 'none';
|
|
|
+ }
|
|
|
+ document.getElementById('confirmDocumentUploadBtn').disabled = !documentSelectedFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmDocumentUpload() {
|
|
|
+ if (!documentSelectedFile) return;
|
|
|
+ addResourcePage('文档', documentSelectedFile.name || '文档');
|
|
|
+ closeDocumentUploadModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeDocumentUploadModal() {
|
|
|
+ documentSelectedFile = null;
|
|
|
+ document.getElementById('documentUploadModal').classList.remove('active');
|
|
|
+ document.getElementById('confirmDocumentUploadBtn').disabled = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function openCollectionModal() {
|
|
|
+ collectionFiles = [];
|
|
|
+ renderCollectionList();
|
|
|
+ document.getElementById('collectionModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeCollectionModal() {
|
|
|
+ collectionFiles = [];
|
|
|
+ renderCollectionList();
|
|
|
+ document.getElementById('collectionModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleCollectionFiles(event) {
|
|
|
+ const newFiles = Array.from(event.target.files || []);
|
|
|
+ newFiles.forEach(file => {
|
|
|
+ if (collectionFiles.length < 5) {
|
|
|
+ collectionFiles.push(file);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ event.target.value = '';
|
|
|
+ renderCollectionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderCollectionList() {
|
|
|
+ const list = document.getElementById('collectionFileList');
|
|
|
+ if (!list) return;
|
|
|
+ list.innerHTML = collectionFiles.map((file, index) => `
|
|
|
+ <div class="resource-file-item">
|
|
|
+ <div class="resource-file-name">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
|
+ <polyline points="14 2 14 8 20 8"/>
|
|
|
+ </svg>
|
|
|
+ <span title="${file.name}">${file.name}</span>
|
|
|
+ </div>
|
|
|
+ <button class="resource-remove-btn" onclick="removeCollectionFile(${index})">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ document.getElementById('confirmCollectionBtn').disabled = collectionFiles.length === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeCollectionFile(index) {
|
|
|
+ collectionFiles.splice(index, 1);
|
|
|
+ renderCollectionList();
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmCollection() {
|
|
|
+ if (collectionFiles.length === 0) return;
|
|
|
+ const filesSnapshot = [...collectionFiles];
|
|
|
+ addResourcePage('资源集合', `资源集合 (${collectionFiles.length})`, filesSnapshot);
|
|
|
+ closeCollectionModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ function addResourcePage(resourceType, label, files = []) {
|
|
|
+ if (!isEditMode) {
|
|
|
+ switchToEditMode();
|
|
|
+ } else {
|
|
|
+ document.getElementById('elementToolbar').classList.add('visible');
|
|
|
+ document.getElementById('bottomOutline').classList.add('visible');
|
|
|
+ const placeholder = document.getElementById('slidePlaceholder');
|
|
|
+ if (placeholder) placeholder.style.display = 'none';
|
|
|
+ }
|
|
|
+
|
|
|
+ const outlineTrack = document.getElementById('outlineTrack');
|
|
|
+ const pageIndex = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
|
|
|
+
|
|
|
+ const wrapper = document.createElement('div');
|
|
|
+ wrapper.className = 'outline-item-wrapper';
|
|
|
+
|
|
|
+ const outlineItem = document.createElement('div');
|
|
|
+ outlineItem.className = 'outline-item active';
|
|
|
+ outlineItem.draggable = true;
|
|
|
+ outlineItem.dataset.pageIndex = pageIndex;
|
|
|
+ outlineItem.onclick = (e) => selectPage(pageIndex, e);
|
|
|
+ outlineItem.innerHTML = `
|
|
|
+ <span class="page-number">${pageIndex}</span>
|
|
|
+ <span>${label}</span>
|
|
|
+ <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageIndex})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="1"/>
|
|
|
+ <circle cx="12" cy="5" r="1"/>
|
|
|
+ <circle cx="12" cy="19" r="1"/>
|
|
|
+ </svg>
|
|
|
+ <div class="page-menu-dropdown">
|
|
|
+ <div class="page-menu-item" onclick="copyPage(${pageIndex})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
|
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
|
+ </svg>
|
|
|
+ 复制
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item" onclick="addBlankPage(${pageIndex})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ 新增
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item danger" onclick="deletePage(${pageIndex})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ 删除
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ outlineItem.addEventListener('dragstart', handleDragStart);
|
|
|
+ outlineItem.addEventListener('dragend', handleDragEnd);
|
|
|
+ outlineItem.addEventListener('dragover', handleDragOver);
|
|
|
+ outlineItem.addEventListener('drop', handleDrop);
|
|
|
+ outlineItem.addEventListener('dragleave', handleDragLeave);
|
|
|
+
|
|
|
+ document.querySelectorAll('.outline-item').forEach(item => item.classList.remove('active'));
|
|
|
+
|
|
|
+ const addBtn = document.createElement('button');
|
|
|
+ addBtn.className = 'add-page-between';
|
|
|
+ addBtn.textContent = '+';
|
|
|
+ addBtn.onclick = () => addPageBetween(pageIndex);
|
|
|
+
|
|
|
+ wrapper.appendChild(outlineItem);
|
|
|
+ wrapper.appendChild(addBtn);
|
|
|
+ outlineTrack.appendChild(wrapper);
|
|
|
+
|
|
|
+ syncRightOutlineWithBottom();
|
|
|
+ renderResourceCanvas(resourceType, label, files);
|
|
|
+ alert('已添加资源页面: ' + label);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderResourceCanvas(resourceType, label, files = []) {
|
|
|
+ const canvas = document.getElementById('slideCanvas');
|
|
|
+ const placeholder = document.getElementById('slidePlaceholder');
|
|
|
+ if (placeholder) {
|
|
|
+ placeholder.style.display = 'none';
|
|
|
+ }
|
|
|
+ const elementToolbar = document.getElementById('elementToolbar');
|
|
|
+ if (elementToolbar) {
|
|
|
+ elementToolbar.classList.remove('visible');
|
|
|
+ elementToolbar.style.display = 'none';
|
|
|
+ }
|
|
|
+ document.getElementById('bottomOutline').classList.add('visible');
|
|
|
+
|
|
|
+ const icons = {
|
|
|
+ '视频': '▶️',
|
|
|
+ '音频': '🎵',
|
|
|
+ '文档': '📄',
|
|
|
+ '资源集合': '📚'
|
|
|
+ };
|
|
|
+ const descMap = {
|
|
|
+ '视频': '全屏视频播放器占位',
|
|
|
+ '音频': '音频播放条占位',
|
|
|
+ '文档': '文档阅读器占位',
|
|
|
+ '资源集合': '顶部标签可切换的资源集合占位'
|
|
|
+ };
|
|
|
+
|
|
|
+ const showTabs = resourceType === '资源集合';
|
|
|
+ const tabItems = showTabs
|
|
|
+ ? (files.length > 0 ? files.slice(0, 5).map((f, idx) => f.name || `资源${idx + 1}`) : ['资源1', '资源2', '资源3'])
|
|
|
+ : [];
|
|
|
+ const tabsHtml = showTabs ? `<div class="resource-tabs">${tabItems.map((name, idx) => `<div class="resource-tab ${idx === 0 ? 'active' : ''}">${name}</div>`).join('')}</div>` : '';
|
|
|
+
|
|
|
+ canvas.innerHTML = `
|
|
|
+ <div style="width: 100%; height: 100%; display: flex; flex-direction: column;">
|
|
|
+ ${tabsHtml}
|
|
|
+ <div style="flex: 1; display: flex; align-items: center; justify-content: center;">
|
|
|
+ <div style="width: 80%; background: #fafbfc; border: 2px dashed #e5e7eb; border-radius: 16px; padding: 32px; text-align: center;">
|
|
|
+ <div style="font-size: 42px; margin-bottom: 12px;">${icons[resourceType] || '📦'}</div>
|
|
|
+ <div style="font-size: 18px; font-weight: 700; color: #111827; margin-bottom: 6px;">${label}</div>
|
|
|
+ <div style="font-size: 13px; color: #6b7280;">${descMap[resourceType] || '资源内容占位'}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换开关
|
|
|
+ function toggleSwitch(switchType) {
|
|
|
+ event.stopPropagation();
|
|
|
+ const switchEl = document.getElementById(switchType + 'Switch');
|
|
|
+ if (switchEl) {
|
|
|
+ // 如果是关闭"学生查看结果",需要同时关闭"学生点赞"
|
|
|
+ if (switchType === 'viewWork' && switchEl.classList.contains('active')) {
|
|
|
+ const votingSwitch = document.getElementById('votingSwitch');
|
|
|
+ if (votingSwitch && votingSwitch.classList.contains('active')) {
|
|
|
+ votingSwitch.classList.remove('active');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是打开"学生点赞",需要同时打开"学生查看结果"
|
|
|
+ if (switchType === 'voting' && !switchEl.classList.contains('active')) {
|
|
|
+ const viewWorkSwitch = document.getElementById('viewWorkSwitch');
|
|
|
+ if (viewWorkSwitch && !viewWorkSwitch.classList.contains('active')) {
|
|
|
+ viewWorkSwitch.classList.add('active');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switchEl.classList.toggle('active');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换模式(多选)
|
|
|
+ function toggleMode(element, mode) {
|
|
|
+ element.classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换模式(单选)
|
|
|
+ function toggleModeRadio(element, mode) {
|
|
|
+ const parent = element.parentElement;
|
|
|
+ parent.querySelectorAll('.mode-item').forEach(item => {
|
|
|
+ item.classList.remove('active');
|
|
|
+ });
|
|
|
+ element.classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入填空符
|
|
|
+ function insertBlank(button) {
|
|
|
+ const wrapper = button.closest('.question-input-wrapper');
|
|
|
+ const textarea = wrapper.querySelector('.question-input');
|
|
|
+ const cursorPos = textarea.selectionStart;
|
|
|
+ const textBefore = textarea.value.substring(0, cursorPos);
|
|
|
+ const textAfter = textarea.value.substring(cursorPos);
|
|
|
+ textarea.value = textBefore + '___' + textAfter;
|
|
|
+ textarea.focus();
|
|
|
+ textarea.setSelectionRange(cursorPos + 3, cursorPos + 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加排序选项
|
|
|
+ function addSortOption(button) {
|
|
|
+ const optionsList = button.parentElement;
|
|
|
+ const currentItems = optionsList.querySelectorAll('.sort-item-static');
|
|
|
+ const nextNumber = currentItems.length + 1;
|
|
|
+
|
|
|
+ const sortItem = document.createElement('div');
|
|
|
+ sortItem.className = 'sort-item-static';
|
|
|
+ sortItem.innerHTML = `
|
|
|
+ <div class="sort-number">${nextNumber}</div>
|
|
|
+ <input type="text" class="option-input" placeholder="片段${nextNumber}">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn delete" onclick="deleteSortOption(this)" title="删除片段">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ optionsList.insertBefore(sortItem, button);
|
|
|
+
|
|
|
+ // 更新正确排序显示
|
|
|
+ updateSortOrderDisplay();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除排序选项
|
|
|
+ function deleteSortOption(button) {
|
|
|
+ const sortItem = button.closest('.sort-item-static');
|
|
|
+ const optionsList = sortItem.parentElement;
|
|
|
+ const allItems = optionsList.querySelectorAll('.sort-item-static');
|
|
|
+
|
|
|
+ // 至少保留2个选项
|
|
|
+ if (allItems.length <= 2) {
|
|
|
+ alert('至少需要保留两个片段');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ sortItem.remove();
|
|
|
+
|
|
|
+ // 重新编号
|
|
|
+ const remainingItems = optionsList.querySelectorAll('.sort-item-static');
|
|
|
+ remainingItems.forEach((item, index) => {
|
|
|
+ const numberEl = item.querySelector('.sort-number');
|
|
|
+ const inputEl = item.querySelector('.option-input');
|
|
|
+ numberEl.textContent = index + 1;
|
|
|
+ if (inputEl.placeholder) {
|
|
|
+ inputEl.placeholder = `片段${index + 1}`;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新正确排序显示
|
|
|
+ updateSortOrderDisplay();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新排序顺序显示
|
|
|
+ function updateSortOrderDisplay() {
|
|
|
+ const sortItems = document.querySelectorAll('.sort-item-static');
|
|
|
+ const display = document.querySelector('.sort-order-display');
|
|
|
+ if (!display) return;
|
|
|
+
|
|
|
+ let html = '';
|
|
|
+ sortItems.forEach((item, index) => {
|
|
|
+ const number = item.querySelector('.sort-number').textContent;
|
|
|
+ html += `
|
|
|
+ <div class="sort-order-item draggable" draggable="true" data-order="${number}">
|
|
|
+ <div class="sort-drag-handle">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <span>${number}</span>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ if (index < sortItems.length - 1) {
|
|
|
+ html += '<div class="sort-arrow">→</div>';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ display.innerHTML = html;
|
|
|
+
|
|
|
+ // 重新初始化拖动事件
|
|
|
+ initSortDragAndDrop();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化排序拖动功能
|
|
|
+ function initSortDragAndDrop() {
|
|
|
+ const draggables = document.querySelectorAll('.sort-order-item.draggable');
|
|
|
+ const container = document.querySelector('.sort-order-display');
|
|
|
+
|
|
|
+ if (!container) return;
|
|
|
+
|
|
|
+ let draggedElement = null;
|
|
|
+
|
|
|
+ draggables.forEach(item => {
|
|
|
+ item.addEventListener('dragstart', function(e) {
|
|
|
+ draggedElement = this;
|
|
|
+ this.classList.add('dragging');
|
|
|
+ e.dataTransfer.effectAllowed = 'move';
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('dragend', function(e) {
|
|
|
+ this.classList.remove('dragging');
|
|
|
+ draggables.forEach(el => el.classList.remove('drag-over'));
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('dragover', function(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ e.dataTransfer.dropEffect = 'move';
|
|
|
+
|
|
|
+ if (this !== draggedElement) {
|
|
|
+ this.classList.add('drag-over');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('dragleave', function(e) {
|
|
|
+ this.classList.remove('drag-over');
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('drop', function(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ this.classList.remove('drag-over');
|
|
|
+
|
|
|
+ if (this !== draggedElement) {
|
|
|
+ // 交换元素
|
|
|
+ const allItems = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
|
|
|
+ const draggedIndex = allItems.indexOf(draggedElement);
|
|
|
+ const targetIndex = allItems.indexOf(this);
|
|
|
+
|
|
|
+ if (draggedIndex < targetIndex) {
|
|
|
+ this.parentNode.insertBefore(draggedElement, this.nextSibling);
|
|
|
+ } else {
|
|
|
+ this.parentNode.insertBefore(draggedElement, this);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重建箭头
|
|
|
+ rebuildSortArrows();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重建排序箭头
|
|
|
+ function rebuildSortArrows() {
|
|
|
+ const container = document.querySelector('.sort-order-display');
|
|
|
+ if (!container) return;
|
|
|
+
|
|
|
+ const items = Array.from(container.querySelectorAll('.sort-order-item.draggable'));
|
|
|
+ const arrows = Array.from(container.querySelectorAll('.sort-arrow'));
|
|
|
+
|
|
|
+ // 移除所有箭头
|
|
|
+ arrows.forEach(arrow => arrow.remove());
|
|
|
+
|
|
|
+ // 重新插入箭头
|
|
|
+ items.forEach((item, index) => {
|
|
|
+ if (index < items.length - 1) {
|
|
|
+ const arrow = document.createElement('div');
|
|
|
+ arrow.className = 'sort-arrow';
|
|
|
+ arrow.textContent = '→';
|
|
|
+ item.parentNode.insertBefore(arrow, item.nextSibling);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面加载后初始化拖动
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ // 延迟初始化,等待工具内容加载
|
|
|
+ setTimeout(initSortDragAndDrop, 500);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 图片上传函数
|
|
|
+ function uploadImage(button) {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = 'image/*';
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const file = e.target.files[0];
|
|
|
+ if (file) {
|
|
|
+ // 创建图片预览
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = function(event) {
|
|
|
+ const wrapper = button.closest('.input-with-image');
|
|
|
+ const textarea = wrapper.querySelector('textarea, .option-input');
|
|
|
+
|
|
|
+ // 在textarea下方插入图片预览
|
|
|
+ let preview = wrapper.querySelector('.image-preview');
|
|
|
+ if (!preview) {
|
|
|
+ preview = document.createElement('div');
|
|
|
+ preview.className = 'image-preview';
|
|
|
+ textarea.parentNode.insertBefore(preview, button);
|
|
|
+ }
|
|
|
+
|
|
|
+ preview.innerHTML = `
|
|
|
+ <img src="${event.target.result}" alt="预览图片">
|
|
|
+ <button class="remove-image-btn" onclick="removeImage(this)" title="删除图片">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ `;
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除图片
|
|
|
+ function removeImage(button) {
|
|
|
+ const preview = button.closest('.image-preview');
|
|
|
+ if (preview) {
|
|
|
+ preview.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换工具类型下拉菜单
|
|
|
+ function toggleToolTypeDropdown() {
|
|
|
+ event.stopPropagation();
|
|
|
+ document.getElementById('toolTypeSelector').classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换工具类型
|
|
|
+ function switchToolType(toolType) {
|
|
|
+ currentTool = toolType;
|
|
|
+ showToolConfig(toolType);
|
|
|
+ showToolEditArea(toolType);
|
|
|
+ document.getElementById('toolTypeSelector').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换题目类型(单选/多选)
|
|
|
+ function toggleQuestionType(toggle) {
|
|
|
+ toggle.classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换选项选中状态
|
|
|
+ function toggleOption(checkbox) {
|
|
|
+ checkbox.classList.toggle('checked');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加选项
|
|
|
+ function addOption(button) {
|
|
|
+ const optionsList = button.parentElement;
|
|
|
+ const optionItem = document.createElement('div');
|
|
|
+ optionItem.className = 'option-item';
|
|
|
+ optionItem.innerHTML = `
|
|
|
+ <div class="option-drag-handle" title="拖动排序">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="option-checkbox" onclick="toggleOption(this)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <input type="text" class="option-input" placeholder="新选项">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ optionsList.insertBefore(optionItem, button);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在指定选项后添加新选项
|
|
|
+ function addOptionAfter(button) {
|
|
|
+ const currentOption = button.closest('.option-item');
|
|
|
+ const optionsList = currentOption.parentElement;
|
|
|
+ const newOption = document.createElement('div');
|
|
|
+ newOption.className = 'option-item';
|
|
|
+ newOption.innerHTML = `
|
|
|
+ <div class="option-drag-handle" title="拖动排序">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="3" y1="12" x2="21" y2="12"/>
|
|
|
+ <line x1="3" y1="6" x2="21" y2="6"/>
|
|
|
+ <line x1="3" y1="18" x2="21" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="option-checkbox" onclick="toggleOption(this)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="20 6 9 17 4 12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <input type="text" class="option-input" placeholder="新选项">
|
|
|
+ <div class="option-actions">
|
|
|
+ <button class="option-action-btn" onclick="addOptionAfter(this)" title="在下方添加选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button class="option-action-btn delete" onclick="deleteOption(this)" title="删除选项">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="18" y1="6" x2="6" y2="18"/>
|
|
|
+ <line x1="6" y1="6" x2="18" y2="18"/>
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ currentOption.after(newOption);
|
|
|
+ newOption.querySelector('.option-input').focus();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除选项
|
|
|
+ function deleteOption(button) {
|
|
|
+ const optionItem = button.closest('.option-item');
|
|
|
+ const optionsList = optionItem.parentElement;
|
|
|
+ const allOptions = optionsList.querySelectorAll('.option-item');
|
|
|
+
|
|
|
+ // 至少保留2个选项
|
|
|
+ if (allOptions.length <= 2) {
|
|
|
+ alert('至少需要保留两个选项');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ optionItem.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制题目
|
|
|
+ function copyQuestion(button) {
|
|
|
+ const questionItem = button.closest('.question-item');
|
|
|
+ const clone = questionItem.cloneNode(true);
|
|
|
+ const questionNumber = clone.querySelector('.question-number');
|
|
|
+ const currentNum = parseInt(questionNumber.textContent.match(/\d+/)[0]);
|
|
|
+ questionNumber.textContent = `题目 ${currentNum + 1}`;
|
|
|
+ questionItem.after(clone);
|
|
|
+ alert('题目已复制');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除题目
|
|
|
+ function deleteQuestion(button) {
|
|
|
+ const questionItem = button.closest('.question-item');
|
|
|
+ const allQuestions = document.querySelectorAll('.question-item');
|
|
|
+
|
|
|
+ // 至少保留1个题目
|
|
|
+ if (allQuestions.length <= 1) {
|
|
|
+ alert('至少需要保留一个题目');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (confirm('确定要删除这道题目吗?')) {
|
|
|
+ questionItem.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加题目
|
|
|
+ function addQuestion() {
|
|
|
+ alert('添加新题目');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加抽认卡
|
|
|
+ function addFlashcard() {
|
|
|
+ alert('添加新抽认卡');
|
|
|
+ }
|
|
|
+
|
|
|
+ // AI生成解释
|
|
|
+ function generateExplanation() {
|
|
|
+ alert('AI正在生成解释...');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击页面其他地方关闭下拉菜单
|
|
|
+ document.addEventListener('click', function() {
|
|
|
+ const selector = document.getElementById('toolTypeSelector');
|
|
|
+ if (selector) {
|
|
|
+ selector.classList.remove('active');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 显示创建弹窗
|
|
|
+ function showCreateModal() {
|
|
|
+ document.getElementById('createModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隐藏创建弹窗
|
|
|
+ function hideCreateModal() {
|
|
|
+ document.getElementById('createModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取消AI卡片高亮
|
|
|
+ function unsetAiFeatured() {
|
|
|
+ const aiCard = document.getElementById('aiCreateCard');
|
|
|
+ if (aiCard) aiCard.classList.remove('featured');
|
|
|
+ }
|
|
|
+
|
|
|
+ function createBlankCourse() {
|
|
|
+ hideCreateModal();
|
|
|
+ addPageFromTemplate('content');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭弹窗
|
|
|
+ document.getElementById('createModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ hideCreateModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // ========== PPT上传与解析(模拟) ==========
|
|
|
+ let currentPptFile = null;
|
|
|
+ let pptParseTimer = null;
|
|
|
+
|
|
|
+ // ========== 右侧大纲(分组 + 拖动) ==========
|
|
|
+ let outlineData = { groups: [], ungrouped: [] };
|
|
|
+ let draggingItem = null;
|
|
|
+
|
|
|
+ function initRightOutline() {
|
|
|
+ syncRightOutlineWithBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ function snapshotBottomOutline() {
|
|
|
+ const track = document.getElementById('outlineTrack');
|
|
|
+ if (!track) return [];
|
|
|
+ const items = Array.from(track.querySelectorAll('.outline-item-wrapper'));
|
|
|
+ return items.map((item, idx) => {
|
|
|
+ const titleSpan = item.querySelector('.outline-item span:nth-child(2)');
|
|
|
+ const title = titleSpan ? titleSpan.textContent.trim() : `页面 ${idx + 1}`;
|
|
|
+ const pageNumber = item.querySelector('.page-number');
|
|
|
+ const id = pageNumber ? pageNumber.textContent.trim() : `${idx + 1}`;
|
|
|
+ return { id, title, type: '页面' };
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function syncRightOutlineWithBottom() {
|
|
|
+ const bottomPages = snapshotBottomOutline();
|
|
|
+ const orderMap = new Map(bottomPages.map((p, idx) => [p.id, idx]));
|
|
|
+ const existsInBottom = (id) => orderMap.has(id);
|
|
|
+
|
|
|
+ // 移除已被删除的页面
|
|
|
+ outlineData.groups.forEach(group => {
|
|
|
+ group.items = group.items.filter(item => existsInBottom(item.id));
|
|
|
+ });
|
|
|
+ outlineData.ungrouped = outlineData.ungrouped.filter(item => existsInBottom(item.id));
|
|
|
+
|
|
|
+ // 添加新增页面到未分组
|
|
|
+ bottomPages.forEach(page => {
|
|
|
+ const inGroup = outlineData.groups.some(g => g.items.some(it => it.id === page.id));
|
|
|
+ const inUngrouped = outlineData.ungrouped.some(it => it.id === page.id);
|
|
|
+ if (!inGroup && !inUngrouped) {
|
|
|
+ outlineData.ungrouped.push(page);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 未分组跟随底部顺序
|
|
|
+ outlineData.ungrouped.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
|
|
|
+
|
|
|
+ renderRightOutline();
|
|
|
+ }
|
|
|
+
|
|
|
+ function refreshRightOutline() {
|
|
|
+ syncRightOutlineWithBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ function addOutlineGroup() {
|
|
|
+ const group = {
|
|
|
+ id: `group_${Date.now()}`,
|
|
|
+ name: '新建分组',
|
|
|
+ collapsed: false,
|
|
|
+ items: []
|
|
|
+ };
|
|
|
+ outlineData.groups.push(group);
|
|
|
+ renderRightOutline();
|
|
|
+ }
|
|
|
+
|
|
|
+ function deleteGroup(groupId) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === groupId);
|
|
|
+ if (!group) return;
|
|
|
+ // 将子项平铺到未分组,保持顺序
|
|
|
+ outlineData.ungrouped = [...outlineData.ungrouped, ...group.items];
|
|
|
+ outlineData.groups = outlineData.groups.filter(g => g.id !== groupId);
|
|
|
+ renderRightOutline();
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmDeleteGroup(groupId) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === groupId);
|
|
|
+ if (!group) return;
|
|
|
+ const ok = confirm('确定删除该分组?其中的页面将移动到未分组');
|
|
|
+ if (ok) {
|
|
|
+ deleteGroup(groupId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function toggleGroupCollapse(groupId) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === groupId);
|
|
|
+ if (group) {
|
|
|
+ group.collapsed = !group.collapsed;
|
|
|
+ renderRightOutline();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renameGroup(groupId, titleSpan) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === groupId);
|
|
|
+ if (!group || !titleSpan || titleSpan.dataset.editing === '1') return;
|
|
|
+
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.className = 'outline-group-input';
|
|
|
+ input.value = group.name;
|
|
|
+ const width = Math.min(220, Math.max(120, titleSpan.offsetWidth + 20));
|
|
|
+ input.style.width = width + 'px';
|
|
|
+ titleSpan.dataset.editing = '1';
|
|
|
+ titleSpan.replaceWith(input);
|
|
|
+ input.focus();
|
|
|
+ input.select();
|
|
|
+
|
|
|
+ let finished = false;
|
|
|
+ const finish = (commit) => {
|
|
|
+ if (finished) return;
|
|
|
+ finished = true;
|
|
|
+ if (commit) {
|
|
|
+ const name = input.value.trim();
|
|
|
+ if (name) group.name = name;
|
|
|
+ }
|
|
|
+ const newSpan = document.createElement('span');
|
|
|
+ newSpan.className = 'outline-group-title';
|
|
|
+ newSpan.textContent = group.name;
|
|
|
+ newSpan.onclick = (e) => { e.stopPropagation(); renameGroup(groupId, newSpan); };
|
|
|
+ input.replaceWith(newSpan);
|
|
|
+ };
|
|
|
+
|
|
|
+ input.addEventListener('blur', () => finish(true));
|
|
|
+ input.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'Enter') {
|
|
|
+ e.preventDefault();
|
|
|
+ input.blur();
|
|
|
+ } else if (e.key === 'Escape') {
|
|
|
+ e.preventDefault();
|
|
|
+ finish(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderRightOutline() {
|
|
|
+ const container = document.getElementById('rightOutlineList');
|
|
|
+ if (!container) return;
|
|
|
+ container.innerHTML = '';
|
|
|
+
|
|
|
+ const hasContent = outlineData.groups.length > 0 || outlineData.ungrouped.length > 0;
|
|
|
+ if (!hasContent) {
|
|
|
+ container.innerHTML = '<div class="outline-empty">暂无页面,创建后将出现在此</div>';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先渲染分组
|
|
|
+ outlineData.groups.forEach(group => {
|
|
|
+ const groupEl = document.createElement('div');
|
|
|
+ groupEl.className = 'outline-group';
|
|
|
+ groupEl.dataset.id = group.id;
|
|
|
+
|
|
|
+ const header = document.createElement('div');
|
|
|
+ header.className = 'outline-group-header';
|
|
|
+ header.draggable = false;
|
|
|
+
|
|
|
+ const left = document.createElement('div');
|
|
|
+ left.className = 'outline-group-left';
|
|
|
+
|
|
|
+ const arrow = document.createElement('span');
|
|
|
+ arrow.innerHTML = `
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="6 9 12 15 18 9" style="transform: ${group.collapsed ? 'rotate(-90deg)' : 'none'}; transform-origin: 12px 12px;"/>
|
|
|
+ </svg>`;
|
|
|
+ arrow.style.cursor = 'pointer';
|
|
|
+ arrow.onclick = (e) => { e.stopPropagation(); toggleGroupCollapse(group.id); };
|
|
|
+
|
|
|
+ const titleSpan = document.createElement('span');
|
|
|
+ titleSpan.className = 'outline-group-title';
|
|
|
+ titleSpan.textContent = group.name;
|
|
|
+ titleSpan.onclick = (e) => { e.stopPropagation(); renameGroup(group.id, titleSpan); };
|
|
|
+
|
|
|
+ left.appendChild(arrow);
|
|
|
+ left.appendChild(titleSpan);
|
|
|
+ header.appendChild(left);
|
|
|
+
|
|
|
+ const dragHandle = document.createElement('div');
|
|
|
+ dragHandle.className = 'outline-drag-handle';
|
|
|
+ dragHandle.innerHTML = `
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="5" cy="8" r="1"/>
|
|
|
+ <circle cx="5" cy="16" r="1"/>
|
|
|
+ <circle cx="12" cy="8" r="1"/>
|
|
|
+ <circle cx="12" cy="16" r="1"/>
|
|
|
+ <circle cx="19" cy="8" r="1"/>
|
|
|
+ <circle cx="19" cy="16" r="1"/>
|
|
|
+ </svg>`;
|
|
|
+ dragHandle.draggable = true;
|
|
|
+ dragHandle.addEventListener('dragstart', (e) => onGroupDragStart(e, group.id));
|
|
|
+ dragHandle.addEventListener('dragover', onDragOver);
|
|
|
+ dragHandle.addEventListener('drop', (e) => onDrop(e, { targetGroup: group.id }));
|
|
|
+ header.appendChild(dragHandle);
|
|
|
+
|
|
|
+ const deleteBtn = document.createElement('div');
|
|
|
+ deleteBtn.className = 'outline-delete-btn';
|
|
|
+ deleteBtn.innerHTML = `
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>`;
|
|
|
+ deleteBtn.onclick = (e) => { e.stopPropagation(); confirmDeleteGroup(group.id); };
|
|
|
+ header.appendChild(deleteBtn);
|
|
|
+
|
|
|
+ groupEl.appendChild(header);
|
|
|
+
|
|
|
+ const dropAreaTarget = { targetGroup: group.id };
|
|
|
+ header.addEventListener('dragover', onDragOver);
|
|
|
+ header.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
|
|
|
+
|
|
|
+ if (!group.collapsed) {
|
|
|
+ const itemsBox = document.createElement('div');
|
|
|
+ itemsBox.className = 'outline-items';
|
|
|
+ itemsBox.addEventListener('dragover', onDragOver);
|
|
|
+ itemsBox.addEventListener('drop', (e) => onDrop(e, dropAreaTarget));
|
|
|
+ if (group.items.length === 0) {
|
|
|
+ const empty = document.createElement('div');
|
|
|
+ empty.className = 'outline-empty';
|
|
|
+ empty.textContent = '拖动页面到此';
|
|
|
+ itemsBox.appendChild(empty);
|
|
|
+ } else {
|
|
|
+ group.items.forEach((item, idx) => {
|
|
|
+ itemsBox.appendChild(createOutlineItem(item, idx, group.id));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ groupEl.appendChild(itemsBox);
|
|
|
+ }
|
|
|
+
|
|
|
+ container.appendChild(groupEl);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 渲染未分组
|
|
|
+ outlineData.ungrouped.forEach((item, idx) => {
|
|
|
+ container.appendChild(createOutlineItem(item, idx, null));
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function createOutlineItem(item, idx, groupId) {
|
|
|
+ const el = document.createElement('div');
|
|
|
+ el.className = 'outline-item';
|
|
|
+ el.draggable = true;
|
|
|
+ el.dataset.id = item.id;
|
|
|
+ el.dataset.group = groupId || '';
|
|
|
+ el.addEventListener('dragstart', (e) => onItemDragStart(e, item, groupId));
|
|
|
+ el.addEventListener('dragover', onDragOver);
|
|
|
+ el.addEventListener('drop', (e) => onDrop(e, { targetItem: item, groupId }));
|
|
|
+ el.onclick = () => selectPageById(item.id);
|
|
|
+
|
|
|
+ el.innerHTML = `
|
|
|
+ <div class="outline-item-left">
|
|
|
+ <span class="outline-index">${idx + 1}</span>
|
|
|
+ <span class="outline-title" title="${item.title}">${item.title}</span>
|
|
|
+ <span class="outline-type">${item.type || '页面'}</span>
|
|
|
+ </div>
|
|
|
+ <span class="outline-handle">
|
|
|
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="5" cy="12" r="1"/>
|
|
|
+ <circle cx="12" cy="12" r="1"/>
|
|
|
+ <circle cx="19" cy="12" r="1"/>
|
|
|
+ </svg>
|
|
|
+ </span>
|
|
|
+ `;
|
|
|
+ return el;
|
|
|
+ }
|
|
|
+
|
|
|
+ function onItemDragStart(e, item, groupId) {
|
|
|
+ draggingItem = { type: 'item', item, groupId };
|
|
|
+ e.dataTransfer.effectAllowed = 'move';
|
|
|
+ }
|
|
|
+
|
|
|
+ function onGroupDragStart(e, groupId) {
|
|
|
+ draggingItem = { type: 'group', groupId };
|
|
|
+ e.dataTransfer.effectAllowed = 'move';
|
|
|
+ }
|
|
|
+
|
|
|
+ function onDragOver(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ e.dataTransfer.dropEffect = 'move';
|
|
|
+ }
|
|
|
+
|
|
|
+ function onDrop(e, target) {
|
|
|
+ e.preventDefault();
|
|
|
+ if (!draggingItem) return;
|
|
|
+
|
|
|
+ if (draggingItem.type === 'group' && target.targetGroup) {
|
|
|
+ reorderGroups(draggingItem.groupId, target.targetGroup);
|
|
|
+ } else if (draggingItem.type === 'item') {
|
|
|
+ moveItem(draggingItem.item, draggingItem.groupId, target);
|
|
|
+ }
|
|
|
+ draggingItem = null;
|
|
|
+ renderRightOutline();
|
|
|
+ syncBottomOutlineOrder();
|
|
|
+ }
|
|
|
+
|
|
|
+ function reorderGroups(sourceId, targetId) {
|
|
|
+ if (sourceId === targetId) return;
|
|
|
+ const list = outlineData.groups;
|
|
|
+ const fromIdx = list.findIndex(g => g.id === sourceId);
|
|
|
+ const toIdx = list.findIndex(g => g.id === targetId);
|
|
|
+ if (fromIdx === -1 || toIdx === -1) return;
|
|
|
+ const [g] = list.splice(fromIdx, 1);
|
|
|
+ list.splice(toIdx, 0, g);
|
|
|
+ }
|
|
|
+
|
|
|
+ function moveItem(item, fromGroupId, target) {
|
|
|
+ removeItemFromData(item.id);
|
|
|
+ if (target.targetItem) {
|
|
|
+ const targetGroup = target.groupId || null;
|
|
|
+ insertItemBefore(item, target.targetItem.id, targetGroup);
|
|
|
+ } else if (target.targetGroup) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === target.targetGroup);
|
|
|
+ if (group) group.items.push(item);
|
|
|
+ } else {
|
|
|
+ outlineData.ungrouped.push(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeItemFromData(itemId) {
|
|
|
+ outlineData.groups.forEach(g => {
|
|
|
+ g.items = g.items.filter(it => it.id !== itemId);
|
|
|
+ });
|
|
|
+ outlineData.ungrouped = outlineData.ungrouped.filter(it => it.id !== itemId);
|
|
|
+ }
|
|
|
+
|
|
|
+ function insertItemBefore(item, targetId, groupId) {
|
|
|
+ if (groupId) {
|
|
|
+ const group = outlineData.groups.find(g => g.id === groupId);
|
|
|
+ if (!group) return;
|
|
|
+ const idx = group.items.findIndex(it => it.id === targetId);
|
|
|
+ if (idx === -1) group.items.push(item); else group.items.splice(idx, 0, item);
|
|
|
+ } else {
|
|
|
+ const idx = outlineData.ungrouped.findIndex(it => it.id === targetId);
|
|
|
+ if (idx === -1) outlineData.ungrouped.push(item); else outlineData.ungrouped.splice(idx, 0, item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function selectPageById(id) {
|
|
|
+ // 通过底部大纲的序号匹配
|
|
|
+ const track = document.getElementById('outlineTrack');
|
|
|
+ if (!track) return;
|
|
|
+ const item = Array.from(track.querySelectorAll('.outline-item-wrapper')).find(w => {
|
|
|
+ const num = w.querySelector('.page-number');
|
|
|
+ return num && num.textContent.trim() === id;
|
|
|
+ });
|
|
|
+ if (item) {
|
|
|
+ const index = Array.from(track.children).indexOf(item) + 1;
|
|
|
+ selectPage(index, { stopPropagation: () => {} });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function syncBottomOutlineOrder() {
|
|
|
+ const track = document.getElementById('outlineTrack');
|
|
|
+ if (!track) return;
|
|
|
+ const order = [];
|
|
|
+ outlineData.groups.forEach(g => {
|
|
|
+ g.items.forEach(it => order.push(it.id));
|
|
|
+ });
|
|
|
+ outlineData.ungrouped.forEach(it => order.push(it.id));
|
|
|
+
|
|
|
+ const wrappers = Array.from(track.querySelectorAll('.outline-item-wrapper'));
|
|
|
+ const map = new Map();
|
|
|
+ wrappers.forEach(w => {
|
|
|
+ const id = w.querySelector('.page-number')?.textContent.trim();
|
|
|
+ if (id) map.set(id, w);
|
|
|
+ });
|
|
|
+ track.innerHTML = '';
|
|
|
+ order.forEach(id => {
|
|
|
+ const node = map.get(id);
|
|
|
+ if (node) track.appendChild(node);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('DOMContentLoaded', () => {
|
|
|
+ initRightOutline();
|
|
|
+ });
|
|
|
+
|
|
|
+ function startPptUploadFlow() {
|
|
|
+ const input = document.getElementById('pptFileInput');
|
|
|
+ if (!input) return;
|
|
|
+ unsetAiFeatured();
|
|
|
+ input.value = '';
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handlePptFileSelected(event) {
|
|
|
+ const file = event.target.files[0];
|
|
|
+ if (!file) return;
|
|
|
+ const ext = file.name.split('.').pop().toLowerCase();
|
|
|
+ if (!['ppt', 'pptx'].includes(ext)) {
|
|
|
+ alert('仅支持上传PPT文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ currentPptFile = file;
|
|
|
+ hideCreateModal();
|
|
|
+ beginPptParseSimulation(file);
|
|
|
+ }
|
|
|
+
|
|
|
+ function beginPptParseSimulation(file) {
|
|
|
+ setPptParseState('parsing', `正在解析 ${file.name}`);
|
|
|
+ const shouldFail = file.name.toLowerCase().includes('fail');
|
|
|
+ clearTimeout(pptParseTimer);
|
|
|
+ pptParseTimer = setTimeout(() => {
|
|
|
+ if (shouldFail) {
|
|
|
+ setPptParseState('error', '解析失败,请重试或更换文件');
|
|
|
+ } else {
|
|
|
+ setPptParseState('success', '解析完成,已生成课件');
|
|
|
+ setTimeout(() => {
|
|
|
+ closePptParseOverlay();
|
|
|
+ addPptContentToCourse(file.name);
|
|
|
+ }, 900);
|
|
|
+ }
|
|
|
+ }, 2200);
|
|
|
+ }
|
|
|
+
|
|
|
+ function setPptParseState(state, desc) {
|
|
|
+ const overlay = document.getElementById('pptParseOverlay');
|
|
|
+ const icon = document.getElementById('pptParseIcon');
|
|
|
+ const title = document.getElementById('pptParseTitle');
|
|
|
+ const text = document.getElementById('pptParseDesc');
|
|
|
+ const actions = document.getElementById('pptParseActions');
|
|
|
+ overlay.classList.add('active');
|
|
|
+ icon.className = 'ppt-parse-icon';
|
|
|
+ icon.innerHTML = statusIcons.loading;
|
|
|
+ title.textContent = '解析中...';
|
|
|
+ text.textContent = desc || '';
|
|
|
+ actions.style.display = 'flex';
|
|
|
+ Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = true);
|
|
|
+
|
|
|
+ if (state === 'parsing') {
|
|
|
+ icon.innerHTML = statusIcons.loading;
|
|
|
+ title.textContent = '解析中...';
|
|
|
+ icon.classList.remove('success', 'error');
|
|
|
+ } else if (state === 'success') {
|
|
|
+ icon.innerHTML = statusIcons.success;
|
|
|
+ icon.classList.add('success');
|
|
|
+ title.textContent = '解析完成';
|
|
|
+ Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
|
|
|
+ } else if (state === 'error') {
|
|
|
+ icon.innerHTML = statusIcons.error;
|
|
|
+ icon.classList.add('error');
|
|
|
+ title.textContent = '解析失败';
|
|
|
+ Array.from(actions.querySelectorAll('button')).forEach(btn => btn.disabled = false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function closePptParseOverlay() {
|
|
|
+ clearTimeout(pptParseTimer);
|
|
|
+ const overlay = document.getElementById('pptParseOverlay');
|
|
|
+ overlay.classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function addPptContentToCourse(pptName) {
|
|
|
+ // 简单模拟:新增一页内容页并提示
|
|
|
+ addPageFromTemplate('content');
|
|
|
+ alert(`已从PPT "${pptName}" 生成课程页面`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 顶部菜单 & 课程设置
|
|
|
+ let courseMeta = { subject: '', grade: '' };
|
|
|
+
|
|
|
+ function toggleTopMenu(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ const dropdown = document.getElementById('topDropdown');
|
|
|
+ dropdown.classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeTopMenu() {
|
|
|
+ const dropdown = document.getElementById('topDropdown');
|
|
|
+ if (dropdown) dropdown.classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('click', () => {
|
|
|
+ closeTopMenu();
|
|
|
+ });
|
|
|
+
|
|
|
+ function handleTopMenuAction(action) {
|
|
|
+ closeTopMenu();
|
|
|
+ if (action === 'back') {
|
|
|
+ showCreateModal();
|
|
|
+ } else if (action === 'settings') {
|
|
|
+ showCourseSettings();
|
|
|
+ } else if (action === 'template') {
|
|
|
+ openTemplateCenter();
|
|
|
+ } else if (action === 'duplicate') {
|
|
|
+ alert('已另存为副本(示例)');
|
|
|
+ } else if (action === 'delete') {
|
|
|
+ openDeleteCourse();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function setMenuActive(panelType) {
|
|
|
+ document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
|
|
|
+ const isMatch = item.getAttribute('data-panel') === panelType;
|
|
|
+ if (isMatch) {
|
|
|
+ item.classList.add('active');
|
|
|
+ } else {
|
|
|
+ item.classList.remove('active');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function showCourseSettings() {
|
|
|
+ const modal = document.getElementById('courseSettingsModal');
|
|
|
+ const subjectSelect = document.getElementById('settingsSubjectSelect');
|
|
|
+ const gradeSelect = document.getElementById('settingsGradeSelect');
|
|
|
+ if (!modal || !subjectSelect || !gradeSelect) return;
|
|
|
+
|
|
|
+ const publishSubject = document.getElementById('subjectSelect');
|
|
|
+ const publishGrade = document.getElementById('classGradeSelect');
|
|
|
+ subjectSelect.value = courseMeta.subject || (publishSubject ? publishSubject.value : '') || '';
|
|
|
+ gradeSelect.value = courseMeta.grade || (publishGrade ? publishGrade.value : '') || '';
|
|
|
+
|
|
|
+ modal.classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function hideCourseSettings() {
|
|
|
+ const modal = document.getElementById('courseSettingsModal');
|
|
|
+ if (modal) modal.classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('courseSettingsModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) hideCourseSettings();
|
|
|
+ });
|
|
|
+
|
|
|
+ function applyCourseSettings() {
|
|
|
+ const subjectSelect = document.getElementById('settingsSubjectSelect');
|
|
|
+ const gradeSelect = document.getElementById('settingsGradeSelect');
|
|
|
+ courseMeta.subject = subjectSelect ? subjectSelect.value : '';
|
|
|
+ courseMeta.grade = gradeSelect ? gradeSelect.value : '';
|
|
|
+ applyCourseMetaToPublish();
|
|
|
+ hideCourseSettings();
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyCourseMetaToPublish() {
|
|
|
+ const publishSubject = document.getElementById('subjectSelect');
|
|
|
+ const publishGrade = document.getElementById('classGradeSelect');
|
|
|
+ if (publishSubject) publishSubject.value = courseMeta.subject || '';
|
|
|
+ if (publishGrade) publishGrade.value = courseMeta.grade || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发布表单变化时同步回全局设置
|
|
|
+ const publishSubjectSelect = document.getElementById('subjectSelect');
|
|
|
+ if (publishSubjectSelect) publishSubjectSelect.addEventListener('change', () => {
|
|
|
+ courseMeta.subject = publishSubjectSelect.value;
|
|
|
+ });
|
|
|
+ const publishGradeSelect = document.getElementById('classGradeSelect');
|
|
|
+ if (publishGradeSelect) publishGradeSelect.addEventListener('change', () => {
|
|
|
+ courseMeta.grade = publishGradeSelect.value;
|
|
|
+ });
|
|
|
+ courseMeta.subject = publishSubjectSelect ? publishSubjectSelect.value : '';
|
|
|
+ courseMeta.grade = publishGradeSelect ? publishGradeSelect.value : '';
|
|
|
+
|
|
|
+ // 模板中心数据
|
|
|
+ const sampleTemplates = [
|
|
|
+ { id: 'tpl-1', name: '小学语文·故事导入', source: 'official', subject: 'chinese', grade: '3', pages: 8 },
|
|
|
+ { id: 'tpl-2', name: '小学数学·分数基础', source: 'official', subject: 'math', grade: '4', pages: 10 },
|
|
|
+ { id: 'tpl-3', name: '科学·水的三态', source: 'mine', subject: 'science', grade: '4', pages: 6 },
|
|
|
+ { id: 'tpl-4', name: '英语·词汇练习', source: 'official', subject: 'english', grade: '5', pages: 5 }
|
|
|
+ ];
|
|
|
+ let selectedTemplates = [];
|
|
|
+
|
|
|
+ function openTemplateCenter() {
|
|
|
+ document.getElementById('templateCenterModal').classList.add('active');
|
|
|
+ filterTemplates();
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeTemplateCenter() {
|
|
|
+ document.getElementById('templateCenterModal').classList.remove('active');
|
|
|
+ selectedTemplates = [];
|
|
|
+ updateTemplateSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('templateCenterModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) closeTemplateCenter();
|
|
|
+ });
|
|
|
+
|
|
|
+ function filterTemplates() {
|
|
|
+ const source = document.getElementById('tplSourceFilter').value;
|
|
|
+ const subject = document.getElementById('tplSubjectFilter').value;
|
|
|
+ const grade = document.getElementById('tplGradeFilter').value;
|
|
|
+ const grid = document.getElementById('templateGrid');
|
|
|
+ let filtered = sampleTemplates.filter(t => {
|
|
|
+ if (source !== 'all' && t.source !== source) return false;
|
|
|
+ if (subject !== 'all' && t.subject !== subject) return false;
|
|
|
+ if (grade !== 'all' && t.grade !== grade) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ grid.innerHTML = filtered.map(t => `
|
|
|
+ <div class="app-card ${selectedTemplates.includes(t.id) ? 'selected' : ''}" onclick="toggleTemplateSelection('${t.id}')">
|
|
|
+ <div class="app-cover">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="4" width="18" height="14" rx="2"/>
|
|
|
+ <line x1="8" y1="8" x2="16" y2="8"/>
|
|
|
+ <line x1="8" y1="12" x2="16" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="app-info">
|
|
|
+ <div class="app-name">${t.name}</div>
|
|
|
+ <div class="app-description">${(t.subject || '通用')} · ${t.pages}页</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ updateTemplateSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ function toggleTemplateSelection(id) {
|
|
|
+ if (selectedTemplates.includes(id)) {
|
|
|
+ selectedTemplates = selectedTemplates.filter(tid => tid !== id);
|
|
|
+ } else {
|
|
|
+ selectedTemplates.push(id);
|
|
|
+ }
|
|
|
+ filterTemplates();
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateTemplateSelectedCount() {
|
|
|
+ const countText = document.getElementById('selectedTemplateCount');
|
|
|
+ const btn = document.getElementById('confirmApplyTemplateBtn');
|
|
|
+ if (countText) countText.textContent = selectedTemplates.length;
|
|
|
+ if (btn) btn.disabled = selectedTemplates.length === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmApplyTemplates() {
|
|
|
+ if (selectedTemplates.length === 0) return;
|
|
|
+ selectedTemplates.forEach(() => addPageFromTemplate('content'));
|
|
|
+ alert('已应用模板,生成对应页面');
|
|
|
+ closeTemplateCenter();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除课程确认
|
|
|
+ function openDeleteCourse() {
|
|
|
+ document.getElementById('deleteCourseModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function hideDeleteCourse() {
|
|
|
+ document.getElementById('deleteCourseModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ document.getElementById('deleteCourseModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) hideDeleteCourse();
|
|
|
+ });
|
|
|
+
|
|
|
+ function confirmDeleteCourse() {
|
|
|
+ hideDeleteCourse();
|
|
|
+ showCreateModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示发布弹窗
|
|
|
+ function showPublishModal() {
|
|
|
+ // 获取当前课程名称
|
|
|
+ const courseName = document.querySelector('.course-title').textContent.trim();
|
|
|
+ document.getElementById('publishCourseNameDisplay').textContent = courseName;
|
|
|
+ document.getElementById('publishModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隐藏发布弹窗
|
|
|
+ function hidePublishModal() {
|
|
|
+ document.getElementById('publishModal').classList.remove('active');
|
|
|
+ // 重置封面
|
|
|
+ const coverImage = document.getElementById('courseCoverImage');
|
|
|
+ const placeholder = document.getElementById('coverPlaceholder');
|
|
|
+ coverImage.style.display = 'none';
|
|
|
+ coverImage.src = '';
|
|
|
+ placeholder.style.display = 'block';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭发布弹窗
|
|
|
+ document.getElementById('publishModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ hidePublishModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 编辑课程名称
|
|
|
+ function editCourseName() {
|
|
|
+ const display = document.getElementById('publishCourseNameDisplay');
|
|
|
+ const currentName = display.textContent;
|
|
|
+
|
|
|
+ // 创建输入框
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'text';
|
|
|
+ input.value = currentName;
|
|
|
+ input.className = 'publish-course-name-input';
|
|
|
+
|
|
|
+ // 设置初始宽度(基于当前文本)
|
|
|
+ const measureSpan = document.createElement('span');
|
|
|
+ measureSpan.style.visibility = 'hidden';
|
|
|
+ measureSpan.style.position = 'absolute';
|
|
|
+ measureSpan.style.fontSize = '18px';
|
|
|
+ measureSpan.style.fontWeight = '600';
|
|
|
+ measureSpan.style.padding = '10px 16px';
|
|
|
+ measureSpan.textContent = currentName;
|
|
|
+ document.body.appendChild(measureSpan);
|
|
|
+ const initialWidth = Math.max(120, Math.min(500, measureSpan.offsetWidth));
|
|
|
+ document.body.removeChild(measureSpan);
|
|
|
+ input.style.width = initialWidth + 'px';
|
|
|
+
|
|
|
+ // 替换显示元素
|
|
|
+ display.replaceWith(input);
|
|
|
+ input.focus();
|
|
|
+ input.select();
|
|
|
+
|
|
|
+ // 输入时自动调整宽度
|
|
|
+ input.addEventListener('input', () => {
|
|
|
+ const tempSpan = document.createElement('span');
|
|
|
+ tempSpan.style.visibility = 'hidden';
|
|
|
+ tempSpan.style.position = 'absolute';
|
|
|
+ tempSpan.style.fontSize = '18px';
|
|
|
+ tempSpan.style.fontWeight = '600';
|
|
|
+ tempSpan.style.padding = '10px 16px';
|
|
|
+ tempSpan.textContent = input.value || currentName;
|
|
|
+ document.body.appendChild(tempSpan);
|
|
|
+ const newWidth = Math.max(120, Math.min(500, tempSpan.offsetWidth));
|
|
|
+ document.body.removeChild(tempSpan);
|
|
|
+ input.style.width = newWidth + 'px';
|
|
|
+ });
|
|
|
+
|
|
|
+ // 失去焦点时恢复
|
|
|
+ const saveEdit = () => {
|
|
|
+ const newName = input.value.trim() || currentName;
|
|
|
+ const newDisplay = document.createElement('div');
|
|
|
+ newDisplay.id = 'publishCourseNameDisplay';
|
|
|
+ newDisplay.className = 'publish-course-name';
|
|
|
+ newDisplay.textContent = newName;
|
|
|
+ newDisplay.onclick = editCourseName;
|
|
|
+ input.replaceWith(newDisplay);
|
|
|
+
|
|
|
+ // 同步更新顶部课程标题
|
|
|
+ document.querySelector('.course-title').textContent = newName;
|
|
|
+ };
|
|
|
+
|
|
|
+ input.addEventListener('blur', saveEdit);
|
|
|
+ input.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'Enter') {
|
|
|
+ input.blur();
|
|
|
+ } else if (e.key === 'Escape') {
|
|
|
+ input.value = currentName;
|
|
|
+ input.blur();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从本地上传课程封面
|
|
|
+ function uploadCourseCoverLocal(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ document.getElementById('courseCoverInput').click();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 网页搜索图片作为封面
|
|
|
+ function searchWebImageForCover(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ console.log('打开网页搜索图片弹窗(用于课程封面)');
|
|
|
+ alert('网页搜索图片功能(为课程封面)');
|
|
|
+ // 实际应用中应打开搜索浮窗,选择后设置封面
|
|
|
+ }
|
|
|
+
|
|
|
+ // AI生成图片作为封面
|
|
|
+ function generateAIImageForCover(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ console.log('打开AI生成图片弹窗(用于课程封面)');
|
|
|
+ alert('AI生成图片功能(为课程封面)');
|
|
|
+ // 实际应用中应打开AI生成浮窗,生成后设置封面
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理封面上传
|
|
|
+ function handleCoverUpload(event) {
|
|
|
+ const file = event.target.files[0];
|
|
|
+ if (file && file.type.startsWith('image/')) {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = function(e) {
|
|
|
+ const coverImage = document.getElementById('courseCoverImage');
|
|
|
+ const placeholder = document.getElementById('coverPlaceholder');
|
|
|
+
|
|
|
+ coverImage.src = e.target.result;
|
|
|
+ coverImage.style.display = 'block';
|
|
|
+ placeholder.style.display = 'none';
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除课程封面
|
|
|
+ function deleteCourseCover() {
|
|
|
+ const coverImage = document.getElementById('courseCoverImage');
|
|
|
+ const placeholder = document.getElementById('coverPlaceholder');
|
|
|
+
|
|
|
+ coverImage.src = '';
|
|
|
+ coverImage.style.display = 'none';
|
|
|
+ placeholder.style.display = 'block';
|
|
|
+
|
|
|
+ // 清空文件选择
|
|
|
+ document.getElementById('courseCoverInput').value = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择可见范围
|
|
|
+ function selectVisibility(element, value) {
|
|
|
+ // 移除所有选中状态
|
|
|
+ document.querySelectorAll('.radio-option').forEach(option => {
|
|
|
+ option.classList.remove('selected');
|
|
|
+ });
|
|
|
+ // 添加选中状态
|
|
|
+ element.classList.add('selected');
|
|
|
+ // 设置radio按钮
|
|
|
+ element.querySelector('input[type="radio"]').checked = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认发布
|
|
|
+ function confirmPublish(event) {
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+ const subject = document.getElementById('subjectSelect').value;
|
|
|
+ const classGrade = document.getElementById('classGradeSelect').value;
|
|
|
+ const classNum = document.getElementById('classSelect').value;
|
|
|
+ const visibility = document.querySelector('input[name="visibility"]:checked').value;
|
|
|
+
|
|
|
+ const courseName = document.getElementById('publishCourseNameDisplay').textContent;
|
|
|
+ const coverImage = document.getElementById('courseCoverImage');
|
|
|
+ const hasCover = coverImage.style.display !== 'none' && coverImage.src;
|
|
|
+
|
|
|
+ console.log('发布课程:', {
|
|
|
+ courseName,
|
|
|
+ subject,
|
|
|
+ classGrade,
|
|
|
+ classNum,
|
|
|
+ visibility,
|
|
|
+ hasCover
|
|
|
+ });
|
|
|
+
|
|
|
+ // 显示成功提示
|
|
|
+ alert(`课程《${courseName}》发布成功!\n学科: ${subject}\n班级: ${classGrade}年级${classNum}班\n可见范围: ${visibility === 'students' ? '仅发布学生可见' : '组织可见'}`);
|
|
|
+
|
|
|
+ hidePublishModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始AI创建
|
|
|
+ function startAICreate() {
|
|
|
+ hideCreateModal();
|
|
|
+ // 切换到AI面板
|
|
|
+ switchToPanel('ai');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换面板
|
|
|
+ function switchToPanel(panelType) {
|
|
|
+ const aiPanel = document.getElementById('aiPanel');
|
|
|
+ const pagesPanel = document.getElementById('pagesPanel');
|
|
|
+ const toolsPanel = document.getElementById('toolsPanel');
|
|
|
+ const appsPanel = document.getElementById('appsPanel');
|
|
|
+ const webPanel = document.getElementById('webPanel');
|
|
|
+ const resourcesPanel = document.getElementById('resourcesPanel');
|
|
|
+
|
|
|
+ [aiPanel, pagesPanel, toolsPanel, appsPanel, webPanel, resourcesPanel].forEach(panel => {
|
|
|
+ if (panel) {
|
|
|
+ panel.classList.remove('collapsed', 'hidden');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (panelType === 'ai') {
|
|
|
+ pagesPanel.classList.add('hidden');
|
|
|
+ toolsPanel.classList.add('hidden');
|
|
|
+ appsPanel.classList.add('hidden');
|
|
|
+ webPanel.classList.add('hidden');
|
|
|
+ resourcesPanel.classList.add('hidden');
|
|
|
+ restoreNormalView();
|
|
|
+ } else if (panelType === 'pages') {
|
|
|
+ aiPanel.classList.add('hidden');
|
|
|
+ toolsPanel.classList.add('hidden');
|
|
|
+ appsPanel.classList.add('hidden');
|
|
|
+ webPanel.classList.add('hidden');
|
|
|
+ resourcesPanel.classList.add('hidden');
|
|
|
+ restoreNormalView();
|
|
|
+ } else if (panelType === 'tools') {
|
|
|
+ aiPanel.classList.add('hidden');
|
|
|
+ pagesPanel.classList.add('hidden');
|
|
|
+ appsPanel.classList.add('hidden');
|
|
|
+ webPanel.classList.add('hidden');
|
|
|
+ resourcesPanel.classList.add('hidden');
|
|
|
+ document.getElementById('toolsListView').classList.remove('hidden');
|
|
|
+ document.getElementById('toolsConfigView').classList.add('hidden');
|
|
|
+ restoreNormalView();
|
|
|
+ } else if (panelType === 'apps') {
|
|
|
+ aiPanel.classList.add('hidden');
|
|
|
+ pagesPanel.classList.add('hidden');
|
|
|
+ toolsPanel.classList.add('hidden');
|
|
|
+ webPanel.classList.add('hidden');
|
|
|
+ resourcesPanel.classList.add('hidden');
|
|
|
+ restoreNormalView();
|
|
|
+ } else if (panelType === 'web') {
|
|
|
+ aiPanel.classList.add('hidden');
|
|
|
+ pagesPanel.classList.add('hidden');
|
|
|
+ toolsPanel.classList.add('hidden');
|
|
|
+ appsPanel.classList.add('hidden');
|
|
|
+ resourcesPanel.classList.add('hidden');
|
|
|
+ switchToWebView('list');
|
|
|
+ restoreNormalView();
|
|
|
+ } else if (panelType === 'resources') {
|
|
|
+ aiPanel.classList.add('hidden');
|
|
|
+ pagesPanel.classList.add('hidden');
|
|
|
+ toolsPanel.classList.add('hidden');
|
|
|
+ appsPanel.classList.add('hidden');
|
|
|
+ webPanel.classList.add('hidden');
|
|
|
+ restoreNormalView();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 二级菜单折叠
|
|
|
+ function toggleSecondaryPanel() {
|
|
|
+ const aiPanel = document.getElementById('aiPanel');
|
|
|
+ const pagesPanel = document.getElementById('pagesPanel');
|
|
|
+ const toolsPanel = document.getElementById('toolsPanel');
|
|
|
+ const appsPanel = document.getElementById('appsPanel');
|
|
|
+ const webPanel = document.getElementById('webPanel');
|
|
|
+ const resourcesPanel = document.getElementById('resourcesPanel');
|
|
|
+
|
|
|
+ let activePanel = null;
|
|
|
+ if (!aiPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = aiPanel;
|
|
|
+ } else if (!pagesPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = pagesPanel;
|
|
|
+ } else if (!toolsPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = toolsPanel;
|
|
|
+ } else if (!appsPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = appsPanel;
|
|
|
+ } else if (!webPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = webPanel;
|
|
|
+ } else if (!resourcesPanel.classList.contains('hidden')) {
|
|
|
+ activePanel = resourcesPanel;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!activePanel) return;
|
|
|
+
|
|
|
+ activePanel.classList.toggle('collapsed');
|
|
|
+ const btn = activePanel.querySelector('.collapse-btn svg');
|
|
|
+ if (activePanel.classList.contains('collapsed')) {
|
|
|
+ btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
|
|
|
+ } else {
|
|
|
+ btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 右侧面板折叠
|
|
|
+ function toggleRightPanel() {
|
|
|
+ const panel = document.getElementById('rightPanel');
|
|
|
+ panel.classList.toggle('collapsed');
|
|
|
+ const btn = panel.querySelector('.collapse-btn svg');
|
|
|
+ if (panel.classList.contains('collapsed')) {
|
|
|
+ btn.innerHTML = '<polyline points="15 18 9 12 15 6"/>';
|
|
|
+ } else {
|
|
|
+ btn.innerHTML = '<polyline points="9 18 15 12 9 6"/>';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 一级菜单点击
|
|
|
+ document.querySelectorAll('.primary-menu .menu-item').forEach(item => {
|
|
|
+ item.addEventListener('click', function() {
|
|
|
+ document.querySelectorAll('.primary-menu .menu-item').forEach(i => i.classList.remove('active'));
|
|
|
+ this.classList.add('active');
|
|
|
+
|
|
|
+ const panelType = this.getAttribute('data-panel');
|
|
|
+
|
|
|
+ if (panelType === 'ai') {
|
|
|
+ switchToPanel('ai');
|
|
|
+ } else if (panelType === 'pages') {
|
|
|
+ switchToPanel('pages');
|
|
|
+ } else if (panelType === 'tools') {
|
|
|
+ switchToPanel('tools');
|
|
|
+ } else if (panelType === 'apps') {
|
|
|
+ switchToPanel('apps');
|
|
|
+ } else if (panelType === 'web') {
|
|
|
+ switchToPanel('web');
|
|
|
+ } else if (panelType === 'resources') {
|
|
|
+ switchToPanel('resources');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 自动调整textarea高度
|
|
|
+ function autoResize(textarea) {
|
|
|
+ textarea.style.height = 'auto';
|
|
|
+ const newHeight = Math.max(72, Math.min(textarea.scrollHeight, 180));
|
|
|
+ textarea.style.height = newHeight + 'px';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ function sendMessage() {
|
|
|
+ const input = document.getElementById('chatInput');
|
|
|
+ const message = input.value.trim();
|
|
|
+ const inputContainer = document.getElementById('chatInputContainer');
|
|
|
+ const statusText = document.getElementById('statusText');
|
|
|
+ const sendBtn = document.getElementById('sendBtn');
|
|
|
+ const uploadBtn = document.getElementById('uploadBtn');
|
|
|
+
|
|
|
+ if (!message) return;
|
|
|
+
|
|
|
+ // 添加用户消息
|
|
|
+ addMessage(message, 'user');
|
|
|
+
|
|
|
+ // 清空输入框并重置高度
|
|
|
+ input.value = '';
|
|
|
+ input.style.height = '72px';
|
|
|
+
|
|
|
+ // 移动对话框到底部
|
|
|
+ inputContainer.classList.add('bottom');
|
|
|
+
|
|
|
+ // 显示生成状态
|
|
|
+ statusText.classList.add('active');
|
|
|
+ sendBtn.classList.add('generating');
|
|
|
+ uploadBtn.style.display = 'none';
|
|
|
+
|
|
|
+ // 禁用输入和按钮
|
|
|
+ input.disabled = true;
|
|
|
+ sendBtn.disabled = true;
|
|
|
+
|
|
|
+ // 模拟AI回复
|
|
|
+ setTimeout(() => {
|
|
|
+ const topic = extractTopic(message);
|
|
|
+ const aiResponse = `好的,我正在为您生成"${topic}"的课程内容。\n\n课程将包括:\n• 教学目标和重点\n• 互动演示内容\n• 课堂练习题\n• 小组活动设计\n\n预计生成时间:30秒`;
|
|
|
+ addMessage(aiResponse, 'ai');
|
|
|
+
|
|
|
+ // 更新课程标题
|
|
|
+ document.querySelector('.course-title').textContent = topic;
|
|
|
+
|
|
|
+ // 模拟生成完成
|
|
|
+ setTimeout(() => {
|
|
|
+ addMessage('✅ 课程已生成完成!已为您创建了5个教学页面和3个互动工具。您可以在底部查看课程大纲,或在中央区域开始编辑。', 'ai');
|
|
|
+
|
|
|
+ // 切换到编辑模式
|
|
|
+ switchToEditMode();
|
|
|
+
|
|
|
+ // 恢复正常状态
|
|
|
+ statusText.classList.remove('active');
|
|
|
+ sendBtn.classList.remove('generating');
|
|
|
+ uploadBtn.style.display = 'flex';
|
|
|
+ input.disabled = false;
|
|
|
+ sendBtn.disabled = false;
|
|
|
+
|
|
|
+ // 更新保存状态
|
|
|
+ document.getElementById('saveStatus').textContent = '已保存';
|
|
|
+ }, 2000);
|
|
|
+ }, 800);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加消息到聊天区域
|
|
|
+ function addMessage(text, type) {
|
|
|
+ const messagesContainer = document.getElementById('chatMessages');
|
|
|
+ const messageDiv = document.createElement('div');
|
|
|
+ messageDiv.className = `message message-${type}`;
|
|
|
+
|
|
|
+ const contentDiv = document.createElement('div');
|
|
|
+ contentDiv.className = 'message-content';
|
|
|
+ contentDiv.textContent = text;
|
|
|
+
|
|
|
+ messageDiv.appendChild(contentDiv);
|
|
|
+ messagesContainer.appendChild(messageDiv);
|
|
|
+
|
|
|
+ // 滚动到底部
|
|
|
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取课程主题
|
|
|
+ function extractTopic(message) {
|
|
|
+ const match = message.match(/教学内容为(.+?)的/);
|
|
|
+ if (match) {
|
|
|
+ return match[1];
|
|
|
+ }
|
|
|
+ return '新课程';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换到编辑模式
|
|
|
+ function switchToEditMode() {
|
|
|
+ isEditMode = true;
|
|
|
+
|
|
|
+ // 隐藏占位符
|
|
|
+ document.getElementById('slidePlaceholder').style.display = 'none';
|
|
|
+
|
|
|
+ // 显示工具栏
|
|
|
+ document.getElementById('elementToolbar').classList.add('visible');
|
|
|
+
|
|
|
+ // 显示底部大纲
|
|
|
+ document.getElementById('bottomOutline').classList.add('visible');
|
|
|
+
|
|
|
+ // 生成示例页面
|
|
|
+ generateSamplePages();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成示例页面
|
|
|
+ function generateSamplePages() {
|
|
|
+ const outlineTrack = document.getElementById('outlineTrack');
|
|
|
+ const pageCount = 5;
|
|
|
+ const pageTypes = ['标题页', '内容页', '选择题', '问答', '总结'];
|
|
|
+
|
|
|
+ for (let i = 1; i <= pageCount; i++) {
|
|
|
+ const wrapper = document.createElement('div');
|
|
|
+ wrapper.className = 'outline-item-wrapper';
|
|
|
+
|
|
|
+ const outlineItem = document.createElement('div');
|
|
|
+ outlineItem.className = 'outline-item' + (i === 1 ? ' active' : '');
|
|
|
+ outlineItem.draggable = true;
|
|
|
+ outlineItem.dataset.pageIndex = i;
|
|
|
+ outlineItem.onclick = (e) => selectPage(i, e);
|
|
|
+ outlineItem.innerHTML = `
|
|
|
+ <span class="page-number">${i}</span>
|
|
|
+ <span>${pageTypes[i - 1]}</span>
|
|
|
+ <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${i})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="1"/>
|
|
|
+ <circle cx="12" cy="5" r="1"/>
|
|
|
+ <circle cx="12" cy="19" r="1"/>
|
|
|
+ </svg>
|
|
|
+ <div class="page-menu-dropdown">
|
|
|
+ <div class="page-menu-item" onclick="copyPage(${i})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
|
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
|
+ </svg>
|
|
|
+ 复制
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item" onclick="addBlankPage(${i})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ 新增
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item danger" onclick="deletePage(${i})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ 删除
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 添加拖拽事件监听器
|
|
|
+ outlineItem.addEventListener('dragstart', handleDragStart);
|
|
|
+ outlineItem.addEventListener('dragend', handleDragEnd);
|
|
|
+ outlineItem.addEventListener('dragover', handleDragOver);
|
|
|
+ outlineItem.addEventListener('drop', handleDrop);
|
|
|
+ outlineItem.addEventListener('dragleave', handleDragLeave);
|
|
|
+
|
|
|
+ const addBtn = document.createElement('button');
|
|
|
+ addBtn.className = 'add-page-between';
|
|
|
+ addBtn.textContent = '+';
|
|
|
+ addBtn.onclick = () => addPageBetween(i);
|
|
|
+
|
|
|
+ wrapper.appendChild(outlineItem);
|
|
|
+ wrapper.appendChild(addBtn);
|
|
|
+ outlineTrack.appendChild(wrapper);
|
|
|
+ }
|
|
|
+ syncRightOutlineWithBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择页面
|
|
|
+ function selectPage(pageNum, evt) {
|
|
|
+ const currentEvent = evt || event;
|
|
|
+ document.querySelectorAll('.outline-item').forEach(item => {
|
|
|
+ item.classList.remove('active');
|
|
|
+ });
|
|
|
+ if (currentEvent && currentEvent.currentTarget) {
|
|
|
+ currentEvent.currentTarget.classList.add('active');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面菜单切换
|
|
|
+ function togglePageMenu(event, pageNum) {
|
|
|
+ event.stopPropagation();
|
|
|
+ const menu = event.currentTarget;
|
|
|
+
|
|
|
+ // 关闭其他菜单
|
|
|
+ document.querySelectorAll('.page-menu').forEach(m => {
|
|
|
+ if (m !== menu) m.classList.remove('active');
|
|
|
+ });
|
|
|
+
|
|
|
+ menu.classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制页面
|
|
|
+ function copyPage(pageNum) {
|
|
|
+ console.log('复制页面', pageNum);
|
|
|
+ alert('复制页面 ' + pageNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增空白页面
|
|
|
+ function addBlankPage(afterPage) {
|
|
|
+ console.log('在页面后新增空白页', afterPage);
|
|
|
+ alert('在页面 ' + afterPage + ' 后新增空白页');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除页面
|
|
|
+ function deletePage(pageNum) {
|
|
|
+ if (confirm('确定要删除页面 ' + pageNum + ' 吗?')) {
|
|
|
+ console.log('删除页面', pageNum);
|
|
|
+ alert('删除页面 ' + pageNum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在页面之间添加
|
|
|
+ function addPageBetween(afterPage) {
|
|
|
+ console.log('在页面后添加新页面', afterPage);
|
|
|
+ alert('在页面 ' + afterPage + ' 后添加新页面');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面拖拽功能
|
|
|
+ let draggedElement = null;
|
|
|
+ let draggedIndex = null;
|
|
|
+
|
|
|
+ function handleDragStart(e) {
|
|
|
+ draggedElement = e.currentTarget;
|
|
|
+ draggedIndex = parseInt(draggedElement.dataset.pageIndex);
|
|
|
+ draggedElement.classList.add('dragging');
|
|
|
+ e.dataTransfer.effectAllowed = 'move';
|
|
|
+ e.dataTransfer.setData('text/html', draggedElement.innerHTML);
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDragEnd(e) {
|
|
|
+ e.currentTarget.classList.remove('dragging');
|
|
|
+ // 清除所有拖拽状态
|
|
|
+ document.querySelectorAll('.outline-item').forEach(item => {
|
|
|
+ item.classList.remove('drag-over');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDragOver(e) {
|
|
|
+ if (e.preventDefault) {
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+ e.dataTransfer.dropEffect = 'move';
|
|
|
+
|
|
|
+ const targetElement = e.currentTarget;
|
|
|
+ if (targetElement !== draggedElement) {
|
|
|
+ targetElement.classList.add('drag-over');
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDragLeave(e) {
|
|
|
+ e.currentTarget.classList.remove('drag-over');
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDrop(e) {
|
|
|
+ if (e.stopPropagation) {
|
|
|
+ e.stopPropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ const targetElement = e.currentTarget;
|
|
|
+ const targetIndex = parseInt(targetElement.dataset.pageIndex);
|
|
|
+
|
|
|
+ if (draggedElement !== targetElement && draggedIndex !== targetIndex) {
|
|
|
+ // 交换页面顺序
|
|
|
+ const outlineTrack = document.getElementById('outlineTrack');
|
|
|
+ const allWrappers = Array.from(outlineTrack.querySelectorAll('.outline-item-wrapper'));
|
|
|
+
|
|
|
+ const draggedWrapper = draggedElement.parentElement;
|
|
|
+ const targetWrapper = targetElement.parentElement;
|
|
|
+
|
|
|
+ // 获取位置
|
|
|
+ const draggedWrapperIndex = allWrappers.indexOf(draggedWrapper);
|
|
|
+ const targetWrapperIndex = allWrappers.indexOf(targetWrapper);
|
|
|
+
|
|
|
+ if (draggedWrapperIndex < targetWrapperIndex) {
|
|
|
+ targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper.nextSibling);
|
|
|
+ } else {
|
|
|
+ targetWrapper.parentNode.insertBefore(draggedWrapper, targetWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新页面序号
|
|
|
+ updatePageNumbers();
|
|
|
+
|
|
|
+ console.log(`页面从位置 ${draggedIndex} 移动到位置 ${targetIndex}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ targetElement.classList.remove('drag-over');
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新页面序号
|
|
|
+ function updatePageNumbers() {
|
|
|
+ const outlineItems = document.querySelectorAll('.outline-item');
|
|
|
+ outlineItems.forEach((item, index) => {
|
|
|
+ const pageNumber = index + 1;
|
|
|
+ item.dataset.pageIndex = pageNumber;
|
|
|
+ const numberSpan = item.querySelector('.page-number');
|
|
|
+ if (numberSpan) {
|
|
|
+ numberSpan.textContent = pageNumber;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下拉菜单切换
|
|
|
+ function toggleDropdown(id) {
|
|
|
+ event.stopPropagation();
|
|
|
+ const dropdown = document.getElementById(id);
|
|
|
+ dropdown.classList.toggle('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击页面其他地方关闭下拉菜单
|
|
|
+ document.addEventListener('click', function() {
|
|
|
+ document.querySelectorAll('.dropdown').forEach(d => d.classList.remove('active'));
|
|
|
+ document.querySelectorAll('.page-menu').forEach(m => m.classList.remove('active'));
|
|
|
+ });
|
|
|
+
|
|
|
+ // 插入表格
|
|
|
+ function insertTable() {
|
|
|
+ alert('插入表格功能');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入形状
|
|
|
+ function insertShape(type) {
|
|
|
+ const canvas = document.getElementById('slideCanvas');
|
|
|
+ const element = document.createElement('div');
|
|
|
+ element.className = 'slide-element';
|
|
|
+ element.id = 'element-' + (++elementIdCounter);
|
|
|
+ element.style.left = '200px';
|
|
|
+ element.style.top = '200px';
|
|
|
+ element.style.width = '150px';
|
|
|
+ element.style.height = '150px';
|
|
|
+
|
|
|
+ if (type === 'circle') {
|
|
|
+ element.style.borderRadius = '50%';
|
|
|
+ element.style.background = '#285cf5';
|
|
|
+ } else if (type === 'rectangle') {
|
|
|
+ element.style.background = '#3b82f6';
|
|
|
+ } else if (type === 'triangle') {
|
|
|
+ element.style.background = '#10b981';
|
|
|
+ } else if (type === 'arrow') {
|
|
|
+ element.style.background = '#f59e0b';
|
|
|
+ }
|
|
|
+
|
|
|
+ addResizeHandles(element);
|
|
|
+ canvas.appendChild(element);
|
|
|
+ makeElementDraggable(element);
|
|
|
+ element.addEventListener('click', () => selectElement(element));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加调整大小手柄
|
|
|
+ function addResizeHandles(element) {
|
|
|
+ const positions = ['nw', 'ne', 'sw', 'se'];
|
|
|
+ positions.forEach(pos => {
|
|
|
+ const handle = document.createElement('div');
|
|
|
+ handle.className = 'resize-handle ' + pos;
|
|
|
+ element.appendChild(handle);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入文本
|
|
|
+ function insertText() {
|
|
|
+ const canvas = document.getElementById('slideCanvas');
|
|
|
+ const element = document.createElement('div');
|
|
|
+ element.className = 'slide-element text-element';
|
|
|
+ element.id = 'element-' + (++elementIdCounter);
|
|
|
+ element.contentEditable = true;
|
|
|
+ element.textContent = '双击编辑文本';
|
|
|
+ element.style.left = '100px';
|
|
|
+ element.style.top = '100px';
|
|
|
+ element.style.width = '300px';
|
|
|
+ element.style.minHeight = '50px';
|
|
|
+
|
|
|
+ addResizeHandles(element);
|
|
|
+ canvas.appendChild(element);
|
|
|
+ makeElementDraggable(element);
|
|
|
+ element.addEventListener('click', () => selectElement(element));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入图片
|
|
|
+ function insertImage() {
|
|
|
+ const canvas = document.getElementById('slideCanvas');
|
|
|
+ const element = document.createElement('div');
|
|
|
+ element.className = 'slide-element has-image';
|
|
|
+ element.id = 'element-' + (++elementIdCounter);
|
|
|
+ element.style.left = '150px';
|
|
|
+ element.style.top = '150px';
|
|
|
+ element.style.width = '200px';
|
|
|
+ element.style.height = '150px';
|
|
|
+ element.style.background = '#e5e7eb';
|
|
|
+ element.style.display = 'flex';
|
|
|
+ element.style.alignItems = 'center';
|
|
|
+ element.style.justifyContent = 'center';
|
|
|
+ element.innerHTML = `
|
|
|
+ <span style="color: #9ca3af;">图片占位</span>
|
|
|
+ <div class="image-hover-menu">
|
|
|
+ <div class="image-menu-item" onclick="uploadLocal(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
|
|
|
+ <polyline points="17 8 12 3 7 8"/>
|
|
|
+ <line x1="12" y1="3" x2="12" y2="15"/>
|
|
|
+ </svg>
|
|
|
+ 自本地上传
|
|
|
+ </div>
|
|
|
+ <div class="image-menu-item" onclick="searchWebImage(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="11" cy="11" r="8"/>
|
|
|
+ <path d="M21 21l-4.35-4.35"/>
|
|
|
+ </svg>
|
|
|
+ 自网页搜索
|
|
|
+ </div>
|
|
|
+ <div class="image-menu-item" onclick="generateAIImage(event)">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
|
+ <path d="M2 17l10 5 10-5"/>
|
|
|
+ <path d="M2 12l10 5 10-5"/>
|
|
|
+ </svg>
|
|
|
+ 自AI生成
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ addResizeHandles(element);
|
|
|
+ canvas.appendChild(element);
|
|
|
+ makeElementDraggable(element);
|
|
|
+ element.addEventListener('click', () => selectElement(element));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从模板添加页面
|
|
|
+ function addPageFromTemplate(templateType) {
|
|
|
+ if (!isEditMode) {
|
|
|
+ switchToEditMode();
|
|
|
+ }
|
|
|
+
|
|
|
+ const outlineTrack = document.getElementById('outlineTrack');
|
|
|
+ const pageCount = outlineTrack.querySelectorAll('.outline-item-wrapper').length + 1;
|
|
|
+
|
|
|
+ const templateNames = {
|
|
|
+ 'title': '标题页',
|
|
|
+ 'image': '图片页',
|
|
|
+ 'content': '内容页',
|
|
|
+ 'text-image': '文图页',
|
|
|
+ 'image-text': '图文页'
|
|
|
+ };
|
|
|
+
|
|
|
+ const wrapper = document.createElement('div');
|
|
|
+ wrapper.className = 'outline-item-wrapper';
|
|
|
+
|
|
|
+ const outlineItem = document.createElement('div');
|
|
|
+ outlineItem.className = 'outline-item';
|
|
|
+ outlineItem.onclick = (e) => selectPage(pageCount, e);
|
|
|
+ outlineItem.innerHTML = `
|
|
|
+ <span class="page-number">${pageCount}</span>
|
|
|
+ <span>${templateNames[templateType]}</span>
|
|
|
+ <div class="page-menu" onclick="event.stopPropagation(); togglePageMenu(event, ${pageCount})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="1"/>
|
|
|
+ <circle cx="12" cy="5" r="1"/>
|
|
|
+ <circle cx="12" cy="19" r="1"/>
|
|
|
+ </svg>
|
|
|
+ <div class="page-menu-dropdown">
|
|
|
+ <div class="page-menu-item" onclick="copyPage(${pageCount})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
|
+ <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
|
+ </svg>
|
|
|
+ 复制
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item" onclick="addBlankPage(${pageCount})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <line x1="12" y1="5" x2="12" y2="19"/>
|
|
|
+ <line x1="5" y1="12" x2="19" y2="12"/>
|
|
|
+ </svg>
|
|
|
+ 新增
|
|
|
+ </div>
|
|
|
+ <div class="page-menu-item danger" onclick="deletePage(${pageCount})">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <polyline points="3 6 5 6 21 6"/>
|
|
|
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
|
+ </svg>
|
|
|
+ 删除
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ const addBtn = document.createElement('button');
|
|
|
+ addBtn.className = 'add-page-between';
|
|
|
+ addBtn.textContent = '+';
|
|
|
+ addBtn.onclick = () => addPageBetween(pageCount);
|
|
|
+
|
|
|
+ wrapper.appendChild(outlineItem);
|
|
|
+ wrapper.appendChild(addBtn);
|
|
|
+ outlineTrack.appendChild(wrapper);
|
|
|
+
|
|
|
+ // 如果是包含图片的页面,自动添加图片元素
|
|
|
+ if (['image', 'text-image', 'image-text'].includes(templateType)) {
|
|
|
+ insertImage();
|
|
|
+ }
|
|
|
+
|
|
|
+ alert('已添加' + templateNames[templateType]);
|
|
|
+ syncRightOutlineWithBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传PPT
|
|
|
+ function uploadPPT() {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = '.ppt,.pptx';
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const file = e.target.files[0];
|
|
|
+ if (file) {
|
|
|
+ alert('正在解析PPT文件: ' + file.name + '\n\n此功能将自动识别PPT中的页面并导入到课件中。');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图片上传方法
|
|
|
+ function uploadLocal(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.accept = 'image/*';
|
|
|
+ input.onchange = (e) => {
|
|
|
+ const file = e.target.files[0];
|
|
|
+ if (file) {
|
|
|
+ alert('已选择图片: ' + file.name);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ function searchWebImage(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ document.getElementById('searchImageModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ function generateAIImage(event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ document.getElementById('generateImageModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭搜索浮窗
|
|
|
+ function closeSearchModal() {
|
|
|
+ document.getElementById('searchImageModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认选择搜索图片
|
|
|
+ function confirmSearchImage() {
|
|
|
+ alert('已选择网页图片');
|
|
|
+ closeSearchModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭生成浮窗
|
|
|
+ function closeGenerateModal() {
|
|
|
+ document.getElementById('generateImageModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始生成图片
|
|
|
+ function startGenerate() {
|
|
|
+ const loading = document.getElementById('generateLoading');
|
|
|
+ loading.classList.add('active');
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ loading.classList.remove('active');
|
|
|
+ alert('图片生成完成!');
|
|
|
+ closeGenerateModal();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击浮窗遮罩关闭
|
|
|
+ document.getElementById('searchImageModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeSearchModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('generateImageModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeGenerateModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('videoSourceModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeVideoSourceModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('audioUploadModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeAudioUploadModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('documentUploadModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeDocumentUploadModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('collectionModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeCollectionModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 使元素可拖动
|
|
|
+ function makeElementDraggable(element) {
|
|
|
+ let isDragging = false;
|
|
|
+ let startX, startY, startLeft, startTop;
|
|
|
+
|
|
|
+ element.addEventListener('mousedown', function(e) {
|
|
|
+ if (e.target.classList.contains('resize-handle')) return;
|
|
|
+ if (e.target.contentEditable === 'true' && e.target === element) return;
|
|
|
+
|
|
|
+ isDragging = true;
|
|
|
+ startX = e.clientX;
|
|
|
+ startY = e.clientY;
|
|
|
+ startLeft = parseInt(element.style.left) || 0;
|
|
|
+ startTop = parseInt(element.style.top) || 0;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', function(e) {
|
|
|
+ if (!isDragging) return;
|
|
|
+
|
|
|
+ const dx = e.clientX - startX;
|
|
|
+ const dy = e.clientY - startY;
|
|
|
+
|
|
|
+ element.style.left = (startLeft + dx) + 'px';
|
|
|
+ element.style.top = (startTop + dy) + 'px';
|
|
|
+ });
|
|
|
+
|
|
|
+ document.addEventListener('mouseup', function() {
|
|
|
+ isDragging = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选中元素
|
|
|
+ function selectElement(element) {
|
|
|
+ if (selectedElement) {
|
|
|
+ selectedElement.classList.remove('selected');
|
|
|
+ }
|
|
|
+ selectedElement = element;
|
|
|
+ element.classList.add('selected');
|
|
|
+
|
|
|
+ // 显示编辑工具
|
|
|
+ document.getElementById('editTools').style.display = 'flex';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除元素
|
|
|
+ function deleteElement() {
|
|
|
+ if (selectedElement) {
|
|
|
+ selectedElement.remove();
|
|
|
+ selectedElement = null;
|
|
|
+ document.getElementById('editTools').style.display = 'none';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击canvas空白处取消选择
|
|
|
+ document.getElementById('slideCanvas').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this && selectedElement) {
|
|
|
+ selectedElement.classList.remove('selected');
|
|
|
+ selectedElement = null;
|
|
|
+ document.getElementById('editTools').style.display = 'none';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听Enter键发送消息
|
|
|
+ document.getElementById('chatInput').addEventListener('keydown', function(e) {
|
|
|
+ if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ e.preventDefault();
|
|
|
+ sendMessage();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 页面加载时显示创建弹窗
|
|
|
+ window.addEventListener('load', function() {
|
|
|
+ setTimeout(() => {
|
|
|
+ showCreateModal();
|
|
|
+ }, 300);
|
|
|
+ });
|
|
|
+
|
|
|
+ // ========== AI应用功能 ==========
|
|
|
+
|
|
|
+ // 应用数据(模拟)
|
|
|
+ const sampleApps = [
|
|
|
+ {
|
|
|
+ id: 'app-001',
|
|
|
+ name: '单词记忆卡',
|
|
|
+ description: '帮助学生记忆单词的互动卡片应用',
|
|
|
+ source: 'public',
|
|
|
+ type: 'agent',
|
|
|
+ mode: 'card',
|
|
|
+ category: '英语',
|
|
|
+ grade: '小学'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-002',
|
|
|
+ name: '数学计算练习',
|
|
|
+ description: '支持四则运算的智能练习系统',
|
|
|
+ source: 'public',
|
|
|
+ type: 'workflow',
|
|
|
+ mode: 'immersive',
|
|
|
+ category: '数学',
|
|
|
+ grade: '小学'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-003',
|
|
|
+ name: '诗词鉴赏助手',
|
|
|
+ description: 'AI辅助的诗词理解与鉴赏工具',
|
|
|
+ source: 'public',
|
|
|
+ type: 'agent',
|
|
|
+ mode: 'chat',
|
|
|
+ category: '语文',
|
|
|
+ grade: '初中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-004',
|
|
|
+ name: '化学实验模拟',
|
|
|
+ description: '虚拟化学实验室,安全探索化学反应',
|
|
|
+ source: 'public',
|
|
|
+ type: 'workflow',
|
|
|
+ mode: 'immersive',
|
|
|
+ category: '化学',
|
|
|
+ grade: '高中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-005',
|
|
|
+ name: '历史时间轴',
|
|
|
+ description: '交互式历史事件时间轴展示',
|
|
|
+ source: 'mine',
|
|
|
+ type: 'workflow',
|
|
|
+ mode: 'card',
|
|
|
+ category: '历史',
|
|
|
+ grade: '初中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-006',
|
|
|
+ name: '地理地图探索',
|
|
|
+ description: '3D地球仪和地理知识问答',
|
|
|
+ source: 'public',
|
|
|
+ type: 'agent',
|
|
|
+ mode: 'immersive',
|
|
|
+ category: '地理',
|
|
|
+ grade: '初中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-007',
|
|
|
+ name: '物理公式推导',
|
|
|
+ description: 'AI辅助的物理公式推导与解题',
|
|
|
+ source: 'mine',
|
|
|
+ type: 'agent',
|
|
|
+ mode: 'chat',
|
|
|
+ category: '物理',
|
|
|
+ grade: '高中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-008',
|
|
|
+ name: '生物细胞识别',
|
|
|
+ description: '显微镜下的细胞结构学习工具',
|
|
|
+ source: 'public',
|
|
|
+ type: 'workflow',
|
|
|
+ mode: 'card',
|
|
|
+ category: '生物',
|
|
|
+ grade: '初中'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'app-009',
|
|
|
+ name: '作文批改助手',
|
|
|
+ description: 'AI智能作文评分与改进建议',
|
|
|
+ source: 'public',
|
|
|
+ type: 'agent',
|
|
|
+ mode: 'chat',
|
|
|
+ category: '语文',
|
|
|
+ grade: '小学'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ let selectedApps = [];
|
|
|
+ let currentFilters = { source: 'all', type: 'all', mode: 'all' };
|
|
|
+ let chatMessages = [];
|
|
|
+
|
|
|
+ // 打开应用中心
|
|
|
+ function openAIAppCenter() {
|
|
|
+ document.getElementById('aiAppCenterModal').classList.add('active');
|
|
|
+ renderAppGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭应用中心
|
|
|
+ function closeAppCenter() {
|
|
|
+ document.getElementById('aiAppCenterModal').classList.remove('active');
|
|
|
+ selectedApps = [];
|
|
|
+ updateSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭
|
|
|
+ document.getElementById('aiAppCenterModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeAppCenter();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 筛选应用
|
|
|
+ function filterApps() {
|
|
|
+ // 获取三个下拉菜单的值
|
|
|
+ currentFilters.source = document.getElementById('sourceFilter').value;
|
|
|
+ currentFilters.type = document.getElementById('typeFilter').value;
|
|
|
+ currentFilters.mode = document.getElementById('modeFilter').value;
|
|
|
+
|
|
|
+ // 重新渲染应用列表
|
|
|
+ renderAppGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染应用网格
|
|
|
+ function renderAppGrid() {
|
|
|
+ const appGrid = document.getElementById('appGrid');
|
|
|
+
|
|
|
+ // 过滤应用
|
|
|
+ let filteredApps = sampleApps.filter(app => {
|
|
|
+ if (currentFilters.source !== 'all' && app.source !== currentFilters.source) return false;
|
|
|
+ if (currentFilters.type !== 'all' && app.type !== currentFilters.type) return false;
|
|
|
+ if (currentFilters.mode !== 'all' && app.mode !== currentFilters.mode) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 生成HTML
|
|
|
+ appGrid.innerHTML = filteredApps.map(app => `
|
|
|
+ <div class="app-card ${selectedApps.includes(app.id) ? 'selected' : ''}"
|
|
|
+ onclick="toggleAppSelection('${app.id}')">
|
|
|
+ <div class="app-cover">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
|
+ <rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="app-info">
|
|
|
+ <div class="app-name">${app.name}</div>
|
|
|
+ <div class="app-description">${app.description}</div>
|
|
|
+ <div class="app-meta">
|
|
|
+ <span class="app-tag">${app.type === 'agent' ? '智能体' : '工作流'}</span>
|
|
|
+ <span class="app-tag">${app.mode === 'card' ? '卡片式' : app.mode === 'immersive' ? '沉浸式' : '聊天式'}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换应用选中状态
|
|
|
+ function toggleAppSelection(appId) {
|
|
|
+ const index = selectedApps.indexOf(appId);
|
|
|
+ if (index > -1) {
|
|
|
+ selectedApps.splice(index, 1);
|
|
|
+ } else {
|
|
|
+ selectedApps.push(appId);
|
|
|
+ }
|
|
|
+ renderAppGrid();
|
|
|
+ updateSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新已选数量
|
|
|
+ function updateSelectedCount() {
|
|
|
+ const countText = document.getElementById('selectedCountText');
|
|
|
+ const confirmBtn = document.getElementById('confirmAddAppsBtn');
|
|
|
+
|
|
|
+ countText.textContent = selectedApps.length;
|
|
|
+ confirmBtn.disabled = selectedApps.length === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认添加应用
|
|
|
+ function confirmAddApps() {
|
|
|
+ if (selectedApps.length === 0) return;
|
|
|
+
|
|
|
+ const selectedAppNames = sampleApps
|
|
|
+ .filter(app => selectedApps.includes(app.id))
|
|
|
+ .map(app => app.name)
|
|
|
+ .join('、');
|
|
|
+
|
|
|
+ alert(`已添加 ${selectedApps.length} 个应用页面:\n${selectedAppNames}`);
|
|
|
+
|
|
|
+ // 这里可以添加实际的页面创建逻辑
|
|
|
+ // selectedApps.forEach(appId => {
|
|
|
+ // const app = sampleApps.find(a => a.id === appId);
|
|
|
+ // addPageFromAIApp(app);
|
|
|
+ // });
|
|
|
+
|
|
|
+ closeAppCenter();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开创建应用方式选择弹窗
|
|
|
+ function openCreateAppModal() {
|
|
|
+ document.getElementById('createAppMethodModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隐藏创建应用方式选择弹窗
|
|
|
+ function hideCreateAppMethodModal() {
|
|
|
+ document.getElementById('createAppMethodModal').classList.remove('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭
|
|
|
+ document.getElementById('createAppMethodModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ hideCreateAppMethodModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择创建方式
|
|
|
+ function selectCreateMethod(method) {
|
|
|
+ hideCreateAppMethodModal();
|
|
|
+
|
|
|
+ if (method === 'blank') {
|
|
|
+ // 创建空白应用
|
|
|
+ alert('创建空白应用页面');
|
|
|
+ // 实际应用中应创建包含空白应用画布的新页面
|
|
|
+ } else if (method === 'ai') {
|
|
|
+ // 打开AI创建界面
|
|
|
+ openAICreateInterface();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开AI创建界面
|
|
|
+ function openAICreateInterface() {
|
|
|
+ document.getElementById('aiCreateAppModal').classList.add('active');
|
|
|
+ initAIChat();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭AI创建应用浮窗
|
|
|
+ function closeAICreateModal() {
|
|
|
+ document.getElementById('aiCreateAppModal').classList.remove('active');
|
|
|
+ chatMessages = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭
|
|
|
+ document.getElementById('aiCreateAppModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeAICreateModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化AI对话
|
|
|
+ function initAIChat() {
|
|
|
+ chatMessages = [
|
|
|
+ {
|
|
|
+ id: 'msg-' + Date.now(),
|
|
|
+ role: 'ai',
|
|
|
+ content: '您好!我将帮您创建AI应用。请描述您想要的应用功能,例如:"创建一个数学口算练习应用"。'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ renderChatMessages();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染对话消息
|
|
|
+ function renderChatMessages() {
|
|
|
+ const chatMessagesContainer = document.getElementById('aiChatMessages');
|
|
|
+ chatMessagesContainer.innerHTML = chatMessages.map(msg => `
|
|
|
+ <div class="ai-chat-message ${msg.role}">
|
|
|
+ <div class="chat-avatar">${msg.role === 'ai' ? 'AI' : 'U'}</div>
|
|
|
+ <div class="chat-bubble">${msg.content}</div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+
|
|
|
+ // 滚动到底部
|
|
|
+ chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送消息给AI
|
|
|
+ function sendMessageToAI() {
|
|
|
+ const input = document.getElementById('aiChatInput');
|
|
|
+ const message = input.value.trim();
|
|
|
+
|
|
|
+ if (!message) return;
|
|
|
+
|
|
|
+ // 添加用户消息
|
|
|
+ chatMessages.push({
|
|
|
+ id: 'msg-' + Date.now(),
|
|
|
+ role: 'user',
|
|
|
+ content: message
|
|
|
+ });
|
|
|
+
|
|
|
+ input.value = '';
|
|
|
+ renderChatMessages();
|
|
|
+
|
|
|
+ // 模拟AI回复
|
|
|
+ setTimeout(() => {
|
|
|
+ const aiResponse = generateAIResponse(message);
|
|
|
+ chatMessages.push({
|
|
|
+ id: 'msg-' + Date.now(),
|
|
|
+ role: 'ai',
|
|
|
+ content: aiResponse
|
|
|
+ });
|
|
|
+ renderChatMessages();
|
|
|
+
|
|
|
+ // 更新画布预览(模拟)
|
|
|
+ updateAppCanvasPreview(message);
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成AI回复(模拟)
|
|
|
+ function generateAIResponse(userMessage) {
|
|
|
+ if (userMessage.includes('数学') || userMessage.includes('计算')) {
|
|
|
+ return '好的!我将为您创建一个数学口算练习应用。\n\n应用将包含:\n1. 题目生成器(支持加减乘除)\n2. 答题输入框\n3. 即时反馈\n4. 分数统计\n\n您可以继续描述更多需求,或点击"确认创建"完成。';
|
|
|
+ } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
|
|
|
+ return '明白了!我将创建一个英语单词学习应用。\n\n功能包括:\n1. 单词卡片展示\n2. 中英文切换\n3. 发音功能\n4. 记忆进度追踪\n\n请告诉我还需要什么功能?';
|
|
|
+ } else {
|
|
|
+ return '收到您的需求!我正在为您设计应用界面。\n\n您可以继续补充功能要求,或者查看左侧预览区的效果。满意的话,点击"确认创建"即可添加到课件中。';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新应用画布预览(模拟)
|
|
|
+ function updateAppCanvasPreview(userMessage) {
|
|
|
+ const canvas = document.getElementById('appCanvasPreview');
|
|
|
+
|
|
|
+ if (userMessage.includes('数学') || userMessage.includes('计算')) {
|
|
|
+ canvas.innerHTML = `
|
|
|
+ <div style="background: white; padding: 20px; border-radius: 12px; width: 80%; text-align: center;">
|
|
|
+ <div style="font-size: 32px; font-weight: 600; color: #111827; margin-bottom: 20px;">
|
|
|
+ 25 + 17 = ?
|
|
|
+ </div>
|
|
|
+ <input type="text" style="width: 200px; padding: 12px; border: 2px solid #e5e7eb; border-radius: 10px; font-size: 18px; text-align: center;" placeholder="输入答案">
|
|
|
+ <div style="margin-top: 16px;">
|
|
|
+ <button style="padding: 10px 30px; background: #285cf5; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">提交</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ } else if (userMessage.includes('单词') || userMessage.includes('英语')) {
|
|
|
+ canvas.innerHTML = `
|
|
|
+ <div style="background: white; padding: 30px; border-radius: 12px; width: 80%; text-align: center;">
|
|
|
+ <div style="font-size: 40px; font-weight: 700; color: #111827; margin-bottom: 12px;">
|
|
|
+ Apple
|
|
|
+ </div>
|
|
|
+ <div style="font-size: 18px; color: #6b7280; margin-bottom: 20px;">
|
|
|
+ 苹果
|
|
|
+ </div>
|
|
|
+ <button style="padding: 10px 30px; background: #3b82f6; color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;">🔊 发音</button>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ } else {
|
|
|
+ canvas.innerHTML = `
|
|
|
+ <div style="background: white; padding: 40px; border-radius: 12px; width: 80%; text-align: center; color: #9ca3af;">
|
|
|
+ <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 12px;">
|
|
|
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
|
+ <circle cx="8.5" cy="8.5" r="1.5"/>
|
|
|
+ <polyline points="21 15 16 10 5 21"/>
|
|
|
+ </svg>
|
|
|
+ <div style="font-size: 14px;">应用界面预览</div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认创建AI应用
|
|
|
+ function confirmAICreatedApp() {
|
|
|
+ alert('AI应用已创建!\n\n新的应用页面已添加到课件中。');
|
|
|
+ closeAICreateModal();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 监听AI对话输入框的Enter键
|
|
|
+ document.getElementById('aiChatInput').addEventListener('keydown', function(e) {
|
|
|
+ if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ e.preventDefault();
|
|
|
+ sendMessageToAI();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // ========== 交互网页功能 ==========
|
|
|
+ let selectedWebs = [];
|
|
|
+ let webFilters = { source: 'all', subject: 'all', grade: 'all' };
|
|
|
+ let currentWebDetail = null;
|
|
|
+ let selectedWebFile = null;
|
|
|
+
|
|
|
+ // 切换交互网页的内部视图 (list, upload, crawl)
|
|
|
+ function switchToWebView(viewType) {
|
|
|
+ const webListView = document.getElementById('webListView');
|
|
|
+ const webConfigView = document.getElementById('webConfigView');
|
|
|
+ const uploadWebView = document.getElementById('uploadWebView');
|
|
|
+ const crawlWebView = document.getElementById('crawlWebView');
|
|
|
+
|
|
|
+ // 先隐藏所有主视图和配置视图
|
|
|
+ webListView.classList.add('hidden');
|
|
|
+ webConfigView.classList.add('hidden');
|
|
|
+ uploadWebView.classList.add('hidden');
|
|
|
+ crawlWebView.classList.add('hidden');
|
|
|
+
|
|
|
+ if (viewType === 'list') {
|
|
|
+ webListView.classList.remove('hidden');
|
|
|
+ } else if (viewType === 'upload') {
|
|
|
+ webConfigView.classList.remove('hidden');
|
|
|
+ uploadWebView.classList.remove('hidden');
|
|
|
+ } else if (viewType === 'crawl') {
|
|
|
+ webConfigView.classList.remove('hidden');
|
|
|
+ crawlWebView.classList.remove('hidden');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从配置界面返回列表
|
|
|
+ function backToWebList() {
|
|
|
+ switchToWebView('list');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开上传网页视图
|
|
|
+ function uploadWebPage() {
|
|
|
+ switchToWebView('upload');
|
|
|
+ resetUploadForm();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开爬取网页视图
|
|
|
+ function crawlWebPage() {
|
|
|
+ switchToWebView('crawl');
|
|
|
+
|
|
|
+ // 重置表单
|
|
|
+ document.getElementById('crawlWebName').value = '';
|
|
|
+ document.getElementById('crawlWebUrl').value = '';
|
|
|
+ document.getElementById('startCrawlBtn').disabled = true;
|
|
|
+
|
|
|
+ updateCrawlStatus('waiting', '等待输入...', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ let currentUploadMode = 'file'; // 'file' or 'code'
|
|
|
+
|
|
|
+ function switchUploadTab(mode) {
|
|
|
+ currentUploadMode = mode;
|
|
|
+ const fileTab = document.getElementById('uploadFileTab');
|
|
|
+ const codeTab = document.getElementById('pasteCodeTab');
|
|
|
+ const filePanel = document.getElementById('uploadFilePanel');
|
|
|
+ const codePanel = document.getElementById('pasteCodePanel');
|
|
|
+
|
|
|
+ if (mode === 'file') {
|
|
|
+ fileTab.classList.add('active');
|
|
|
+ codeTab.classList.remove('active');
|
|
|
+ filePanel.classList.remove('hidden');
|
|
|
+ codePanel.classList.add('hidden');
|
|
|
+ } else {
|
|
|
+ fileTab.classList.remove('active');
|
|
|
+ codeTab.classList.add('active');
|
|
|
+ filePanel.classList.add('hidden');
|
|
|
+ codePanel.classList.remove('hidden');
|
|
|
+ }
|
|
|
+ validateUploadForm();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleFileSelect(event) {
|
|
|
+ const file = event.target.files[0];
|
|
|
+ const fileNameDisplay = document.getElementById('fileNameDisplay');
|
|
|
+ const fileUploadArea = document.getElementById('fileUploadArea');
|
|
|
+ if (!file) {
|
|
|
+ fileNameDisplay.textContent = '';
|
|
|
+ fileNameDisplay.style.display = 'none';
|
|
|
+ fileUploadArea.style.display = 'flex';
|
|
|
+ selectedWebFile = null;
|
|
|
+ validateUploadForm();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedWebFile = file;
|
|
|
+ fileNameDisplay.textContent = '已选择文件: ' + file.name;
|
|
|
+ fileNameDisplay.style.display = 'block';
|
|
|
+ fileUploadArea.style.display = 'none';
|
|
|
+
|
|
|
+ // 自动填充网页名称
|
|
|
+ const fileName = file.name.replace(/\.[^/.]+$/, "");
|
|
|
+ if (!document.getElementById('uploadWebName').value) {
|
|
|
+ document.getElementById('uploadWebName').value = fileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新校验按钮状态
|
|
|
+ validateUploadForm();
|
|
|
+ }
|
|
|
+
|
|
|
+ function validateUploadForm() {
|
|
|
+ const webName = document.getElementById('uploadWebName').value.trim();
|
|
|
+ const fileInput = document.getElementById('fileInput').files[0];
|
|
|
+ const codeInput = document.getElementById('pasteCodeTextarea').value.trim();
|
|
|
+ const confirmBtn = document.getElementById('confirmCreateWebBtn');
|
|
|
+
|
|
|
+ let isModeValid = false;
|
|
|
+ if (currentUploadMode === 'file') {
|
|
|
+ isModeValid = !!fileInput;
|
|
|
+ } else { // mode === 'code'
|
|
|
+ isModeValid = codeInput.length > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (webName.length > 0 && isModeValid) {
|
|
|
+ confirmBtn.disabled = false;
|
|
|
+ updateUploadButtonStatus('ready', '开始上传', 'ready');
|
|
|
+ } else {
|
|
|
+ confirmBtn.disabled = true;
|
|
|
+ updateUploadButtonStatus('waiting', '等待上传...', '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function confirmCreateWebPage() {
|
|
|
+ if (currentUploadMode === 'file') {
|
|
|
+ startUploadWeb();
|
|
|
+ } else {
|
|
|
+ startRecognizeCode();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function startRecognizeCode() {
|
|
|
+ const uploadBtn = document.getElementById('confirmCreateWebBtn');
|
|
|
+ uploadBtn.disabled = true;
|
|
|
+ updateUploadButtonStatus('loading', '解析中...', 'loading');
|
|
|
+
|
|
|
+ const code = document.getElementById('pasteCodeTextarea').value;
|
|
|
+ console.log('Recognizing code:', code);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ updateUploadButtonStatus('success', '解析完成!', 'success');
|
|
|
+ // Mock success
|
|
|
+ addWebToPage({ name: document.getElementById('uploadWebName').value, type: 'pasted' });
|
|
|
+ setTimeout(() => {
|
|
|
+ resetUploadForm();
|
|
|
+ backToWebList();
|
|
|
+ }, 800);
|
|
|
+ }, 800);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 模拟网页数据
|
|
|
+ const sampleWebs = [
|
|
|
+ {
|
|
|
+ id: 'web_001',
|
|
|
+ name: '化学实验-酸碱中和反应',
|
|
|
+ source: 'public',
|
|
|
+ subject: '化学',
|
|
|
+ grade: '初中',
|
|
|
+ description: '通过交互式动画展示酸碱中和反应的全过程,学生可以自主调整反应物浓度',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/chem-001',
|
|
|
+ duration: '10分钟',
|
|
|
+ author: '张老师',
|
|
|
+ createTime: '2024-01-15'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'web_002',
|
|
|
+ name: '物理模拟-牛顿摆',
|
|
|
+ source: 'public',
|
|
|
+ subject: '物理',
|
|
|
+ grade: '高中',
|
|
|
+ description: '3D交互式牛顿摆模拟,可调整球数、质量和初速度',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/physics-001',
|
|
|
+ duration: '15分钟',
|
|
|
+ author: '李老师',
|
|
|
+ createTime: '2024-01-10'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'web_003',
|
|
|
+ name: '数学可视化-函数图像',
|
|
|
+ source: 'mine',
|
|
|
+ subject: '数学',
|
|
|
+ grade: '高中',
|
|
|
+ description: '动态函数图像绘制工具,支持多种函数类型',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/math-001',
|
|
|
+ duration: '20分钟',
|
|
|
+ author: '我',
|
|
|
+ createTime: '2024-01-20'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'web_004',
|
|
|
+ name: '地理探索-地球仪3D',
|
|
|
+ source: 'public',
|
|
|
+ subject: '地理',
|
|
|
+ grade: '初中',
|
|
|
+ description: '交互式3D地球仪,可查看地形、气候、人口等多种数据层',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/geo-001',
|
|
|
+ duration: '12分钟',
|
|
|
+ author: '王老师',
|
|
|
+ createTime: '2024-01-18'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'web_005',
|
|
|
+ name: '生物模型-细胞结构',
|
|
|
+ source: 'public',
|
|
|
+ subject: '生物',
|
|
|
+ grade: '初中',
|
|
|
+ description: '3D细胞结构模型,可放大查看各细胞器的详细结构',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/bio-001',
|
|
|
+ duration: '8分钟',
|
|
|
+ author: '赵老师',
|
|
|
+ createTime: '2024-01-12'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'web_006',
|
|
|
+ name: '历史时间轴-中国近代史',
|
|
|
+ source: 'mine',
|
|
|
+ subject: '历史',
|
|
|
+ grade: '高中',
|
|
|
+ description: '交互式历史时间轴,包含重要事件、人物和影响',
|
|
|
+ preview: '',
|
|
|
+ url: 'https://example.com/history-001',
|
|
|
+ duration: '25分钟',
|
|
|
+ author: '我',
|
|
|
+ createTime: '2024-01-22'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // ========== 网页中心功能 ==========
|
|
|
+ // 打开网页中心
|
|
|
+ function openWebCenter() {
|
|
|
+ document.getElementById('webCenterModal').classList.add('active');
|
|
|
+ renderWebGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭网页中心
|
|
|
+ function closeWebCenter() {
|
|
|
+ document.getElementById('webCenterModal').classList.remove('active');
|
|
|
+ selectedWebs = [];
|
|
|
+ updateWebSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭
|
|
|
+ document.getElementById('webCenterModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeWebCenter();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 筛选网页
|
|
|
+ function filterWebs() {
|
|
|
+ webFilters.source = document.getElementById('webSourceFilter').value;
|
|
|
+ webFilters.subject = document.getElementById('webSubjectFilter').value;
|
|
|
+ webFilters.grade = document.getElementById('webGradeFilter').value;
|
|
|
+ renderWebGrid();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染网页网格
|
|
|
+ function renderWebGrid() {
|
|
|
+ const webGrid = document.getElementById('webGrid');
|
|
|
+
|
|
|
+ // 过滤网页
|
|
|
+ let filteredWebs = sampleWebs.filter(web => {
|
|
|
+ if (webFilters.source !== 'all' && web.source !== webFilters.source) return false;
|
|
|
+ if (webFilters.subject !== 'all' && web.subject !== webFilters.subject) return false;
|
|
|
+ if (webFilters.grade !== 'all' && web.grade !== webFilters.grade) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 生成HTML
|
|
|
+ webGrid.innerHTML = filteredWebs.map(web => `
|
|
|
+ <div class="web-card ${selectedWebs.includes(web.id) ? 'selected' : ''}"
|
|
|
+ onclick="toggleWebSelection('${web.id}')">
|
|
|
+ <div class="web-preview">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <rect x="2" y="3" width="20" height="14" rx="2"/>
|
|
|
+ <line x1="2" y1="7" x2="22" y2="7"/>
|
|
|
+ <path d="M8 11h8M8 14h5"/>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="web-info">
|
|
|
+ <div class="web-name">${web.name}</div>
|
|
|
+ <div class="web-description">${web.description}</div>
|
|
|
+ <div class="web-meta">
|
|
|
+ <span class="web-tag">${web.subject}</span>
|
|
|
+ <span class="web-tag">${web.grade}</span>
|
|
|
+ <span class="web-tag">${web.duration}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换网页选中状态
|
|
|
+ function toggleWebSelection(webId) {
|
|
|
+ const index = selectedWebs.indexOf(webId);
|
|
|
+ if (index > -1) {
|
|
|
+ selectedWebs.splice(index, 1);
|
|
|
+ } else {
|
|
|
+ selectedWebs.push(webId);
|
|
|
+ }
|
|
|
+ renderWebGrid();
|
|
|
+ updateWebSelectedCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新选中计数
|
|
|
+ function updateWebSelectedCount() {
|
|
|
+ const countText = document.getElementById('webSelectedCountText');
|
|
|
+ const confirmBtn = document.getElementById('confirmAddWebsBtn');
|
|
|
+
|
|
|
+ countText.textContent = selectedWebs.length;
|
|
|
+ confirmBtn.disabled = selectedWebs.length === 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认添加网页
|
|
|
+ function confirmAddWebs() {
|
|
|
+ if (selectedWebs.length === 0) return;
|
|
|
+
|
|
|
+ console.log('添加网页:', selectedWebs);
|
|
|
+ alert(`已添加 ${selectedWebs.length} 个网页到课件`);
|
|
|
+
|
|
|
+ // 这里应该实际创建新页面并插入网页内容
|
|
|
+ // 暂时模拟功能
|
|
|
+ closeWebCenter();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开网页详情
|
|
|
+ function openWebDetail(webId) {
|
|
|
+ const web = sampleWebs.find(w => w.id === webId);
|
|
|
+ if (!web) return;
|
|
|
+
|
|
|
+ currentWebDetail = web;
|
|
|
+
|
|
|
+ // 填充详情信息
|
|
|
+ document.getElementById('webDetailName').textContent = web.name;
|
|
|
+ document.getElementById('webDetailSubject').textContent = web.subject;
|
|
|
+ document.getElementById('webDetailGrade').textContent = web.grade;
|
|
|
+ document.getElementById('webDetailDuration').textContent = web.duration;
|
|
|
+ document.getElementById('webDetailDescription').textContent = web.description;
|
|
|
+ document.getElementById('webDetailAuthor').textContent = web.author;
|
|
|
+ document.getElementById('webDetailTime').textContent = web.createTime;
|
|
|
+ document.getElementById('webDetailPreviewTitle').textContent = web.name;
|
|
|
+ document.getElementById('webDetailIframe').src = web.url;
|
|
|
+
|
|
|
+ // 显示详情浮窗
|
|
|
+ document.getElementById('webDetailModal').classList.add('active');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭网页详情
|
|
|
+ function closeWebDetail() {
|
|
|
+ document.getElementById('webDetailModal').classList.remove('active');
|
|
|
+ document.getElementById('webDetailIframe').src = '';
|
|
|
+ currentWebDetail = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击遮罩关闭详情
|
|
|
+ document.getElementById('webDetailModal').addEventListener('click', function(e) {
|
|
|
+ if (e.target === this) {
|
|
|
+ closeWebDetail();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 切换预览全屏
|
|
|
+ function toggleWebPreviewFullscreen() {
|
|
|
+ const iframe = document.getElementById('webDetailIframe');
|
|
|
+ if (iframe.requestFullscreen) {
|
|
|
+ iframe.requestFullscreen();
|
|
|
+ } else if (iframe.webkitRequestFullscreen) {
|
|
|
+ iframe.webkitRequestFullscreen();
|
|
|
+ } else if (iframe.mozRequestFullScreen) {
|
|
|
+ iframe.mozRequestFullScreen();
|
|
|
+ } else if (iframe.msRequestFullscreen) {
|
|
|
+ iframe.msRequestFullscreen();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从详情页添加网页
|
|
|
+ function addWebFromDetail() {
|
|
|
+ if (!currentWebDetail) return;
|
|
|
+
|
|
|
+ console.log('从详情页添加网页:', currentWebDetail);
|
|
|
+ alert(`已添加"${currentWebDetail.name}"到课件`);
|
|
|
+
|
|
|
+ // 这里应该实际创建新页面并插入网页内容
|
|
|
+ closeWebDetail();
|
|
|
+ closeWebCenter();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 上传网页功能 ==========
|
|
|
+
|
|
|
+ // 开始上传网页
|
|
|
+ function startUploadWeb() {
|
|
|
+ const webName = document.getElementById('uploadWebName').value.trim();
|
|
|
+
|
|
|
+ if (!webName) {
|
|
|
+ alert('请输入网页名称');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!selectedWebFile) {
|
|
|
+ alert('请选择文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示解析中状态
|
|
|
+ const uploadBtn = document.getElementById('confirmCreateWebBtn');
|
|
|
+ uploadBtn.disabled = true;
|
|
|
+ updateUploadButtonStatus('loading', '解析中...', 'loading');
|
|
|
+
|
|
|
+ // 模拟解析过程
|
|
|
+ setTimeout(() => {
|
|
|
+ updateUploadButtonStatus('success', '解析完成!', 'success');
|
|
|
+
|
|
|
+ // 2秒后创建页面并显示内容
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('创建新页面并插入网页内容:', webName);
|
|
|
+ alert(`网页"${webName}"已成功添加到课件`);
|
|
|
+ resetUploadForm();
|
|
|
+ returnFromUpload();
|
|
|
+ }, 2000);
|
|
|
+ }, 2000);
|
|
|
+ }
|
|
|
+
|
|
|
+ function resetUploadForm() {
|
|
|
+ const uploadBtn = document.getElementById('confirmCreateWebBtn');
|
|
|
+ document.getElementById('uploadWebName').value = '';
|
|
|
+ document.getElementById('fileInput').value = '';
|
|
|
+ document.getElementById('pasteCodeTextarea').value = '';
|
|
|
+ const fileNameDisplay = document.getElementById('fileNameDisplay');
|
|
|
+ const fileUploadArea = document.getElementById('fileUploadArea');
|
|
|
+ fileNameDisplay.textContent = '';
|
|
|
+ fileNameDisplay.style.display = 'none';
|
|
|
+ fileUploadArea.style.display = 'flex';
|
|
|
+ selectedWebFile = null;
|
|
|
+ uploadBtn.disabled = true;
|
|
|
+ updateUploadButtonStatus('waiting', '等待上传...', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回列表视图
|
|
|
+ function returnFromUpload() {
|
|
|
+ switchToWebView('list');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新上传按钮状态
|
|
|
+ function updateUploadButtonStatus(statusKey, text, className) {
|
|
|
+ const uploadBtn = document.getElementById('confirmCreateWebBtn');
|
|
|
+ const statusIcon = document.getElementById('uploadStatusIcon');
|
|
|
+ const statusText = document.getElementById('uploadStatusText');
|
|
|
+
|
|
|
+ uploadBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
|
|
|
+ if (className) {
|
|
|
+ uploadBtn.classList.add(`status-${className}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
|
|
|
+ statusText.textContent = text;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文件拖拽上传
|
|
|
+ const fileUploadArea = document.getElementById('fileUploadArea');
|
|
|
+
|
|
|
+ fileUploadArea.addEventListener('dragover', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ fileUploadArea.classList.add('dragover');
|
|
|
+ });
|
|
|
+
|
|
|
+ fileUploadArea.addEventListener('dragleave', () => {
|
|
|
+ fileUploadArea.classList.remove('dragover');
|
|
|
+ });
|
|
|
+
|
|
|
+ fileUploadArea.addEventListener('drop', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ fileUploadArea.classList.remove('dragover');
|
|
|
+
|
|
|
+ const files = e.dataTransfer.files;
|
|
|
+ if (files.length > 0) {
|
|
|
+ const file = files[0];
|
|
|
+ // 检查文件类型
|
|
|
+ const validTypes = ['.html', '.htm', '.zip'];
|
|
|
+ const fileExt = '.' + file.name.split('.').pop().toLowerCase();
|
|
|
+
|
|
|
+ if (validTypes.includes(fileExt)) {
|
|
|
+ document.getElementById('fileInput').files = files;
|
|
|
+ handleFileSelect({ target: { files: files } });
|
|
|
+ } else {
|
|
|
+ alert('仅支持 HTML、HTM、ZIP 格式文件');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // ========== 交互网页按钮状态图标 ==========
|
|
|
+ const statusIcons = {
|
|
|
+ waiting: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>',
|
|
|
+ ready: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="10 7 17 12 10 17 10 7"></polygon><line x1="7" y1="7" x2="7" y2="17"></line></svg>',
|
|
|
+ loading: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="56" stroke-dashoffset="20"></circle></svg>',
|
|
|
+ success: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"></path></svg>',
|
|
|
+ error: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"></circle><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>'
|
|
|
+ };
|
|
|
+
|
|
|
+ // 验证爬取URL
|
|
|
+ function validateCrawlUrl() {
|
|
|
+ const url = document.getElementById('crawlWebUrl').value.trim();
|
|
|
+ const name = document.getElementById('crawlWebName').value.trim();
|
|
|
+ const startBtn = document.getElementById('startCrawlBtn');
|
|
|
+
|
|
|
+ if (url && name && isValidUrl(url)) {
|
|
|
+ startBtn.disabled = false;
|
|
|
+ updateCrawlStatus('ready', '开始爬取', 'ready');
|
|
|
+ } else if (url && !isValidUrl(url)) {
|
|
|
+ startBtn.disabled = true;
|
|
|
+ updateCrawlStatus('error', 'URL格式不正确,请检查', 'error');
|
|
|
+ } else {
|
|
|
+ startBtn.disabled = true;
|
|
|
+ updateCrawlStatus('waiting', '等待输入...', '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证URL格式
|
|
|
+ function isValidUrl(string) {
|
|
|
+ try {
|
|
|
+ const url = new URL(string);
|
|
|
+ return url.protocol === 'http:' || url.protocol === 'https:';
|
|
|
+ } catch (_) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始爬取网页
|
|
|
+ function startCrawlWeb() {
|
|
|
+ const webName = document.getElementById('crawlWebName').value.trim();
|
|
|
+ const webUrl = document.getElementById('crawlWebUrl').value.trim();
|
|
|
+ const startBtn = document.getElementById('startCrawlBtn');
|
|
|
+
|
|
|
+ if (!webName) {
|
|
|
+ alert('请输入网页名称');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!webUrl || !isValidUrl(webUrl)) {
|
|
|
+ alert('请输入有效的网页链接');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示爬取中状态
|
|
|
+ startBtn.disabled = true;
|
|
|
+ updateCrawlStatus('loading', '爬取中...', 'loading');
|
|
|
+
|
|
|
+ // 模拟爬取过程
|
|
|
+ setTimeout(() => {
|
|
|
+ updateCrawlStatus('success', '爬取完成!', 'success');
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ resetCrawlForm();
|
|
|
+ }, 1200);
|
|
|
+ }, 2500);
|
|
|
+ }
|
|
|
+
|
|
|
+ function resetCrawlForm() {
|
|
|
+ const startBtn = document.getElementById('startCrawlBtn');
|
|
|
+ const nameInput = document.getElementById('crawlWebName');
|
|
|
+ const urlInput = document.getElementById('crawlWebUrl');
|
|
|
+ nameInput.value = '';
|
|
|
+ urlInput.value = '';
|
|
|
+ startBtn.disabled = true;
|
|
|
+ updateCrawlStatus('waiting', '等待输入...', '');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回列表视图(保留,未使用)
|
|
|
+ function returnFromCrawl() {
|
|
|
+ switchToWebView('list');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新爬取状态
|
|
|
+ function updateCrawlStatus(statusKey, text, className) {
|
|
|
+ const startBtn = document.getElementById('startCrawlBtn');
|
|
|
+ const statusIcon = document.getElementById('crawlStatusIcon');
|
|
|
+ const statusText = document.getElementById('crawlStatusText');
|
|
|
+
|
|
|
+ startBtn.classList.remove('status-loading', 'status-success', 'status-error', 'status-ready');
|
|
|
+ if (className) {
|
|
|
+ startBtn.classList.add(`status-${className}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ statusIcon.innerHTML = statusIcons[statusKey] || statusIcons.waiting;
|
|
|
+ statusText.textContent = text;
|
|
|
+ }
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|