| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- <template>
- <div class="create-course-dialog">
- <div class="dialog-header">
- <button class="close-btn" @click="handleClose">
- <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 class="dialog-content">
- <h2>{{ lang.ssCreateCourse }}</h2>
- <p class="subtitle">{{ lang.ssCreateCourseSubtitle }}</p>
- <div class="options-grid">
- <div class="option-card disabled">
- <div class="option-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>{{ lang.ssCreateFromAI }}</h3>
- <p>{{ lang.ssAIGenerateContent }}</p>
- <div class="coming-soon">{{ lang.ssComingSoon }}</div>
- </div>
- <FileInput accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"
- @change="handleFileUpload">
- <div class="option-card">
- <div class="option-icon">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
- <polyline points="17 8 12 3 7 8" />
- <line x1="12" y1="3" x2="12" y2="15" />
- </svg>
- </div>
- <h3>{{ lang.ssUploadLocalFile }}</h3>
- <p>{{ lang.ssUploadPPTFile }}</p>
- </div>
- </FileInput>
- <div class="option-card disabled">
- <div class="option-icon">
- <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
- <g id="Component 1">
- <path id="Vector"
- d="M3.5 10.5007L14 2.33398L24.5 10.5007V23.334C24.5 23.9528 24.2542 24.5463 23.8166 24.9839C23.379 25.4215 22.7855 25.6673 22.1667 25.6673H5.83333C5.21449 25.6673 4.621 25.4215 4.18342 24.9839C3.74583 24.5463 3.5 23.9528 3.5 23.334V10.5007Z"
- stroke="currentColor" stroke-width="2.33333" />
- <path id="Vector_2" d="M10.5 25.6667V14H17.5V25.6667" stroke="currentColor" stroke-width="2.33333" />
- </g>
- </svg>
- </div>
- <h3>{{ lang.ssImportFromLibrary }}</h3>
- <p>{{ lang.ssSelectExistingContent }}</p>
- <div class="coming-soon">{{ lang.ssComingSoon }}</div>
- </div>
- <div class="option-card" @click="handleOptionClick('blank')">
- <div class="option-icon">
- <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
- <g id="Component 1">
- <path id="Vector"
- d="M16.3332 2.33398H6.99984C6.381 2.33398 5.78751 2.57982 5.34992 3.0174C4.91234 3.45499 4.6665 4.04848 4.6665 4.66732V23.334C4.6665 23.9528 4.91234 24.5463 5.34992 24.9839C5.78751 25.4215 6.381 25.6673 6.99984 25.6673H20.9998C21.6187 25.6673 22.2122 25.4215 22.6498 24.9839C23.0873 24.5463 23.3332 23.9528 23.3332 23.334V9.33398L16.3332 2.33398Z"
- stroke="currentColor" stroke-width="2.33333" />
- <path id="Vector_2" d="M16.3335 2.33398V9.33398H23.3335" stroke="currentColor" stroke-width="2.33333" />
- </g>
- </svg>
- </div>
- <h3>{{ lang.ssCreateBlank }}</h3>
- <p>{{ lang.ssStartFromScratch }}</p>
- </div>
- </div>
- </div>
- <!-- 解析状态弹窗 -->
- <div v-if="exporting" class="parsing-modal">
- <div class="parsing-content">
- <div class="loading-spinner" v-if="exporting"></div>
- <div class="success-icon" v-if="!exporting">
- <svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
- <g id="Component 1">
- <path id="Vector" d="M5.41675 14.084L9.75008 18.4173L20.5834 7.58398" stroke="#FF9300"
- stroke-width="2.16667" stroke-linecap="round" stroke-linejoin="round" />
- </g>
- </svg>
- </div>
- <h3>{{ exporting ? lang.ssParsing : lang.ssExportCompleted }}</h3>
- <p v-if="exporting">{{ lang.ssParsingFile }}{{ currentFileName }}</p>
- <p v-if="!exporting">{{ lang.ssParsingCompleted }}</p>
- <button class="close-btn2" @click="handleParsingClose">
- {{ exporting ? lang.ssClose : lang.ssComplete }}
- </button>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref } from 'vue'
- import useImport from '@/hooks/useImport'
- import FileInput from '@/components/FileInput.vue'
- import message from '@/utils/message'
- import { lang } from '@/main'
- const emit = defineEmits<{
- (e: 'close'): void
- (e: 'select', option: string): void
- }>()
- const { importPPTXFile, exporting } = useImport()
- const currentFileName = ref('')
- const parsingStatus = ref<'parsing' | 'success'>('parsing')
- const parsingAbortController = ref<AbortController | null>(null)
- const handleOptionClick = (option: string) => {
- emit('select', option)
- emit('close')
- }
- const handleClose = () => {
- interface ParentWindowWithToolList extends Window {
- goBack?: () => void;
- }
- const parentWindow = window.parent as ParentWindowWithToolList
- parentWindow?.goBack?.()
- }
- const handleFileUpload = async (files: FileList) => {
- if (!files || files.length === 0) return
- const file = files[0]
- currentFileName.value = file.name
- try {
- // 创建AbortController用于取消操作
- parsingAbortController.value = new AbortController()
- const signal = parsingAbortController.value.signal
- // 调用importPPTXFile并传入signal
- await importPPTXFile(files, { signal, onclose: () => emit('close') })
- }
- catch (error) {
- if (error instanceof DOMException && error.name === 'AbortError') {
- console.log('文件解析已取消')
- }
- else {
- console.error('文件解析失败:', error)
- // message.error('文件解析失败,请重试')
- message.error(lang.ssFileParseFailedRetry)
- }
- }
- }
- const handleParsingClose = () => {
- if (exporting.value && parsingAbortController.value) {
- parsingAbortController.value.abort()
- exporting.value = false
- parsingAbortController.value = null
- // message.info('解析已取消')
- }
- else if (!exporting.value) {
- emit('close')
- }
- }
- </script>
- <style lang="scss" scoped>
- .create-course-dialog {
- width: 100%;
- max-width: 800px;
- margin: 0 auto;
- .dialog-header {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- margin-bottom: 0;
- .close-btn {
- width: 32px;
- height: 32px;
- border: none;
- background: none;
- cursor: pointer;
- color: #999;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all 0.2s;
- &:hover {
- background: #f0f0f0;
- color: #666;
- }
- svg {
- width: 16px;
- height: 16px;
- }
- }
- }
- .dialog-content {
- h2 {
- font-size: 24px;
- font-weight: 600;
- color: #333;
- margin: 0 auto 20px;
- text-align: center;
- }
- .subtitle {
- text-align: center;
- color: #666;
- margin-bottom: 32px;
- font-size: 14px;
- }
- .options-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 20px;
- .option-card {
- background: #fafbfc;
- border: 1px solid #E5E7EB;
- border-radius: 12px;
- padding: 24px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s;
- position: relative;
- &:hover {
- border-color: #FF9300;
- background: #FFFAF0;
- .option-icon {
- color: #FF9300;
- }
- }
- &.active {
- background: #FFFAF0;
- border-color: #FF9300;
- }
- &.disabled {
- background: #f8f8f9;
- border-color: #eff0f3;
- cursor: not-allowed;
- h3 {
- color: #7c7f86;
- }
- p {
- color: #b5b9bf;
- }
- .option-icon {
- color: #a9aeb5;
- background: #fff;
- }
- }
- .option-icon {
- width: 48px;
- height: 48px;
- background: #fff;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 0 auto 16px;
- color: #6b7280;
- transition: all 0.3s;
- svg {
- width: 24px;
- height: 24px;
- }
- }
- h3 {
- font-size: 18px;
- font-weight: 600;
- color: #333;
- margin: 0 0 8px;
- }
- p {
- font-size: 14px;
- color: #999;
- margin: 0 0 16px;
- }
- .coming-soon {
- position: absolute;
- top: 12px;
- right: 12px;
- background: #c5c9d0;
- color: #fff;
- font-size: 14px;
- font-weight: 500;
- padding: 4px 8px;
- border-radius: 15px;
- text-transform: uppercase;
- }
- }
- }
- }
- .parsing-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- .parsing-content {
- background: white;
- border-radius: 12px;
- padding: 40px;
- text-align: center;
- max-width: 400px;
- width: 90%;
- .loading-spinner {
- width: 48px;
- height: 48px;
- border: 4px solid #f3f3f3;
- border-top: 4px solid #FF9300;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin: 0 auto 20px;
- }
- .success-icon {
- width: 48px;
- height: 48px;
- margin: 0 auto 20px;
- background: #FFFAF0;
- border-radius: 5px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-size: 24px;
- font-weight: bold;
- }
- h3 {
- font-size: 20px;
- font-weight: 600;
- color: #333;
- margin: 0 0 12px;
- }
- p {
- font-size: 14px;
- color: #666;
- margin: 0 0 24px;
- }
- .close-btn2 {
- background: #FF9300;
- color: white;
- border: none;
- border-radius: 8px;
- padding: 12px 24px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- width: 100%;
- transition: all 0.3s;
- &:hover {
- background: #e68a00;
- }
- }
- }
- @keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
- }
- .close-btn {
- width: 32px;
- height: 32px;
- border: none;
- background: none;
- cursor: pointer;
- color: #999;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all 0.2s;
- &:hover {
- background: #f0f0f0;
- color: #666;
- }
- svg {
- width: 16px;
- height: 16px;
- }
- }
- }
- }
- </style>
|