فهرست منبع

feat(Editor): 重构编辑器头部并添加课程标题编辑功能

refactor(Canvas): 添加课程加载事件发射器
fix(Student): 修正PPT文件命名使用title字段
lsc 6 ساعت پیش
والد
کامیت
30861b9b56
3فایلهای تغییر یافته به همراه486 افزوده شده و 29 حذف شده
  1. 6 0
      src/views/Editor/Canvas/index.vue
  2. 479 28
      src/views/Editor/index3.vue
  3. 1 1
      src/views/Student/index.vue

+ 6 - 0
src/views/Editor/Canvas/index.vue

@@ -173,6 +173,10 @@ const props = withDefaults(defineProps<Props>(), {
   courseid: null,
   courseid: null,
 })
 })
 
 
+const emit = defineEmits<{
+  (e: 'courseLoaded', data: any): void
+}>()
+
 const mainStore = useMainStore()
 const mainStore = useMainStore()
 const {
 const {
   activeElementIdList,
   activeElementIdList,
@@ -266,6 +270,8 @@ const getCourseDetail = async () => {
     const res = await api.getCourseDetail(props.courseid as string)
     const res = await api.getCourseDetail(props.courseid as string)
     console.log(res)
     console.log(res)
     const courseDetail = res[0][0]
     const courseDetail = res[0][0]
+    emit('courseLoaded', courseDetail)
+    
     const pptJSONUrl = JSON.parse(courseDetail.chapters).pptData ? JSON.parse(courseDetail.chapters).pptData : ''
     const pptJSONUrl = JSON.parse(courseDetail.chapters).pptData ? JSON.parse(courseDetail.chapters).pptData : ''
     console.log(pptJSONUrl)
     console.log(pptJSONUrl)
     
     

+ 479 - 28
src/views/Editor/index3.vue

@@ -1,11 +1,152 @@
 <template>
 <template>
   <div class="pptist-editor">
   <div class="pptist-editor">
-    <EditorHeader class="layout-header" />
+    <div class="ppt_header">
+      <div class="header-left">
+        <div class="dropdown-menu">
+          <button class="dropdown-toggle" @click="toggleDropdown">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <g id="logo">
+                <g id="Group 11">
+                  <path id="Subtract" fill-rule="evenodd" clip-rule="evenodd"
+                    d="M15.7522 20.215C20.0656 18.3614 22.483 12.3886 20.6867 7.69776C19.6205 4.91327 14.8444 4.162 10.0191 6.01976C5.19376 7.87752 2.14642 11.6408 3.21267 14.4253C5.89294 20.8328 11.4388 22.0686 15.7522 20.215ZM13.5748 15.1938C9.19666 16.8794 4.92206 16.3514 4.02721 14.0145C3.13236 11.6776 5.95613 8.41676 10.3343 6.73117C14.7124 5.04559 18.987 5.57357 19.8818 7.91045C20.4333 9.35053 19.5726 11.1415 17.8062 12.6985V15.066L15.9598 14.0363C15.2358 14.4703 14.4353 14.8625 13.5748 15.1938Z"
+                    fill="#3681FC" />
+                  <path id="Ellipse 2"
+                    d="M5.80332 13.1298C6.42226 14.7462 9.59505 15.0282 12.8899 13.7596C13.506 13.5225 14.0827 13.2479 14.6091 12.9476L15.9575 14.0353L15.9575 12.0377C17.3937 10.886 18.1291 9.56474 17.7352 8.53605C17.1162 6.91968 13.9434 6.6377 10.6485 7.90624C7.35365 9.17477 5.18437 11.5135 5.80332 13.1298Z"
+                    fill="#3681FC" />
+                </g>
+              </g>
+            </svg>
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <g id="chevron-down">
+                <path id="Icon" d="M6 9L12 15L18 9" stroke="black" stroke-opacity="0.6" stroke-width="2"
+                  stroke-linecap="round" stroke-linejoin="round" />
+              </g>
+            </svg>
+          </button>
+          <div class="dropdown-content" v-if="isDropdownOpen">
+            <div class="dropdown-item" @click="handleBackToList"><svg width="24" height="24" viewBox="0 0 24 24"
+                fill="none" xmlns="http://www.w3.org/2000/svg">
+                <g clip-path="url(#clip0_446_5970)">
+                  <g id="chevron-left">
+                    <path id="Icon" d="M15 18L9 12L15 6" stroke="black" stroke-opacity="0.6" stroke-width="2"
+                      stroke-linecap="round" stroke-linejoin="round" />
+                  </g>
+                </g>
+                <defs>
+                  <clipPath id="clip0_446_5970">
+                    <rect width="24" height="24" fill="white" />
+                  </clipPath>
+                </defs>
+              </svg>
+              返回列表</div>
+            <div class="dropdown-item" @click="handleSettings">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <g id="&#232;&#174;&#190;&#231;&#189;&#174;">
+                  <g id="Frame">
+                    <path id="Vector"
+                      d="M19.1497 11.12L16.6227 6.177C16.4608 5.86021 16.2145 5.59433 15.9109 5.40869C15.6074 5.22305 15.2585 5.12488 14.9027 5.125H9.14975C8.42975 5.125 7.76975 5.525 7.43775 6.163L4.85775 11.106C4.71361 11.382 4.63833 11.6887 4.63833 12C4.63833 12.3113 4.71361 12.618 4.85775 12.894L7.43775 17.837C7.60098 18.15 7.84688 18.4122 8.14871 18.5952C8.45055 18.7782 8.79677 18.875 9.14975 18.875H14.9027C15.6287 18.875 16.2927 18.469 16.6227 17.823L19.1497 12.879C19.2889 12.6069 19.3614 12.3056 19.3614 12C19.3614 11.6944 19.2889 11.3931 19.1497 11.121V11.12ZM17.7357 18.392C17.469 18.9138 17.0633 19.3518 16.5634 19.6576C16.0634 19.9634 15.4888 20.1251 14.9027 20.125H9.14975C8.56831 20.1249 7.99801 19.9655 7.50083 19.664C7.00366 19.3625 6.59862 18.9305 6.32975 18.415L3.74975 13.472C3.51255 13.0176 3.38867 12.5126 3.38867 12C3.38867 11.4874 3.51255 10.9824 3.74975 10.528L6.32975 5.584C6.59876 5.06865 7.00385 4.63687 7.50102 4.33558C7.99818 4.03428 8.56841 3.87499 9.14975 3.875H14.9027C16.0977 3.875 17.1917 4.545 17.7357 5.608L20.2627 10.552C20.7277 11.462 20.7277 12.539 20.2627 13.448L17.7357 18.392Z"
+                      fill="#5A5A68" />
+                    <path id="Vector_2"
+                      d="M12 15.625C11.524 15.625 11.0526 15.5312 10.6128 15.3491C10.173 15.1669 9.77335 14.8999 9.43674 14.5633C9.10013 14.2266 8.83311 13.827 8.65094 13.3872C8.46876 12.9474 8.375 12.476 8.375 12C8.375 11.524 8.46876 11.0526 8.65094 10.6128C8.83311 10.173 9.10013 9.77335 9.43674 9.43674C9.77335 9.10013 10.173 8.83311 10.6128 8.65094C11.0526 8.46876 11.524 8.375 12 8.375C12.9614 8.375 13.8834 8.75692 14.5633 9.43674C15.2431 10.1166 15.625 11.0386 15.625 12C15.625 12.9614 15.2431 13.8834 14.5633 14.5633C13.8834 15.2431 12.9614 15.625 12 15.625ZM12 14.375C12.3119 14.375 12.6207 14.3136 12.9089 14.1942C13.197 14.0749 13.4588 13.8999 13.6794 13.6794C13.8999 13.4588 14.0749 13.197 14.1942 12.9089C14.3136 12.6207 14.375 12.3119 14.375 12C14.375 11.6881 14.3136 11.3793 14.1942 11.0911C14.0749 10.803 13.8999 10.5412 13.6794 10.3206C13.4588 10.1001 13.197 9.92514 12.9089 9.80579C12.6207 9.68643 12.3119 9.625 12 9.625C11.3701 9.625 10.766 9.87522 10.3206 10.3206C9.87522 10.766 9.625 11.3701 9.625 12C9.625 12.6299 9.87522 13.234 10.3206 13.6794C10.766 14.1248 11.3701 14.375 12 14.375Z"
+                      fill="#5A5A68" />
+                  </g>
+                </g>
+              </svg>
+              设置
+            </div>
+            <div class="dropdown-item" @click="handleSaveAsCopy"><svg width="24" height="24" viewBox="0 0 24 24"
+                fill="none" xmlns="http://www.w3.org/2000/svg">
+                <g>
+                  <path id="Vector"
+                    d="M14.7244 7.04762C15.0339 7.04762 15.3307 7.16803 15.5495 7.38235C15.7684 7.59668 15.8913 7.88737 15.8913 8.19048V18.8571C15.8913 19.1602 15.7684 19.4509 15.5495 19.6653C15.3307 19.8796 15.0339 20 14.7244 20H6.16693C6.01368 20 5.86194 19.9704 5.72036 19.913C5.57878 19.8556 5.45014 19.7714 5.34178 19.6653C5.23343 19.5591 5.14747 19.4332 5.08883 19.2945C5.03018 19.1558 5 19.0072 5 18.8571V8.19048C5 7.88737 5.12294 7.59668 5.34178 7.38235C5.56063 7.16803 5.85744 7.04762 6.16693 7.04762H14.7244ZM14.3354 8.57143H6.5559V18.4762H14.3354V8.57143ZM17.8331 4C18.1233 3.99982 18.4032 4.10557 18.6181 4.2966C18.833 4.48762 18.9675 4.75022 18.9953 5.03314L19 5.14286V15.4232C18.9998 15.6174 18.9239 15.8042 18.7877 15.9454C18.6516 16.0866 18.4656 16.1716 18.2676 16.183C18.0697 16.1944 17.8748 16.1313 17.7227 16.0067C17.5707 15.882 17.473 15.7052 17.4495 15.5124L17.4441 15.4232V5.52381H9.6677C9.47716 5.52379 9.29325 5.45527 9.15086 5.33126C9.00846 5.20726 8.91749 5.03638 8.8952 4.85105L8.88975 4.7619C8.88978 4.57529 8.95973 4.39517 9.08635 4.25572C9.21297 4.11626 9.38745 4.02717 9.57668 4.00533L9.6677 4H17.8331Z"
+                    fill="black" fill-opacity="0.6" />
+                </g>
+              </svg>
+              另存为副本</div>
+            <div class="dropdown-item danger" @click="handleDelete"><svg width="24" height="24" viewBox="0 0 24 24"
+                fill="none" xmlns="http://www.w3.org/2000/svg">
+                <g>
+                  <g id="Frame" clip-path="url(#clip0_446_5972)">
+                    <path id="Vector"
+                      d="M18.5021 6.25H15.4996V5C15.4996 4.86739 15.4469 4.74021 15.3531 4.64645C15.2594 4.55268 15.1322 4.5 14.9996 4.5H8.99957C8.86696 4.5 8.73978 4.55268 8.64602 4.64645C8.55225 4.74021 8.49957 4.86739 8.49957 5V6.25H5.49707C5.36446 6.25 5.23729 6.30268 5.14352 6.39645C5.04975 6.49021 4.99707 6.61739 4.99707 6.75C4.99707 6.88261 5.04975 7.00979 5.14352 7.10355C5.23729 7.19732 5.36446 7.25 5.49707 7.25H6.02457L7.24207 18.1665C7.28302 18.5332 7.45768 18.8719 7.73265 19.1179C8.00762 19.3639 8.36361 19.4999 8.73257 19.5H15.2661C15.6351 19.5 15.9911 19.3639 16.2662 19.1179C16.5412 18.8719 16.716 18.5332 16.7571 18.1665L17.9746 7.25H18.5016C18.5672 7.25003 18.6323 7.23713 18.6929 7.21204C18.7536 7.18694 18.8087 7.15014 18.8552 7.10373C18.9016 7.05732 18.9385 7.00222 18.9637 6.94157C18.9888 6.88092 19.0018 6.81591 19.0018 6.75025C19.0019 6.68459 18.989 6.61956 18.9639 6.55889C18.9388 6.49821 18.902 6.44308 18.8555 6.39662C18.8091 6.35017 18.754 6.31331 18.6934 6.28816C18.6327 6.263 18.5677 6.25003 18.5021 6.25ZM9.49957 5.5H14.4996V6.25H9.49957V5.5ZM15.7636 18.0555C15.7499 18.1778 15.6917 18.2907 15.6 18.3727C15.5083 18.4547 15.3896 18.5 15.2666 18.5H8.73257C8.60956 18.5 8.49085 18.4547 8.39916 18.3727C8.30747 18.2907 8.24922 18.1778 8.23557 18.0555L7.03057 7.25H16.9686L15.7636 18.0555Z"
+                      fill="black" fill-opacity="0.6" />
+                    <path id="Vector_2"
+                      d="M13.5 16.8273C13.6326 16.8273 13.7598 16.7746 13.8536 16.6809C13.9473 16.5871 14 16.4599 14 16.3273V8.98633C14 8.85372 13.9473 8.72654 13.8536 8.63277C13.7598 8.53901 13.6326 8.48633 13.5 8.48633C13.3674 8.48633 13.2402 8.53901 13.1464 8.63277C13.0527 8.72654 13 8.85372 13 8.98633V16.3273C13 16.4599 13.0527 16.5871 13.1464 16.6809C13.2402 16.7746 13.3674 16.8273 13.5 16.8273ZM10.75 16.8273C10.8826 16.8273 11.0098 16.7746 11.1036 16.6809C11.1973 16.5871 11.25 16.4599 11.25 16.3273V8.98633C11.25 8.85372 11.1973 8.72654 11.1036 8.63277C11.0098 8.53901 10.8826 8.48633 10.75 8.48633C10.6174 8.48633 10.4902 8.53901 10.3964 8.63277C10.3027 8.72654 10.25 8.85372 10.25 8.98633V16.3273C10.25 16.4599 10.3027 16.5871 10.3964 16.6809C10.4902 16.7746 10.6174 16.8273 10.75 16.8273Z"
+                      fill="black" fill-opacity="0.6" />
+                  </g>
+                </g>
+                <defs>
+                  <clipPath id="clip0_446_5972">
+                    <rect width="16" height="16" fill="white" transform="translate(4 4)" />
+                  </clipPath>
+                </defs>
+              </svg>
+              删除</div>
+          </div>
+        </div>
+
+        <!-- 课程标题和保存状态 -->
+        <div class="header-center">
+          <div class="course-title-container">
+            <input v-if="editingTitle" v-model="courseTitle" class="course-title-input" @blur="editingTitle = false"
+              @keyup.enter="editingTitle = false" @input="isSaved = false" ref="titleInput" />
+            <span v-else class="course-title" @click="startEditingTitle">{{ courseTitle || '未命名课程' }}</span>
+          </div>
+          <div class="save-status">
+            <span v-if="lastSaveTime" class="last-save-time">上次保存时间:{{ lastSaveTime }}</span>
+            <span v-else class="status-unsaved">未保存</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="header-right">
+        <button class="action-btn preview-btn" @click="handlePreview">
+          <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g id="Component 1">
+              <path id="Vector"
+                d="M10 8C10 8.53043 9.78929 9.03914 9.41421 9.41421C9.03914 9.78929 8.53043 10 8 10C7.46957 10 6.96086 9.78929 6.58579 9.41421C6.21071 9.03914 6 8.53043 6 8C6 7.46957 6.21071 6.96086 6.58579 6.58579C6.96086 6.21071 7.46957 6 8 6C8.53043 6 9.03914 6.21071 9.41421 6.58579C9.78929 6.96086 10 7.46957 10 8Z"
+                stroke="#374151" stroke-width="1.33333" />
+              <path id="Vector_2"
+                d="M1.63867 8.00065C2.48801 5.29598 5.01534 3.33398 8.00001 3.33398C10.9853 3.33398 13.512 5.29598 14.3613 8.00065C13.512 10.7053 10.9853 12.6673 8.00001 12.6673C5.01534 12.6673 2.48801 10.7053 1.63867 8.00065Z"
+                stroke="#374151" stroke-width="1.33333" />
+            </g>
+          </svg>
+
+          预览
+        </button>
+        <button class="action-btn preview-btn" @click="handleSave">
+          <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g id="Component 1">
+              <path id="Vector"
+                d="M12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V3.33333C2 2.97971 2.14048 2.64057 2.39052 2.39052C2.64057 2.14048 2.97971 2 3.33333 2H10.6667L14 5.33333V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14Z"
+                stroke="#374151" stroke-width="1.33333" />
+              <path id="Vector_2" d="M11.3332 13.9993V8.66602H4.6665V13.9993" stroke="#374151" stroke-width="1.33333" />
+              <path id="Vector_3" d="M4.6665 2V5.33333H9.99984" stroke="#374151" stroke-width="1.33333" />
+            </g>
+          </svg>
+          保存
+        </button>
+        <button class="action-btn publish-btn" @click="handlePublish">
+          <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g id="Component 1">
+              <path id="Vector"
+                d="M8.00016 14.6673C11.6821 14.6673 14.6668 11.6825 14.6668 8.00065C14.6668 4.31875 11.6821 1.33398 8.00016 1.33398C4.31826 1.33398 1.3335 4.31875 1.3335 8.00065C1.3335 11.6825 4.31826 14.6673 8.00016 14.6673Z"
+                stroke="white" stroke-width="1.33333" />
+              <path id="Vector_2" d="M8 4V8L10.6667 9.33333" stroke="white" stroke-width="1.33333" />
+            </g>
+          </svg>
+          发布
+        </button>
+      </div>
+    </div>
+    <!-- <EditorHeader class="layout-header" /> -->
     <div class="layout-content">
     <div class="layout-content">
       <CollapsibleToolbar class="layout-sidebar" @toggle="handleToolbarToggle" />
       <CollapsibleToolbar class="layout-sidebar" @toggle="handleToolbarToggle" />
       <div class="layout-content-center">
       <div class="layout-content-center">
         <CanvasTool class="center-top" />
         <CanvasTool class="center-top" />
-        <Canvas class="center-body" :style="{ height: `calc(100% - ${remarkHeight + 40}px  - 120px)` }" :courseid="props.courseid"/>
+        <Canvas class="center-body" :style="{ height: `calc(100% - ${remarkHeight + 40}px  - 120px)` }"
+          :courseid="props.courseid" @course-loaded="handleCourseLoaded" />
         <!-- <Remark
         <!-- <Remark
           class="center-bottom" 
           class="center-bottom" 
           v-model:height="remarkHeight" 
           v-model:height="remarkHeight" 
@@ -14,7 +155,7 @@
         /> -->
         /> -->
         <Thumbnails class="layout-content-left" />
         <Thumbnails class="layout-content-left" />
       </div>
       </div>
-      <Toolbar class="layout-content-right" v-show="false"/>
+      <Toolbar class="layout-content-right" v-show="false" />
     </div>
     </div>
   </div>
   </div>
 
 
@@ -23,39 +164,23 @@
   <NotesPanel v-if="showNotesPanel" />
   <NotesPanel v-if="showNotesPanel" />
   <MarkupPanel v-if="showMarkupPanel" />
   <MarkupPanel v-if="showMarkupPanel" />
 
 
-  <Modal
-    :visible="!!dialogForExport" 
-    :width="680"
-    @closed="closeExportDialog()"
-  >
+  <Modal :visible="!!dialogForExport" :width="680" @closed="closeExportDialog()">
     <ExportDialog />
     <ExportDialog />
   </Modal>
   </Modal>
 
 
-  <Modal
-    :visible="showAIPPTDialog" 
-    :width="720"
-    :closeOnClickMask="false"
-    :closeOnEsc="false"
-    closeButton
-    @closed="closeAIPPTDialog()"
-  >
+  <Modal :visible="showAIPPTDialog" :width="720" :closeOnClickMask="false" :closeOnEsc="false" closeButton
+    @closed="closeAIPPTDialog()">
     <AIPPTDialog />
     <AIPPTDialog />
   </Modal>
   </Modal>
 
 
-  <Modal
-    class="createCourseDialog"
-    :visible="showCreateCourseDialog" 
-    :closeOnClickMask="false"
-    :closeOnEsc="false"
-    :closeButton="false"
-    @closed="closeCreateCourseDialog()"
-  >
+  <Modal class="createCourseDialog" :visible="showCreateCourseDialog" :closeOnClickMask="false" :closeOnEsc="false"
+    :closeButton="false" @closed="closeCreateCourseDialog()">
     <CreateCourseDialog @close="closeCreateCourseDialog" @select="handleCreateCourseSelect" />
     <CreateCourseDialog @close="closeCreateCourseDialog" @select="handleCreateCourseSelect" />
   </Modal>
   </Modal>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { storeToRefs } from 'pinia'
 import { storeToRefs } from 'pinia'
 import { useMainStore } from '@/store'
 import { useMainStore } from '@/store'
 import useGlobalHotkey from '@/hooks/useGlobalHotkey'
 import useGlobalHotkey from '@/hooks/useGlobalHotkey'
@@ -77,6 +202,11 @@ import Modal from '@/components/Modal.vue'
 import CollapsibleToolbar from '@/components/CollapsibleToolbar/index.vue'
 import CollapsibleToolbar from '@/components/CollapsibleToolbar/index.vue'
 import CreateCourseDialog from '@/components/CreateCourseDialog.vue'
 import CreateCourseDialog from '@/components/CreateCourseDialog.vue'
 
 
+interface ParentWindowWithToolList extends Window {
+  // goBack?: () => void;
+}
+const parentWindow = window.parent as ParentWindowWithToolList
+
 interface Props {
 interface Props {
   courseid?: string | null
   courseid?: string | null
 }
 }
@@ -93,11 +223,75 @@ const closeAIPPTDialog = () => mainStore.setAIPPTDialogState(false)
 const remarkHeight = ref(0)
 const remarkHeight = ref(0)
 const sidebarCollapsed = ref(false)
 const sidebarCollapsed = ref(false)
 const showCreateCourseDialog = ref(false)
 const showCreateCourseDialog = ref(false)
+const isDropdownOpen = ref(false)
+const courseTitle = ref('新建课程')
+
+// 课程标题相关
+const editingTitle = ref(false)
+const titleInput = ref<HTMLInputElement | null>(null)
+
+// 保存状态相关
+const isSaved = ref(true)
+const lastSaveTime = ref('')
+
+const toggleDropdown = () => {
+  isDropdownOpen.value = !isDropdownOpen.value
+}
+
+const startEditingTitle = () => {
+  editingTitle.value = true
+  // 在下一个渲染周期聚焦输入框
+  setTimeout(() => {
+    titleInput.value?.focus()
+  }, 0)
+}
+
+const handleBackToList = () => {
+  isDropdownOpen.value = false
+  // 导航回课程列表
+  console.log('导航回课程列表')
+}
+
+const handleSettings = () => {
+  isDropdownOpen.value = false
+  // 打开设置面板
+  console.log('打开设置')
+}
+
+const handleSaveAsCopy = () => {
+  isDropdownOpen.value = false
+  // 另存为副本
+  console.log('另存为副本')
+}
+
+const handleDelete = () => {
+  isDropdownOpen.value = false
+  // 删除课程
+  console.log('删除课程')
+}
 
 
 const handleToolbarToggle = (collapsed: boolean) => {
 const handleToolbarToggle = (collapsed: boolean) => {
   sidebarCollapsed.value = collapsed
   sidebarCollapsed.value = collapsed
 }
 }
 
 
+const handleCourseLoaded = (data: any) => {
+  console.log('课程数据已加载:', data)
+  courseTitle.value = data.title || '新建课程'
+}
+
+const handlePreview = () => {
+  console.log('预览课程')
+}
+
+const handleSave = () => {
+  console.log('保存课程')
+  lastSaveTime.value = new Date().toLocaleString()
+}
+
+const handlePublish = () => {
+  console.log('发布课程')
+}
+
 const closeCreateCourseDialog = () => {
 const closeCreateCourseDialog = () => {
   showCreateCourseDialog.value = false
   showCreateCourseDialog.value = false
 }
 }
@@ -118,8 +312,22 @@ onMounted(() => {
   if (!props.courseid) {
   if (!props.courseid) {
     showCreateCourseDialog.value = true
     showCreateCourseDialog.value = true
   }
   }
+  // 添加点击外部关闭下拉菜单的事件监听
+  document.addEventListener('click', handleClickOutside)
+})
+
+onUnmounted(() => {
+  // 移除事件监听
+  document.removeEventListener('click', handleClickOutside)
 })
 })
 
 
+const handleClickOutside = (event: MouseEvent) => {
+  const dropdownMenu = document.querySelector('.dropdown-menu')
+  if (dropdownMenu && !dropdownMenu.contains(event.target as Node)) {
+    isDropdownOpen.value = false
+  }
+}
+
 useGlobalHotkey()
 useGlobalHotkey()
 usePasteEvent()
 usePasteEvent()
 </script>
 </script>
@@ -127,20 +335,30 @@ usePasteEvent()
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .pptist-editor {
 .pptist-editor {
   height: 100%;
   height: 100%;
+  background: #eff0f2
 }
 }
+
 .layout-header {
 .layout-header {
   height: 40px;
   height: 40px;
 }
 }
+
 .layout-content {
 .layout-content {
-  height: calc(100% - 40px);
+  height: calc(100% - 70px);
+  width: calc(100% - 20px);
+  margin: 10px;
+  border-radius: 10px;
   display: flex;
   display: flex;
+  overflow: hidden;
 }
 }
+
 .layout-sidebar {
 .layout-sidebar {
   // width: 200px;
   // width: 200px;
   width: auto;
   width: auto;
   height: 100%;
   height: 100%;
   flex-shrink: 0;
   flex-shrink: 0;
   transition: width 0.3s ease;
   transition: width 0.3s ease;
+  margin-right: 10px;
+  border-radius: 10px;
 }
 }
 
 
 .layout-sidebar.collapsed {
 .layout-sidebar.collapsed {
@@ -158,6 +376,7 @@ usePasteEvent()
   transition: width 0.3s ease;
   transition: width 0.3s ease;
   max-width: 100%;
   max-width: 100%;
   overflow: hidden;
   overflow: hidden;
+  border-radius: 10px;
 }
 }
 
 
 .layout-content-center .center-top {
 .layout-content-center .center-top {
@@ -169,12 +388,244 @@ usePasteEvent()
   height: 100%;
   height: 100%;
 }
 }
 
 
+
+.ppt_header {
+  height: 50px;
+  width: 100%;
+  background: #fff;
+  display: flex;
+  align-items: center;
+  padding: 0 20px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  justify-content: space-between;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.header-center {
+  flex: 1;
+  margin: 0;
+  min-width: 300px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.course-title-container {
+  margin-bottom: 4px;
+}
+
+.course-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  cursor: pointer;
+  padding: 4px 8px;
+  border-radius: 4px;
+  transition: background-color 0.2s;
+  max-width: 300px;
+  overflow: hidden;
+  display: block;
+  text-overflow: ellipsis;
+
+  &:hover {
+    background-color: #f5f5f5;
+  }
+}
+
+.course-title-input {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  border: 2px solid #3681FC;
+  border-radius: 4px;
+  padding: 4px 8px;
+  outline: none;
+  width: 200px;
+  transition: border-color 0.2s;
+
+  &:focus {
+    border-color: #1e60d0;
+  }
+}
+
+.save-status {
+  font-size: 12px;
+  color: #666;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.status-saved {
+  color: #10b981;
+  font-weight: 500;
+}
+
+.status-unsaved {
+  color: #6B7280;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+
+  &::before {
+    content: '';
+    display: inline-block;
+    width: 4px;
+    height: 4px;
+    background-color: #10B981;
+    border-radius: 50%;
+    margin-right: 4px;
+  }
+}
+
+.last-save-time {
+  color: #6B7280;
+}
+
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.action-btn {
+  padding: 6px 16px;
+  border: none;
+  border-radius: 6px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+
+  svg {
+    width: 14px;
+    height: 14px;
+    flex-shrink: 0;
+  }
+
+  &.preview-btn {
+    background: #FFFFFF;
+    border: 1px solid #E5E7EB;
+    color: #374151;
+
+    &:hover {
+      background: #e5e7eb;
+    }
+  }
+
+  &.save-btn {
+    background: #3681FC;
+    color: white;
+
+    &:hover {
+      background: #2563eb;
+    }
+  }
+
+  &.publish-btn {
+    background: #FF9300;
+    color: white;
+
+    &:hover {
+      background: #e68a00;
+    }
+  }
+}
+
+.dropdown-menu {
+  position: relative;
+}
+
+.dropdown-toggle {
+  background: none;
+  border: none;
+  cursor: pointer;
+  padding: 6px 8px;
+  border-radius: 6px;
+  color: #333;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 2px;
+  transition: background-color 0.2s;
+
+  &:hover {
+    background-color: #f0f4ff;
+  }
+
+  svg:first-child {
+    width: 22px;
+    height: 22px;
+  }
+
+  svg:last-child {
+    width: 14px;
+    height: 14px;
+    margin-left: 2px;
+  }
+}
+
+.dropdown-content {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  margin-top: 6px;
+  background: white;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  min-width: 135px;
+  z-index: 1000;
+  overflow: hidden;
+  padding: 4px 0;
+}
+
+.dropdown-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 14px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+  font-size: 14px;
+  color: #333;
+
+  svg {
+    width: 18px;
+    height: 18px;
+    flex-shrink: 0;
+  }
+
+  &:hover {
+    background-color: #f5f7fa;
+  }
+
+  &.danger {
+    color: #ef4444;
+
+    svg {
+      color: #ef4444;
+    }
+
+    &:hover {
+      background-color: #fef2f2;
+    }
+  }
+}
 </style>
 </style>
 <style lang="scss">
 <style lang="scss">
-.createCourseDialog{
+.createCourseDialog {
   background: #78797b;
   background: #78797b;
 
 
-  .modal-content{
+  .modal-content {
     width: auto !important;
     width: auto !important;
     min-width: 800px;
     min-width: 800px;
     border-radius: 10px;
     border-radius: 10px;

+ 1 - 1
src/views/Student/index.vue

@@ -2385,7 +2385,7 @@ const checkPPTFile = async (jsonObj: any) => {
     pptJsonFileid.value = data1[0].fileid
     pptJsonFileid.value = data1[0].fileid
   }
   }
   else {
   else {
-    const pptJsonFile = new File([jsonObj], courseDetail.value.courseName + '.json', { type: 'application/json' })
+    const pptJsonFile = new File([jsonObj], courseDetail.value.title + '.json', { type: 'application/json' })
     uploadFile2(pptJsonFile, props.courseid as string)
     uploadFile2(pptJsonFile, props.courseid as string)
   }
   }
 }
 }