|
|
@@ -306,7 +306,18 @@
|
|
|
</div>
|
|
|
<span class="submenu-label">{{ lang.ssWebpageCenter }}</span>
|
|
|
</div> -->
|
|
|
- <div class="submenu-item">
|
|
|
+ <div class="submenu-item" @click="handleToolClick('uploadWebpage')">
|
|
|
+ <div class="submenu-icon">
|
|
|
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <circle cx="12" cy="12" r="10"></circle>
|
|
|
+ <path d="M2 12h20"></path>
|
|
|
+ <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>
|
|
|
+ <path d="M16 8l-4 4-4-4" stroke-width="1.5"></path>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <span class="submenu-label">{{ lang.ssUploadWebpageLink }}</span>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="submenu-item">
|
|
|
<div class="submenu-icon">
|
|
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
<g id="Component 1">
|
|
|
@@ -319,7 +330,7 @@
|
|
|
</svg>
|
|
|
</div>
|
|
|
<span class="submenu-label">{{ lang.ssUploadWebpage }}</span>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
<div class="submenu-item" @click="handleToolClick('createWebpage')">
|
|
|
<div class="submenu-icon">
|
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
@@ -349,6 +360,33 @@
|
|
|
</div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="submenu" :class="{ visible: activeSubmenu === 'uploadWebpage' }">
|
|
|
+ <div class="submenu-title">
|
|
|
+ <div class="title">{{ lang.ssUploadWebpageLink }}</div>
|
|
|
+ <div class="close-icon" @click="activeSubmenu = 'h5page'">
|
|
|
+ <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <g id="Component 3">
|
|
|
+ <g id="Component 1">
|
|
|
+ <path id="Vector" d="M16 18L12 14L16 10" stroke="#9CA3AF" stroke-width="1.33333" />
|
|
|
+ </g>
|
|
|
+ </g>
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="line_box">
|
|
|
+ <div class="webpage-link-container">
|
|
|
+ <h3 class="webpage-link-title">{{ lang.ssWebpageLink }}</h3>
|
|
|
+ <input type="text" class="webpage-link-input" :placeholder="lang.ssEnterCompleteUrl" v-model="webpageUrl"
|
|
|
+ @input="handleUrlInput" />
|
|
|
+ <button class="webpage-link-button"
|
|
|
+ :class="{ 'loading': isLoading, 'error': isValidUrl === false, 'disabled': isValidUrl === null || isLoading }"
|
|
|
+ :disabled="isValidUrl === null || isLoading || isValidUrl === false" @click="uploadWebpageLink">
|
|
|
+ {{ isLoading ? lang.ssUploading : isValidUrl === null ? lang.ssWaitingForInput : isValidUrl === false ?
|
|
|
+ lang.ssInvalidUrl : lang.ssStartUpload }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="submenu" :class="{ visible: activeSubmenu === 'multimedia' }">
|
|
|
<div class="submenu-title">
|
|
|
<div class="title">{{ lang.ssAddMultimedia }}</div>
|
|
|
@@ -482,6 +520,9 @@ const isCollapsed = ref(props.defaultCollapsed)
|
|
|
const activeSubmenu = ref<string | null>(null)
|
|
|
const contentList = ref<ContentItem[]>([])
|
|
|
const hoveredTool = ref<string | null>(null)
|
|
|
+const webpageUrl = ref('')
|
|
|
+const isLoading = ref(false)
|
|
|
+const isValidUrl = ref<boolean | null>(null) // null: 未输入, true: 有效, false: 无效
|
|
|
|
|
|
const slidesStore = useSlidesStore()
|
|
|
const { currentSlide } = storeToRefs(slidesStore)
|
|
|
@@ -489,6 +530,191 @@ const { currentSlide } = storeToRefs(slidesStore)
|
|
|
const { createFrameElement } = useCreateElement()
|
|
|
const { createSlide, createSlideByTemplate } = useSlideHandler()
|
|
|
|
|
|
+const handleUrlInput = () => {
|
|
|
+ const url = webpageUrl.value.trim()
|
|
|
+ if (!url) {
|
|
|
+ isValidUrl.value = null
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 改进的URL格式验证,支持查询参数
|
|
|
+ const urlRegex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*(\?[\w=&-]+)?\/?$/i
|
|
|
+ isValidUrl.value = urlRegex.test(url)
|
|
|
+ }
|
|
|
+ console.log('URL输入:', webpageUrl.value, '验证结果:', isValidUrl.value)
|
|
|
+}
|
|
|
+
|
|
|
+const uploadWebpageLink = async () => {
|
|
|
+ if (!webpageUrl.value || isValidUrl.value !== true) {
|
|
|
+ // 可以添加提示信息
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ isLoading.value = true
|
|
|
+
|
|
|
+ // 模拟上传过程
|
|
|
+ // isLoading.value = false
|
|
|
+ // 上传成功后创建iframe元素
|
|
|
+
|
|
|
+ const isValid = await new Promise((resolve) => {
|
|
|
+ // 创建隐藏iframe
|
|
|
+ const iframe = document.createElement('iframe')
|
|
|
+ iframe.style.display = 'none'
|
|
|
+ iframe.src = webpageUrl.value
|
|
|
+ let finished = false
|
|
|
+ const timeout = setTimeout(() => {
|
|
|
+ if (finished) return
|
|
|
+ finished = true
|
|
|
+ // 超时,移除iframe,进入XHR判断
|
|
|
+ document.body.removeChild(iframe)
|
|
|
+ // 用XHR判断
|
|
|
+ const xhr = new XMLHttpRequest()
|
|
|
+ xhr.open('GET', iframe.src, true)
|
|
|
+ xhr.onreadystatechange = function() {
|
|
|
+ if (xhr.readyState === 4) {
|
|
|
+ if (xhr.status === 200) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.bind(this)
|
|
|
+ xhr.onerror = function() {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }.bind(this)
|
|
|
+ xhr.send()
|
|
|
+ }, 5000)
|
|
|
+
|
|
|
+ iframe.onload = function() {
|
|
|
+ if (finished) return
|
|
|
+ finished = true
|
|
|
+ clearTimeout(timeout)
|
|
|
+ try {
|
|
|
+ // 尝试访问contentWindow.document
|
|
|
+ const doc = iframe?.contentWindow?.document
|
|
|
+ document.body.removeChild(iframe)
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ // 跨域或其他异常,移除iframe,进入XHR判断
|
|
|
+ document.body.removeChild(iframe)
|
|
|
+ const xhr = new XMLHttpRequest()
|
|
|
+ xhr.open('GET', iframe.src, true)
|
|
|
+ xhr.onreadystatechange = function() {
|
|
|
+ if (xhr.readyState === 4) {
|
|
|
+ if (xhr.status === 200) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.bind(this)
|
|
|
+ xhr.onerror = function() {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }.bind(this)
|
|
|
+ xhr.send()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ iframe.onerror = function() {
|
|
|
+ if (finished) return
|
|
|
+ finished = true
|
|
|
+ clearTimeout(timeout)
|
|
|
+ document.body.removeChild(iframe)
|
|
|
+ // iframe加载失败,进入XHR判断
|
|
|
+ const xhr = new XMLHttpRequest()
|
|
|
+ xhr.open('GET', iframe.src, true)
|
|
|
+ xhr.onreadystatechange = function() {
|
|
|
+ if (xhr.readyState === 4) {
|
|
|
+ if (xhr.status === 200) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }.bind(this)
|
|
|
+ xhr.onerror = function() {
|
|
|
+ // 再试一次 getFile
|
|
|
+ getFile(iframe.src as any).then(res => {
|
|
|
+ if (res && res.data && res.data !== 1) {
|
|
|
+ resolve(true)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(false)
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ resolve(false)
|
|
|
+ })
|
|
|
+ }.bind(this)
|
|
|
+ xhr.send()
|
|
|
+ }
|
|
|
+ document.body.appendChild(iframe)
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!isValid) {
|
|
|
+ message.error(lang.ssCocoLinkTip)
|
|
|
+ isLoading.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ isLoading.value = false
|
|
|
+
|
|
|
+ createFrameElement(webpageUrl.value, 73) // 假设15是网页工具的类型
|
|
|
+ // 清空输入框和验证状态
|
|
|
+ webpageUrl.value = ''
|
|
|
+ isValidUrl.value = null
|
|
|
+}
|
|
|
+
|
|
|
const toggleCollapse = () => {
|
|
|
isCollapsed.value = !isCollapsed.value
|
|
|
emit('toggle', isCollapsed.value)
|
|
|
@@ -568,6 +794,9 @@ const handleToolClick = (tool: string) => {
|
|
|
window.open('https://cloud.cocorobo.hk/admin.html?type=cocoflow3', '_blank')
|
|
|
}
|
|
|
}
|
|
|
+ else if (tool === 'uploadWebpage') {
|
|
|
+ activeSubmenu.value = 'uploadWebpage'
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const loadContentList = () => {
|
|
|
@@ -671,7 +900,7 @@ const getTypeClass = (type?: number) => {
|
|
|
|
|
|
import useImport from '@/hooks/useImport'
|
|
|
import message from '@/utils/message'
|
|
|
-const { importPPTXFile, exporting } = useImport()
|
|
|
+const { importPPTXFile, exporting, getFile } = useImport()
|
|
|
const currentFileName = ref('')
|
|
|
const parsingStatus = ref<'parsing' | 'success'>('parsing')
|
|
|
const parsingAbortController = ref<AbortController | null>(null)
|
|
|
@@ -1142,19 +1371,19 @@ const handleParsingClose = () => {
|
|
|
.parsing-content {
|
|
|
background: white;
|
|
|
border-radius: 12px;
|
|
|
- padding: 40px;
|
|
|
+ padding: 24px;
|
|
|
+ width: 400px;
|
|
|
text-align: center;
|
|
|
- max-width: 400px;
|
|
|
- width: 90%;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
.loading-spinner {
|
|
|
width: 48px;
|
|
|
height: 48px;
|
|
|
- border: 4px solid #f3f3f3;
|
|
|
+ border: 4px solid #f0f0f0;
|
|
|
border-top: 4px solid #FF9300;
|
|
|
border-radius: 50%;
|
|
|
- animation: spin 1s linear infinite;
|
|
|
margin: 0 auto 20px;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
}
|
|
|
|
|
|
.success-icon {
|
|
|
@@ -1236,4 +1465,73 @@ const handleParsingClose = () => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.line_box {
|
|
|
+ .webpage-link-container {
|
|
|
+ padding: 20px;
|
|
|
+ // text-align: center;
|
|
|
+
|
|
|
+ .webpage-link-title {
|
|
|
+ margin: 0 0 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .webpage-link-input {
|
|
|
+ width: 100%;
|
|
|
+ padding: 12px 12px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #FF9300;
|
|
|
+ background: #fff8f0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .webpage-link-button {
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px 24px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ background-color: #FF9300;
|
|
|
+ color: white;
|
|
|
+ margin: 0 auto;
|
|
|
+ display: block;
|
|
|
+
|
|
|
+ &:hover:not(:disabled) {
|
|
|
+ background-color: #e68a00;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.loading {
|
|
|
+ cursor: not-allowed;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.error {
|
|
|
+ background-color: #ff4d4f;
|
|
|
+ color: white;
|
|
|
+
|
|
|
+ &:hover:not(:disabled) {
|
|
|
+ background-color: #ff7875;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|