{
  "type": "page",
  "body": [
    {
      "type": "liquid",
      "id": "u:b10e5475cbfd",
      "template": "\n<div class=\"max-w-7xl mx-auto p-4 sm:p-8\">\n    \n    \n    <div class=\"flex items-center justify-between gap-4 mb-8 h-16\">\n        <div class=\"shrink-0\">\n            <h1 class=\"text-2xl font-bold text-slate-900 tracking-tight leading-none\">应用市场</h1>\n        </div>\n        <div class=\"flex items-center justify-end gap-3 flex-1 min-w-0\">\n            <div class=\"relative group w-full max-w-xs\">\n                <input type=\"text\" id=\"search-input\" class=\"block w-full pl-9 pr-3 py-2 bg-white border border-slate-200 rounded-lg text-sm placeholder-slate-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all shadow-sm\" placeholder=\"搜索应用...\">\n            </div>\n        </div>\n    </div>\n\n    \n    <div class=\"relative overflow-hidden rounded-2xl bg-gradient-to-r from-blue-600 to-indigo-600 shadow-xl shadow-blue-900/10 mb-8\">\n        <div class=\"absolute top-0 right-0 -mr-16 -mt-16 w-64 h-64 rounded-full bg-white opacity-10 blur-3xl\"></div>\n        <div class=\"relative z-10 px-6 py-8 flex flex-col md:flex-row items-center justify-between gap-4\">\n            <div>\n                <h2 class=\"text-xl md:text-2xl font-bold text-white mb-1\">Steedos 3.0 现已上线</h2>\n                <p class=\"text-blue-100 text-sm opacity-90\">采用先进的 元数据驱动 架构，帮助企业以传统开发 1/10 的成本，快速构建安全、可扩展的数字化核心应用。</p>\n            </div>\n            <a class=\"shrink-0 bg-white text-blue-600 hover:bg-blue-50 font-semibold py-2 px-5 rounded-lg text-sm shadow-sm transition-colors\" href=\"https://docs.steedos.com\" target=\"_blank\">查看详情</a>\n        </div>\n    </div>\n\n    \n    <div id=\"app-grid\" class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 min-h-[300px]\">\n        \n        <div class=\"col-span-full flex flex-col items-center justify-center py-20 text-slate-400\">\n            <svg class=\"animate-spin h-8 w-8 text-blue-500 mb-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n                <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n                <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n            </svg>\n            <span>正在加载应用数据...</span>\n        </div>\n    </div>\n</div>\n\n<script>\n(function() {\n    // === 1. 配置 ===\n    // 修改点：使用新的 Rest API 获取已安装列表，设置 top=5000 确保获取全部\n    const API_INSTALLED = '/api/v6/data/steedos_packages?skip=0&top=5000';\n    const API_MARKET = '/api/cloud/appstore/packages'; \n    const API_INSTALL = '/api/nodes/package/add';\n    \n    let globalMarketApps = [];\n    let globalInstalledNames = [];\n\n    // === 2. 核心：等待 DOM 就绪 ===\n    function checkReady() {\n        const grid = document.getElementById('app-grid');\n        const searchInput = document.getElementById('search-input');\n        \n        if (grid && searchInput) {\n            init();\n            \n            searchInput.addEventListener('input', (e) => {\n                const term = e.target.value.toLowerCase();\n                const filtered = globalMarketApps.filter(app => \n                    (app.name && app.name.toLowerCase().includes(term)) || \n                    (app.label && app.label.toLowerCase().includes(term))\n                );\n                render(filtered, globalInstalledNames);\n            });\n        } else {\n            setTimeout(checkReady, 100);\n        }\n    }\n\n    // === 3. 初始化数据 ===\n    async function init() {\n        try {\n            const [installedRes, marketRes] = await Promise.all([\n                fetchInstalled(),\n                fetchMarket()\n            ]);\n\n            globalInstalledNames = installedRes;\n            globalMarketApps = marketRes;\n\n            render(globalMarketApps, globalInstalledNames);\n\n        } catch (error) {\n            console.error('Init Error:', error);\n            const grid = document.getElementById('app-grid');\n            if(grid) {\n                grid.innerHTML = `<div class=\"col-span-full text-center py-10 text-red-500\">数据加载失败: ${error.message}</div>`;\n            }\n        }\n    }\n\n    // === 4. API 请求 ===\n    \n    // 修改点：fetchInstalled 改为 GET 请求并适配新数据结构\n    async function fetchInstalled() {\n        try {\n            const res = await fetch(API_INSTALLED, {\n                method: 'GET', // 默认为 GET，显式写出方便理解\n                headers: { 'Content-Type': 'application/json' }\n            });\n            if (!res.ok) return [];\n            const json = await res.json();\n            // 新接口结构是 { data: [...] }\n            const list = json.data || [];\n            // 提取 name 字段用于比对\n            return list.map(item => item.name);\n        } catch(e) {\n            console.warn('Failed to fetch installed packages', e);\n            return [];\n        }\n    }\n\n    async function fetchMarket() {\n        const res = await fetch(API_MARKET);\n        if (!res.ok) throw new Error(`API Error: ${res.status}`);\n        const json = await res.json();\n        return Array.isArray(json) ? json : (json.data || json.rows || []);\n    }\n\n    // === 5. 渲染 HTML ===\n    function render(apps, installedNames) {\n        const grid = document.getElementById('app-grid');\n        if (!grid) return;\n\n        if (!apps || apps.length === 0) {\n            grid.innerHTML = '<div class=\"col-span-full text-center text-slate-400 py-20\">暂无应用数据</div>';\n            return;\n        }\n\n        const html = apps.map(app => {\n            const isInstalled = installedNames.includes(app.package_name);\n            const iconSlds = app.icon_slds || 'user';\n            const displayName = app.name; \n            const description = app.description || '暂无描述信息';\n            const vender = app.vender;\n            const version = app.version;\n            \n            let btnHtml = '';\n            if (isInstalled) {\n                btnHtml = `\n                    <button class=\"w-full rounded-lg bg-slate-100 text-slate-400 font-semibold py-2 px-4 cursor-not-allowed\" disabled>\n                        已安装\n                    </button>`;\n            } else {\n                btnHtml = `\n                    <button \n                        onclick=\"window.installApp('\\${app.package_name}', this)\" \n                        class=\"w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-lg shadow-lg shadow-blue-500/30 transition-all active:scale-95 flex items-center justify-center gap-2\">\n                        立即安装\n                    </button>`;\n            }\n\n            return `\n            <div class=\"group relative bg-white rounded-xl border border-slate-200 p-5 flex flex-col h-[260px] shadow-sm hover:shadow-xl hover:shadow-slate-200/50 hover:-translate-y-1 transition-all duration-300 overflow-hidden\">\n                <div class=\"flex items-start justify-between mb-4\">\n                    <div class=\"w-14 h-14 rounded-xl flex items-center justify-center slds-icon_container slds-icon-standard-\\${iconSlds}\">\n                        <svg class=\"w-8 h-8 slds-icon\" aria-hidden=\"true\">\n                            <use xlink:href=\"/assets/icons/standard-sprite/svg/symbols.svg#\\${iconSlds}\"></use>\n                        </svg>\n                    </div>\n                    \\${app.featured ? '<span class=\"px-2 py-1 rounded text-[10px] font-bold bg-slate-100 text-slate-500 uppercase tracking-wider\">推荐</span>' : ''}\n                </div>\n\n                <h3 class=\"text-lg font-bold text-slate-800 mb-2 transition-colors truncate\" title=\"\\${displayName}\">\n                    \\${displayName}\n                </h3>\n                \n                <p class=\"text-sm text-slate-500 leading-relaxed line-clamp-3\">\\${description}</p>\n\n                <div class=\"mt-auto pt-2 flex justify-between items-center border-t border-slate-50\">\n                    <p class=\"text-xs text-slate-400 mt-2\">v\\${version}</p>\n                    <p class=\"text-xs text-slate-400 mt-2\">\\${vender}</p>\n                </div>\n\n                <div class=\"absolute inset-x-0 bottom-0 pt-10 pb-5 px-5 bg-gradient-to-t from-white via-white/95 to-transparent translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-out flex items-end\">\n                    \\${btnHtml}\n                </div>\n            </div>\n            `;\n        }).join('');\n\n        grid.innerHTML = html;\n    }\n\n    // === 6. 挂载点击事件 ===\n    window.installApp = async function(packageName, btnElement) {\n        const originalText = btnElement.innerText;\n        btnElement.innerText = '安装中...';\n        btnElement.disabled = true;\n        btnElement.classList.add('opacity-75', 'cursor-wait');\n\n        try {\n            const registry = 'https://registry.npmmirror.com';\n            const response = await fetch(API_INSTALL, {\n                method: 'POST',\n                headers: { 'Content-Type': 'application/json' },\n                body: JSON.stringify({\n                    package: packageName\n                })\n            });\n\n            if (!response.ok) throw new Error('Network error');\n            await response.json();\n\n            // 成功\n            btnElement.innerText = '已安装';\n            btnElement.className = 'w-full rounded-lg bg-slate-100 text-slate-400 font-semibold py-2 px-4 cursor-not-allowed';\n            globalInstalledNames.push(packageName);\n\n        } catch (error) {\n            console.error('Install Failed:', error);\n            alert('安装失败，请重试');\n            btnElement.innerText = originalText;\n            btnElement.disabled = false;\n            btnElement.classList.remove('opacity-75', 'cursor-wait');\n        }\n    };\n\n    checkReady();\n})();\n</script>"
    }
  ]
}