Spaces:
Sleeping
Sleeping
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Banana Pro AI</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <style> | |
| /* 仅添加需要覆盖的样式 */ | |
| .input-section { | |
| bottom: 60px ; | |
| } | |
| .history-container { | |
| padding-bottom: 220px ; | |
| } | |
| @media (max-width: 768px) { | |
| .input-section { | |
| bottom: 0 ; | |
| } | |
| .history-container { | |
| padding-bottom: 160px ; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- 登录界面 --> | |
| <div id="login-overlay"> | |
| <div class="login-card glass-panel"> | |
| <h1 style="font-size: 2rem;">🍌</h1> | |
| <h3 style="margin: 10px 0; color: #cbd5e1;">Banana Pro</h3> | |
| <input type="password" id="pwd" placeholder="Password" onkeypress="if(event.key==='Enter')doLogin()"> | |
| <button class="btn-3d" onclick="doLogin()" style="width: 100%; padding: 10px;">Unlock</button> | |
| </div> | |
| </div> | |
| <!-- 主应用 --> | |
| <div class="app-container" id="app" style="filter: blur(10px); pointer-events: none;"> | |
| <header> | |
| <div> | |
| <h2>创意画板</h2> | |
| <div style="font-size: 12px; color: var(--text-sub);" id="status-bar">Ready</div> | |
| </div> | |
| </header> | |
| <!-- 历史画廊 --> | |
| <div class="history-container"> | |
| <div class="grid-layout" id="gallery"></div> | |
| </div> | |
| <!-- 底部输入与预览组合区 --> | |
| <div class="input-section glass-panel" id="drop-zone"> | |
| <!-- 上部分:预览条 --> | |
| <div class="preview-bar" id="preview-bar"></div> | |
| <!-- 下部分:操作栏 --> | |
| <div class="control-bar"> | |
| <button class="upload-trigger" id="upload-btn" title="上传参考图">📷</button> | |
| <textarea id="prompt" class="main-input" rows="1" placeholder="描述画面... (支持拖拽图片)"></textarea> | |
| <button class="btn-3d send-btn" id="send-btn"> | |
| <span>生成</span> | |
| <div class="loader"></div> | |
| </button> | |
| </div> | |
| </div> | |
| <input type="file" id="file-input" multiple accept="image/*" hidden> | |
| </div> | |
| <!-- 详情弹窗 --> | |
| <div class="modal" id="modal"> | |
| <div class="modal-content glass-panel"> | |
| <button class="close-modal" onclick="closeModal()">×</button> | |
| <div class="modal-img-area"> | |
| <img id="m-img" src=""> | |
| </div> | |
| <div class="modal-footer"> | |
| <div class="input-refs" id="m-refs"></div> | |
| <div class="prompt-display" id="m-prompt"></div> | |
| <button class="btn-3d" id="m-reuse" style="width: 100%; padding: 12px;"> | |
| 🎨 复用参数与图片 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ============================================ | |
| // 应用配置 | |
| // ============================================ | |
| const CONFIG = { | |
| DB_NAME: 'BananaProDB_v3', | |
| DB_VERSION: 1, | |
| STORE_NAME: 'artworks', | |
| MAX_IMAGES: 16, | |
| ERROR_IMAGE: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect fill="%23333" width="100" height="100"/><text fill="%23666" x="50%" y="50%" text-anchor="middle" dy=".3em">Error</text></svg>' | |
| }; | |
| // ============================================ | |
| // 全局状态管理 | |
| // ============================================ | |
| class AppState { | |
| constructor() { | |
| this.db = null; | |
| this.currentImages = []; | |
| this.galleryData = []; | |
| this.currentModalItem = null; | |
| this._listeners = new Map(); | |
| } | |
| // 简单的事件系统 | |
| on(event, callback) { | |
| if (!this._listeners.has(event)) { | |
| this._listeners.set(event, new Set()); | |
| } | |
| this._listeners.get(event).add(callback); | |
| } | |
| emit(event, data) { | |
| if (this._listeners.has(event)) { | |
| this._listeners.get(event).forEach(callback => callback(data)); | |
| } | |
| } | |
| } | |
| const appState = new AppState(); | |
| // ============================================ | |
| // 工具函数 | |
| // ============================================ | |
| const Utils = { | |
| // 防抖函数 | |
| debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| }, | |
| // 创建元素的辅助函数 | |
| createElement(tag, attrs = {}, children = []) { | |
| const el = document.createElement(tag); | |
| Object.entries(attrs).forEach(([key, value]) => { | |
| if (key === 'className') { | |
| el.className = value; | |
| } else if (key === 'textContent') { | |
| el.textContent = value; | |
| } else if (key.startsWith('on')) { | |
| el.addEventListener(key.slice(2).toLowerCase(), value); | |
| } else { | |
| el.setAttribute(key, value); | |
| } | |
| }); | |
| children.forEach(child => { | |
| if (typeof child === 'string') { | |
| el.appendChild(document.createTextNode(child)); | |
| } else { | |
| el.appendChild(child); | |
| } | |
| }); | |
| return el; | |
| } | |
| }; | |
| // ============================================ | |
| // IndexedDB 模块 | |
| // ============================================ | |
| class Database { | |
| static async init() { | |
| return new Promise((resolve, reject) => { | |
| const request = indexedDB.open(CONFIG.DB_NAME, CONFIG.DB_VERSION); | |
| request.onerror = () => reject(new Error('数据库打开失败')); | |
| request.onsuccess = () => { | |
| appState.db = request.result; | |
| resolve(); | |
| }; | |
| request.onupgradeneeded = (event) => { | |
| const db = event.target.result; | |
| if (!db.objectStoreNames.contains(CONFIG.STORE_NAME)) { | |
| const store = db.createObjectStore(CONFIG.STORE_NAME, { | |
| keyPath: 'id', | |
| autoIncrement: true | |
| }); | |
| store.createIndex('timestamp', 'timestamp', { unique: false }); | |
| } | |
| }; | |
| }); | |
| } | |
| static async transaction(storeName, mode, operation) { | |
| return new Promise((resolve, reject) => { | |
| const tx = appState.db.transaction([storeName], mode); | |
| const store = tx.objectStore(storeName); | |
| tx.oncomplete = () => resolve(result); | |
| tx.onerror = () => reject(new Error('事务执行失败')); | |
| let result; | |
| try { | |
| result = operation(store); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }); | |
| } | |
| static async save(item) { | |
| return this.transaction(CONFIG.STORE_NAME, 'readwrite', (store) => { | |
| const record = { | |
| ...item, | |
| timestamp: Date.now() | |
| }; | |
| return store.add(record); | |
| }); | |
| } | |
| static async getAll() { | |
| return new Promise((resolve, reject) => { | |
| const tx = appState.db.transaction([CONFIG.STORE_NAME], 'readonly'); | |
| const store = tx.objectStore(CONFIG.STORE_NAME); | |
| const request = store.getAll(); | |
| request.onsuccess = () => { | |
| const results = request.result.sort((a, b) => b.timestamp - a.timestamp); | |
| resolve(results); | |
| }; | |
| request.onerror = () => reject(new Error('读取数据失败')); | |
| }); | |
| } | |
| static async delete(id) { | |
| return this.transaction(CONFIG.STORE_NAME, 'readwrite', (store) => { | |
| return store.delete(id); | |
| }); | |
| } | |
| static async getById(id) { | |
| return new Promise((resolve, reject) => { | |
| const tx = appState.db.transaction([CONFIG.STORE_NAME], 'readonly'); | |
| const store = tx.objectStore(CONFIG.STORE_NAME); | |
| const request = store.get(id); | |
| request.onsuccess = () => resolve(request.result); | |
| request.onerror = () => reject(new Error('获取数据失败')); | |
| }); | |
| } | |
| } | |
| // ============================================ | |
| // 图片处理模块 | |
| // ============================================ | |
| class ImageHandler { | |
| static fileToBase64(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve(reader.result); | |
| reader.onerror = reject; | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| static async processFiles(files) { | |
| const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/')); | |
| const promises = []; | |
| for (const file of imageFiles) { | |
| if (appState.currentImages.length >= CONFIG.MAX_IMAGES) { | |
| alert(`最多只能上传 ${CONFIG.MAX_IMAGES} 张图片`); | |
| break; | |
| } | |
| promises.push(this.fileToBase64(file)); | |
| } | |
| try { | |
| const results = await Promise.all(promises); | |
| appState.currentImages.push(...results); | |
| appState.emit('imagesChanged'); | |
| } catch (err) { | |
| console.error('图片读取失败:', err); | |
| alert('部分图片读取失败'); | |
| } | |
| } | |
| static removeAt(index) { | |
| appState.currentImages.splice(index, 1); | |
| appState.emit('imagesChanged'); | |
| } | |
| static clear() { | |
| appState.currentImages = []; | |
| appState.emit('imagesChanged'); | |
| } | |
| static setImages(images) { | |
| appState.currentImages = images ? [...images] : []; | |
| appState.emit('imagesChanged'); | |
| } | |
| } | |
| // ============================================ | |
| // UI组件基类 | |
| // ============================================ | |
| class Component { | |
| constructor(container) { | |
| this.container = typeof container === 'string' | |
| ? document.getElementById(container) | |
| : container; | |
| } | |
| render() { | |
| throw new Error('render method must be implemented'); | |
| } | |
| } | |
| // ============================================ | |
| // 预览条管理模块 | |
| // ============================================ | |
| class PreviewManager extends Component { | |
| constructor() { | |
| super('preview-bar'); | |
| this.uploadBtn = document.getElementById('upload-btn'); | |
| this.statusBar = document.getElementById('status-bar'); | |
| // 监听图片变化 | |
| appState.on('imagesChanged', () => this.render()); | |
| } | |
| render() { | |
| const images = appState.currentImages; | |
| // 使用 DocumentFragment 减少重排 | |
| const fragment = document.createDocumentFragment(); | |
| if (images.length === 0) { | |
| this.container.classList.remove('visible'); | |
| this.uploadBtn.classList.remove('active'); | |
| this.statusBar.textContent = 'Ready'; | |
| this.container.innerHTML = ''; | |
| return; | |
| } | |
| this.container.classList.add('visible'); | |
| this.uploadBtn.classList.add('active'); | |
| this.statusBar.textContent = `已选择 ${images.length}/${CONFIG.MAX_IMAGES} 张图片`; | |
| images.forEach((imgData, index) => { | |
| const wrapper = Utils.createElement('div', { className: 'thumb-wrapper' }, [ | |
| Utils.createElement('img', { src: imgData }), | |
| Utils.createElement('div', { | |
| className: 'thumb-remove', | |
| textContent: '×', | |
| onClick: (e) => { | |
| e.stopPropagation(); | |
| ImageHandler.removeAt(index); | |
| } | |
| }) | |
| ]); | |
| fragment.appendChild(wrapper); | |
| }); | |
| this.container.innerHTML = ''; | |
| this.container.appendChild(fragment); | |
| } | |
| } | |
| // ============================================ | |
| // 画廊管理模块 | |
| // ============================================ | |
| class GalleryManager extends Component { | |
| constructor() { | |
| super('gallery'); | |
| this.renderDebounced = Utils.debounce(() => this.render(), 100); | |
| } | |
| async load() { | |
| try { | |
| appState.galleryData = await Database.getAll(); | |
| this.render(); | |
| } catch (err) { | |
| console.error('加载画廊失败:', err); | |
| alert('加载画廊失败,请刷新页面重试'); | |
| } | |
| } | |
| render() { | |
| if (appState.galleryData.length === 0) { | |
| this.container.innerHTML = ` | |
| <div style="grid-column: 1/-1; text-align: center; color: var(--text-sub); padding: 60px 20px;"> | |
| <div style="font-size: 48px; margin-bottom: 10px;">🎨</div> | |
| <div>暂无作品,开始创作吧!</div> | |
| </div> | |
| `; | |
| return; | |
| } | |
| const fragment = document.createDocumentFragment(); | |
| appState.galleryData.forEach(item => { | |
| fragment.appendChild(this.createCard(item)); | |
| }); | |
| this.container.innerHTML = ''; | |
| this.container.appendChild(fragment); | |
| } | |
| createCard(item) { | |
| const card = Utils.createElement('div', { | |
| className: 'history-item', | |
| 'data-id': item.id, | |
| onClick: () => ModalManager.open(item) | |
| }); | |
| // 参考图标记 | |
| if (item.inputImages?.length > 0) { | |
| card.appendChild(Utils.createElement('div', { | |
| className: 'item-badge', | |
| textContent: `📎 ${item.inputImages.length}` | |
| })); | |
| } | |
| // 主图片 | |
| const img = Utils.createElement('img', { | |
| loading: 'lazy', | |
| src: item.image, | |
| onError: function() { | |
| this.src = CONFIG.ERROR_IMAGE; | |
| console.error('图片加载失败, ID:', item.id); | |
| } | |
| }); | |
| card.appendChild(img); | |
| // 操作按钮 | |
| const actions = Utils.createElement('div', { className: 'item-actions' }, [ | |
| Utils.createElement('button', { | |
| className: 'icon-btn', | |
| innerHTML: '⬇', | |
| onClick: (e) => { | |
| e.stopPropagation(); | |
| this.downloadImage(item); | |
| } | |
| }), | |
| Utils.createElement('button', { | |
| className: 'icon-btn', | |
| style: 'background: rgba(239,68,68,0.8)', | |
| innerHTML: '🗑', | |
| onClick: (e) => { | |
| e.stopPropagation(); | |
| this.deleteItem(item.id); | |
| } | |
| }) | |
| ]); | |
| card.appendChild(actions); | |
| return card; | |
| } | |
| downloadImage(item) { | |
| const link = Utils.createElement('a', { | |
| href: item.image, | |
| download: `banana-pro-${item.id}-${Date.now()}.png` | |
| }); | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| async deleteItem(id) { | |
| if (!confirm('确定要删除这张图片吗?')) return; | |
| try { | |
| await Database.delete(id); | |
| await this.load(); | |
| } catch (err) { | |
| console.error('删除失败:', err); | |
| alert('删除失败,请重试'); | |
| } | |
| } | |
| } | |
| // ============================================ | |
| // 弹窗管理模块 | |
| // ============================================ | |
| class ModalManager { | |
| static init() { | |
| this.modal = document.getElementById('modal'); | |
| this.imgEl = document.getElementById('m-img'); | |
| this.promptEl = document.getElementById('m-prompt'); | |
| this.refsEl = document.getElementById('m-refs'); | |
| this.reuseBtn = document.getElementById('m-reuse'); | |
| // 事件绑定 | |
| this.modal.addEventListener('click', (e) => { | |
| if (e.target === this.modal) this.close(); | |
| }); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape' && this.modal.style.display === 'flex') { | |
| this.close(); | |
| } | |
| }); | |
| this.reuseBtn.addEventListener('click', () => this.reuse()); | |
| } | |
| static open(item) { | |
| appState.currentModalItem = item; | |
| this.imgEl.src = item.image; | |
| this.promptEl.textContent = item.prompt; | |
| // 渲染参考图 | |
| const fragment = document.createDocumentFragment(); | |
| if (item.inputImages?.length > 0) { | |
| item.inputImages.forEach(imgData => { | |
| const thumb = Utils.createElement('img', { | |
| className: 'ref-thumb', | |
| src: imgData, | |
| onClick: () => window.open(imgData, '_blank') | |
| }); | |
| fragment.appendChild(thumb); | |
| }); | |
| } | |
| this.refsEl.innerHTML = ''; | |
| this.refsEl.appendChild(fragment); | |
| this.modal.style.display = 'flex'; | |
| } | |
| static close() { | |
| this.modal.style.display = 'none'; | |
| appState.currentModalItem = null; | |
| } | |
| static reuse() { | |
| const item = appState.currentModalItem; | |
| if (!item) return; | |
| const textarea = document.getElementById('prompt'); | |
| textarea.value = item.prompt; | |
| this.adjustTextareaHeight(textarea); | |
| ImageHandler.setImages(item.inputImages || []); | |
| this.close(); | |
| textarea.focus(); | |
| } | |
| static adjustTextareaHeight(textarea) { | |
| textarea.style.height = 'auto'; | |
| textarea.style.height = textarea.scrollHeight + 'px'; | |
| } | |
| } | |
| // ============================================ | |
| // 生成请求模块 | |
| // ============================================ | |
| class Generator { | |
| constructor() { | |
| this.sendBtn = document.getElementById('send-btn'); | |
| this.textarea = document.getElementById('prompt'); | |
| this.isGenerating = false; | |
| this.bindEvents(); | |
| } | |
| bindEvents() { | |
| this.sendBtn.addEventListener('click', () => this.generate()); | |
| // 输入框自动高度 | |
| this.textarea.addEventListener('input', () => { | |
| ModalManager.adjustTextareaHeight(this.textarea); | |
| }); | |
| // 回车发送 | |
| this.textarea.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey && !this.isGenerating) { | |
| e.preventDefault(); | |
| this.generate(); | |
| } | |
| }); | |
| } | |
| setLoading(loading) { | |
| this.isGenerating = loading; | |
| this.sendBtn.classList.toggle('loading', loading); | |
| this.sendBtn.disabled = loading; | |
| } | |
| async generate() { | |
| const prompt = this.textarea.value.trim(); | |
| if (!prompt) { | |
| alert('请输入提示词'); | |
| this.textarea.focus(); | |
| return; | |
| } | |
| this.setLoading(true); | |
| try { | |
| const response = await fetch('/api/generate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| prompt: prompt, | |
| images: appState.currentImages | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| if (!data.success || !data.image?.startsWith('data:image')) { | |
| throw new Error(data.message || '生成失败:返回的图片数据无效'); | |
| } | |
| // 保存到数据库 | |
| await Database.save({ | |
| prompt: prompt, | |
| image: data.image, | |
| inputImages: [...appState.currentImages] | |
| }); | |
| // 刷新画廊 | |
| await galleryManager.load(); | |
| // 清空输入 | |
| this.textarea.value = ''; | |
| this.textarea.style.height = 'auto'; | |
| ImageHandler.clear(); | |
| } catch (err) { | |
| console.error('生成失败:', err); | |
| alert(`生成失败: ${err.message}`); | |
| } finally { | |
| this.setLoading(false); | |
| } | |
| } | |
| } | |
| // ============================================ | |
| // 认证模块 | |
| // ============================================ | |
| class Auth { | |
| static async check() { | |
| try { | |
| const res = await fetch('/api/check-auth'); | |
| const data = await res.json(); | |
| return data.authenticated === true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| static async login(password) { | |
| try { | |
| const res = await fetch('/api/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ password }) | |
| }); | |
| const data = await res.json(); | |
| return data.success === true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| static unlock() { | |
| document.getElementById('login-overlay').style.display = 'none'; | |
| const app = document.getElementById('app'); | |
| app.style.filter = 'none'; | |
| app.style.pointerEvents = 'all'; | |
| } | |
| } | |
| // ============================================ | |
| // 拖拽上传模块 | |
| // ============================================ | |
| class DragDrop { | |
| constructor(dropZone) { | |
| this.dropZone = document.getElementById(dropZone); | |
| this.dragCounter = 0; | |
| this.bindEvents(); | |
| } | |
| bindEvents() { | |
| const events = ['dragenter', 'dragover', 'dragleave', 'drop']; | |
| events.forEach(event => { | |
| this.dropZone.addEventListener(event, this.preventDefaults); | |
| }); | |
| this.dropZone.addEventListener('dragenter', () => { | |
| this.dragCounter++; | |
| this.highlight(); | |
| }); | |
| this.dropZone.addEventListener('dragleave', () => { | |
| this.dragCounter--; | |
| if (this.dragCounter === 0) { | |
| this.unhighlight(); | |
| } | |
| }); | |
| this.dropZone.addEventListener('drop', async (e) => { | |
| this.dragCounter = 0; | |
| this.unhighlight(); | |
| const files = e.dataTransfer.files; | |
| if (files.length > 0) { | |
| await ImageHandler.processFiles(files); | |
| } | |
| }); | |
| } | |
| preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| highlight() { | |
| this.dropZone.style.borderColor = 'var(--accent-color)'; | |
| this.dropZone.style.background = 'rgba(59, 130, 246, 0.1)'; | |
| } | |
| unhighlight() { | |
| this.dropZone.style.borderColor = ''; | |
| this.dropZone.style.background = ''; | |
| } | |
| } | |
| // ============================================ | |
| // 文件选择模块 | |
| // ============================================ | |
| class FileSelector { | |
| constructor() { | |
| this.input = document.getElementById('file-input'); | |
| this.btn = document.getElementById('upload-btn'); | |
| this.bindEvents(); | |
| } | |
| bindEvents() { | |
| this.btn.addEventListener('click', () => this.input.click()); | |
| this.input.addEventListener('change', async () => { | |
| if (this.input.files.length > 0) { | |
| await ImageHandler.processFiles(this.input.files); | |
| } | |
| this.input.value = ''; | |
| }); | |
| } | |
| } | |
| // ============================================ | |
| // 全局实例 | |
| // ============================================ | |
| let previewManager, galleryManager, generator, dragDrop, fileSelector; | |
| // ============================================ | |
| // 全局函数 | |
| // ============================================ | |
| async function doLogin() { | |
| const pwd = document.getElementById('pwd').value.trim(); | |
| if (!pwd) { | |
| document.getElementById('pwd').focus(); | |
| return; | |
| } | |
| const button = event.target; | |
| button.disabled = true; | |
| try { | |
| const success = await Auth.login(pwd); | |
| if (success) { | |
| Auth.unlock(); | |
| await galleryManager.load(); | |
| } else { | |
| alert('密码错误'); | |
| document.getElementById('pwd').value = ''; | |
| document.getElementById('pwd').focus(); | |
| } | |
| } finally { | |
| button.disabled = false; | |
| } | |
| } | |
| function closeModal() { | |
| ModalManager.close(); | |
| } | |
| // ============================================ | |
| // 应用初始化 | |
| // ============================================ | |
| async function initApp() { | |
| try { | |
| // 初始化数据库 | |
| await Database.init(); | |
| // 初始化各模块 | |
| previewManager = new PreviewManager(); | |
| galleryManager = new GalleryManager(); | |
| ModalManager.init(); | |
| generator = new Generator(); | |
| dragDrop = new DragDrop('drop-zone'); | |
| fileSelector = new FileSelector(); | |
| // 检查认证状态 | |
| const isAuth = await Auth.check(); | |
| if (isAuth) { | |
| Auth.unlock(); | |
| await galleryManager.load(); | |
| } else { | |
| // 聚焦到密码输入框 | |
| document.getElementById('pwd').focus(); | |
| } | |
| console.log('App initialized successfully'); | |
| } catch (err) { | |
| console.error('App initialization failed:', err); | |
| alert('应用初始化失败,请刷新页面重试'); | |
| } | |
| } | |
| // 启动应用 | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', initApp); | |
| } else { | |
| initApp(); | |
| } | |
| </script> | |
| </body> | |
| </html> |