|
@@ -131,21 +131,94 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="submenu-content">
|
|
<div class="submenu-content">
|
|
|
- <FileInput accept=".pptx"
|
|
|
|
|
- @change="handleFileUpload">
|
|
|
|
|
- <div class="upload-dropzone">
|
|
|
|
|
- <div class="upload-dropzone-icon">
|
|
|
|
|
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
|
|
- <path d="M12 15V4"></path>
|
|
|
|
|
- <path d="M7 9l5-5 5 5"></path>
|
|
|
|
|
- <path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2"></path>
|
|
|
|
|
- </svg>
|
|
|
|
|
|
|
+ <template v-if="readingFile">
|
|
|
|
|
+ <div class="reading-file">
|
|
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
|
|
+ <div class="reading-text">{{ lang.ssReadingFile }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else-if="!showFileConfirmModal && !exportingDialog">
|
|
|
|
|
+ <FileInput accept=".pptx"
|
|
|
|
|
+ @change="handleFileUpload">
|
|
|
|
|
+ <div class="upload-dropzone">
|
|
|
|
|
+ <div class="upload-dropzone-icon">
|
|
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
|
|
|
+ <path d="M12 15V4"></path>
|
|
|
|
|
+ <path d="M7 9l5-5 5 5"></path>
|
|
|
|
|
+ <path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2"></path>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="upload-dropzone-title">{{ lang.ssDragAndDrop }}</div>
|
|
|
|
|
+ <div class="upload-dropzone-subtitle">{{ lang.ssSupportPptx }}</div>
|
|
|
|
|
+ <!-- <div class="upload-dropzone-footnote">同类型文件支持批量导入,跨类型文件请分开处理</div> -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </FileInput>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-else-if="!exportingDialog">
|
|
|
|
|
+ <div class="file-confirm-inline">
|
|
|
|
|
+ <div class="file-info">
|
|
|
|
|
+ <div class="file-title">{{ lang.ssFileDetected }}{{ currentFileName }}</div>
|
|
|
|
|
+ <div class="file-subtitle">{{ currentFileName }}({{ lang.ssTotalPages.replace('{count}', pageCount.toString()) }})</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="import-options">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="import-option"
|
|
|
|
|
+ :class="{ active: selectedImportOption === 'page' }"
|
|
|
|
|
+ @click="selectedImportOption = 'page'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="option-icon">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="option-text">
|
|
|
|
|
+ <div class="option-title">{{ lang.ssImportAsSlide }}</div>
|
|
|
|
|
+ <div class="option-desc">{{ lang.ssImportAsSlideDesc }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-show="false"
|
|
|
|
|
+ class="import-option"
|
|
|
|
|
+ :class="{ active: selectedImportOption === 'library' }"
|
|
|
|
|
+ @click="selectedImportOption = 'library'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="option-icon">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="option-text">
|
|
|
|
|
+ <div class="option-title">{{ lang.ssImportAndSave }}</div>
|
|
|
|
|
+ <div class="option-desc">{{ lang.ssImportAndSaveDesc }}</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="upload-dropzone-title">拖拽文件至此,或点击选择</div>
|
|
|
|
|
- <div class="upload-dropzone-subtitle">支持.pptx</div>
|
|
|
|
|
- <!-- <div class="upload-dropzone-footnote">同类型文件支持批量导入,跨类型文件请分开处理</div> -->
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="modal-buttons">
|
|
|
|
|
+ <button class="cancel-btn" @click="cancelFileUpload">{{ lang.ssCancel }}</button>
|
|
|
|
|
+ <button class="confirm-btn" @click="confirmFileUpload">{{ lang.ssConfirm }}</button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </FileInput>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="exportingDialog">
|
|
|
|
|
+ <div class="progress-inline">
|
|
|
|
|
+ <div class="progress-header">
|
|
|
|
|
+ <!-- <span class="file-name">{{ currentFileName }}</span>
|
|
|
|
|
+ <span class="progress-percent">{{ importProgress }}%</span> -->
|
|
|
|
|
+ <div class="upload-task-main">
|
|
|
|
|
+ <div class="upload-task-name">{{ currentFileName }}</div>
|
|
|
|
|
+ <div class="upload-task-meta">课件文件 · {{ formatFileSize(currentFileSize) }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="upload-task-side">
|
|
|
|
|
+ <div class="upload-task-percent">{{importProgress}}%</div>
|
|
|
|
|
+ <button type="button" class="upload-task-action" :class="{ 'upload-task-close': !exporting}" @click="handleParsingClose">{{ exporting ? '取消' :'×'}}</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div class="progress-fill" :style="{ width: importProgress + '%' }"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="progress-loading" v-if="exporting">上传中...</div>
|
|
|
|
|
+ <!-- <button class="close-btn" @click="handleParsingClose">{{ lang.ssClose }}</button> -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="submenu" :class="{ visible: activeSubmenu === 'page' }">
|
|
<div class="submenu" :class="{ visible: activeSubmenu === 'page' }">
|
|
@@ -571,26 +644,9 @@
|
|
|
<SpeakingPanel />
|
|
<SpeakingPanel />
|
|
|
</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>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -1042,24 +1098,96 @@ const getTypeClass = (type?: number) => {
|
|
|
|
|
|
|
|
import useImport from '@/hooks/useImport'
|
|
import useImport from '@/hooks/useImport'
|
|
|
import message from '@/utils/message'
|
|
import message from '@/utils/message'
|
|
|
-const { importPPTXFile, exporting, getFile } = useImport()
|
|
|
|
|
|
|
+const { importPPTXFile, exporting, getFile, getPPTInfo } = useImport()
|
|
|
const currentFileName = ref('')
|
|
const currentFileName = ref('')
|
|
|
|
|
+const currentFileSize = ref(0)
|
|
|
const parsingStatus = ref<'parsing' | 'success'>('parsing')
|
|
const parsingStatus = ref<'parsing' | 'success'>('parsing')
|
|
|
const parsingAbortController = ref<AbortController | null>(null)
|
|
const parsingAbortController = ref<AbortController | null>(null)
|
|
|
|
|
+const showFileConfirmModal = ref(false)
|
|
|
|
|
+const pendingFile = ref<FileList | null>(null)
|
|
|
|
|
+const selectedImportOption = ref<'page' | 'library'>('page')
|
|
|
|
|
+const pageCount = ref(1)
|
|
|
|
|
+const readingFile = ref(false)
|
|
|
|
|
+const importProgress = ref(0)
|
|
|
|
|
+const progressInterval = ref<NodeJS.Timeout | null>(null)
|
|
|
|
|
+const exportingDialog = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// 格式化文件大小
|
|
|
|
|
+const formatFileSize = (bytes: number): string => {
|
|
|
|
|
+ if (bytes === 0) return '0 B'
|
|
|
|
|
+ const k = 1024
|
|
|
|
|
+ const sizes = ['B', 'KB', 'MB', 'GB']
|
|
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const handleFileUpload = async (files: FileList) => {
|
|
const handleFileUpload = async (files: FileList) => {
|
|
|
if (!files || files.length === 0) return
|
|
if (!files || files.length === 0) return
|
|
|
|
|
|
|
|
const file = files[0]
|
|
const file = files[0]
|
|
|
currentFileName.value = file.name
|
|
currentFileName.value = file.name
|
|
|
|
|
+ currentFileSize.value = file.size
|
|
|
|
|
+ pendingFile.value = files
|
|
|
|
|
+ selectedImportOption.value = 'page'
|
|
|
|
|
+ readingFile.value = true
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 读取 PPT 信息获取页码
|
|
|
|
|
+ const info = await getPPTInfo(file)
|
|
|
|
|
+ pageCount.value = info.pageCount
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error('获取 PPT 信息失败:', error)
|
|
|
|
|
+ pageCount.value = 1 // 失败时默认显示 1 页
|
|
|
|
|
+ }
|
|
|
|
|
+ finally {
|
|
|
|
|
+ readingFile.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ showFileConfirmModal.value = true
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
|
|
+const confirmFileUpload = async () => {
|
|
|
|
|
+ if (!pendingFile.value) return
|
|
|
|
|
+
|
|
|
|
|
+ showFileConfirmModal.value = false
|
|
|
|
|
+ importProgress.value = 0
|
|
|
|
|
+ exportingDialog.value = true
|
|
|
|
|
+ const startTimer = () => {
|
|
|
|
|
+ // 启动虚拟进度条(30秒从0-99%)
|
|
|
|
|
+ if (progressInterval.value) clearInterval(progressInterval.value)
|
|
|
|
|
+ const startTime = Date.now()
|
|
|
|
|
+ const duration = 30000 // 30秒
|
|
|
|
|
+ progressInterval.value = setInterval(() => {
|
|
|
|
|
+ console.log('progressInterval.value', progressInterval.value)
|
|
|
|
|
+ const elapsed = Date.now() - startTime
|
|
|
|
|
+ const progress = Math.min(99, Math.floor((elapsed / duration) * 99))
|
|
|
|
|
+ importProgress.value = progress
|
|
|
|
|
+ if (progress >= 99) {
|
|
|
|
|
+ importProgress.value = 99
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!exporting.value) {
|
|
|
|
|
+ if (progressInterval.value) {
|
|
|
|
|
+ clearInterval(progressInterval.value)
|
|
|
|
|
+ importProgress.value = 100
|
|
|
|
|
+ progressInterval.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 100)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const confirmOnclose = () => {
|
|
|
|
|
+ handleParsingClose()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
// 创建AbortController用于取消操作
|
|
// 创建AbortController用于取消操作
|
|
|
parsingAbortController.value = new AbortController()
|
|
parsingAbortController.value = new AbortController()
|
|
|
const signal = parsingAbortController.value.signal
|
|
const signal = parsingAbortController.value.signal
|
|
|
|
|
|
|
|
// 调用importPPTXFile并传入signal
|
|
// 调用importPPTXFile并传入signal
|
|
|
- await importPPTXFile(files, { signal })
|
|
|
|
|
|
|
+ await importPPTXFile(pendingFile.value, { signal, startTimer, confirmOnclose })
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
@@ -1070,9 +1198,26 @@ const handleFileUpload = async (files: FileList) => {
|
|
|
message.error(lang.ssFileParseFailedRetry)
|
|
message.error(lang.ssFileParseFailedRetry)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ finally {
|
|
|
|
|
+ // if (progressInterval.value) {
|
|
|
|
|
+ // clearInterval(progressInterval.value)
|
|
|
|
|
+ // progressInterval.value = null
|
|
|
|
|
+ // importProgress.value = 100
|
|
|
|
|
+ // }
|
|
|
|
|
+ pendingFile.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const cancelFileUpload = () => {
|
|
|
|
|
+ showFileConfirmModal.value = false
|
|
|
|
|
+ pendingFile.value = null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleParsingClose = () => {
|
|
const handleParsingClose = () => {
|
|
|
|
|
+ if (progressInterval.value) {
|
|
|
|
|
+ clearInterval(progressInterval.value)
|
|
|
|
|
+ progressInterval.value = null
|
|
|
|
|
+ }
|
|
|
if (exporting.value && parsingAbortController.value) {
|
|
if (exporting.value && parsingAbortController.value) {
|
|
|
parsingAbortController.value.abort()
|
|
parsingAbortController.value.abort()
|
|
|
exporting.value = false
|
|
exporting.value = false
|
|
@@ -1082,6 +1227,8 @@ const handleParsingClose = () => {
|
|
|
else if (!exporting.value) {
|
|
else if (!exporting.value) {
|
|
|
emit('close')
|
|
emit('close')
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ exportingDialog.value = false
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -1549,112 +1696,125 @@ const handleParsingClose = () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-.parsing-modal {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- top: 0;
|
|
|
|
|
- left: 0;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+.progress-inline {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- z-index: 1000;
|
|
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 14px 14px 12px;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #f0ebe3;
|
|
|
|
|
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.04);
|
|
|
|
|
+ width: calc(100% - 30px);
|
|
|
|
|
+ margin: 15px auto;
|
|
|
|
|
|
|
|
- .parsing-content {
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- padding: 24px;
|
|
|
|
|
- width: 400px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
-
|
|
|
|
|
- .loading-spinner {
|
|
|
|
|
- width: 48px;
|
|
|
|
|
- height: 48px;
|
|
|
|
|
- border: 4px solid #f0f0f0;
|
|
|
|
|
- border-top: 4px solid #FF9300;
|
|
|
|
|
- border-radius: 50%;
|
|
|
|
|
- margin: 0 auto 20px;
|
|
|
|
|
- animation: spin 1s linear infinite;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .progress-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ // .file-name {
|
|
|
|
|
+ // font-size: 14px;
|
|
|
|
|
+ // font-weight: 600;
|
|
|
|
|
+ // color: #333;
|
|
|
|
|
+ // }
|
|
|
|
|
|
|
|
- .success-icon {
|
|
|
|
|
- width: 48px;
|
|
|
|
|
- height: 48px;
|
|
|
|
|
- margin: 0 auto 20px;
|
|
|
|
|
- background: #FFFAF0;
|
|
|
|
|
- border-radius: 5px;
|
|
|
|
|
|
|
+ // .progress-percent {
|
|
|
|
|
+ // font-size: 14px;
|
|
|
|
|
+ // font-weight: 600;
|
|
|
|
|
+ // color: #FF9300;
|
|
|
|
|
+ // }
|
|
|
|
|
+
|
|
|
|
|
+ .upload-task-main{
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-task-name{
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #111827;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-task-meta{
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #8b7356;
|
|
|
|
|
+ }
|
|
|
|
|
+ .upload-task-side{
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
|
|
- color: white;
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- h3 {
|
|
|
|
|
- font-size: 20px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #333;
|
|
|
|
|
- margin: 0 0 12px;
|
|
|
|
|
|
|
+ .upload-task-percent{
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #c76a0c;
|
|
|
|
|
+ min-width: 40px;
|
|
|
|
|
+ text-align: right;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- p {
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #666;
|
|
|
|
|
- margin: 0 0 24px;
|
|
|
|
|
|
|
+ .upload-task-action {
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ color: #9a8a77;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .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;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .upload-task-close{
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ color: #8b7356;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ line-height: 1;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- @keyframes spin {
|
|
|
|
|
- 0% {
|
|
|
|
|
- transform: rotate(0deg);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .progress-bar {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 4px;
|
|
|
|
|
+ background: #f0f0f0;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
|
|
|
- 100% {
|
|
|
|
|
- transform: rotate(360deg);
|
|
|
|
|
|
|
+ .progress-fill {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: linear-gradient(90deg, #f9b24e 0%, #f07815 100%);
|
|
|
|
|
+ transition: width 0.24s ease;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ transition: width 0.1s ease;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .progress-loading{
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #111827;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.close-btn {
|
|
.close-btn {
|
|
|
- width: 32px;
|
|
|
|
|
- height: 32px;
|
|
|
|
|
|
|
+ background: #FF9300;
|
|
|
|
|
+ color: white;
|
|
|
border: none;
|
|
border: none;
|
|
|
- background: none;
|
|
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 10px 20px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- color: #999;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- transition: all 0.2s;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
|
|
|
&:hover {
|
|
&:hover {
|
|
|
- background: #f0f0f0;
|
|
|
|
|
- color: #666;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- svg {
|
|
|
|
|
- width: 16px;
|
|
|
|
|
- height: 16px;
|
|
|
|
|
|
|
+ background: #e68a00;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1790,4 +1950,159 @@ const handleParsingClose = () => {
|
|
|
color: #9a8a77;
|
|
color: #9a8a77;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.reading-file {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 40px 20px;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+
|
|
|
|
|
+ .loading-spinner {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ border: 4px solid #f0f0f0;
|
|
|
|
|
+ border-top: 4px solid #FF9300;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .reading-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes spin {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ transform: rotate(0deg);
|
|
|
|
|
+ }
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ transform: rotate(360deg);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.file-confirm-inline {
|
|
|
|
|
+ width: calc(100% - 30px);
|
|
|
|
|
+ margin: 15px auto;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ margin-top: 18px;
|
|
|
|
|
+ padding: 18px;
|
|
|
|
|
+ border-radius: 18px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #f3e4cf;
|
|
|
|
|
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06);
|
|
|
|
|
+
|
|
|
|
|
+ .file-info {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ .file-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #111827;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-subtitle {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #7c6d5d;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .import-options {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ .import-option {
|
|
|
|
|
+ border: 1px solid #efe6d8;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.22s ease;
|
|
|
|
|
+ background: #fffdfa;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ border-color: rgba(247, 139, 34, 0.4);
|
|
|
|
|
+ background: #fffdfa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ border-color: #ff9300;
|
|
|
|
|
+ background: #fff5e5;
|
|
|
|
|
+
|
|
|
|
|
+ .option-icon {
|
|
|
|
|
+ border-color: #f78b22;
|
|
|
|
|
+
|
|
|
|
|
+ &::after {
|
|
|
|
|
+ content: '';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 3px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #f78b22;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .option-icon {
|
|
|
|
|
+ width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ border: 1.5px solid #d6c0a1;
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .option-text {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+
|
|
|
|
|
+ .option-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #111827;
|
|
|
|
|
+ margin-bottom: 3px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .option-desc {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .modal-buttons {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+
|
|
|
|
|
+ .cancel-btn, .confirm-btn {
|
|
|
|
|
+ min-width: 84px;
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ padding: 0 14px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .cancel-btn {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .confirm-btn {
|
|
|
|
|
+ background: linear-gradient(180deg, #f89a34 0%, #f07815 100%);
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ box-shadow: 0 10px 20px rgba(240, 120, 21, 0.18);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|