feat: 全站20个模板添加国际化(i18n)支持 - 简体中文/繁体中文/英文三语切换
- 所有18套前端模板 + 管理后台共20个HTML文件添加i18n引擎 - 前端JavaScript方案:I18N_LOCALES翻译字典 + data-i18n属性 + t()函数调用 - 语言切换器UI:下拉菜单选择,匹配各模板主题风格 - 语言持久化:localStorage存储 + URL参数覆盖 + 浏览器语言自动检测 - onLangChange回调:语言切换时动态重新渲染所有内容 - 支持语言:zh-CN(简体中文) / zh-TW(繁体中文) / en(English)
This commit is contained in:
553
web/dist/app1/index.html
vendored
553
web/dist/app1/index.html
vendored
@@ -94,6 +94,34 @@ img { max-width: 100%; display: block; }
|
||||
.header-search { flex: 1; }
|
||||
}
|
||||
|
||||
/* ============ Language Switcher ============ */
|
||||
.lang-switcher { position: relative; flex-shrink: 0; }
|
||||
.lang-btn {
|
||||
display: flex; align-items: center; gap: 6px; padding: 7px 12px; border-radius: 980px;
|
||||
font-size: 13px; font-weight: 500; color: var(--text-secondary);
|
||||
border: 1px solid var(--border-soft); background: var(--bg-soft);
|
||||
transition: all .25s ease;
|
||||
}
|
||||
.lang-btn:hover { background: #ededf0; color: var(--text); border-color: var(--border); }
|
||||
.lang-btn svg { width: 16px; height: 16px; }
|
||||
.lang-btn .chevron { width: 12px; height: 12px; }
|
||||
.lang-dropdown {
|
||||
position: absolute; top: calc(100% + 6px); right: 0; min-width: 150px;
|
||||
background: #fff; border: 1px solid var(--border-soft); border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg); padding: 6px; z-index: 200;
|
||||
opacity: 0; visibility: hidden; transform: translateY(-6px); transition: all .2s ease;
|
||||
}
|
||||
.lang-dropdown.open { opacity: 1; visibility: visible; transform: translateY(0); }
|
||||
.lang-option {
|
||||
display: flex; align-items: center; gap: 8px; padding: 9px 12px;
|
||||
border-radius: var(--radius-sm); font-size: 14px; color: var(--text-secondary);
|
||||
cursor: pointer; transition: all .15s;
|
||||
}
|
||||
.lang-option:hover { background: var(--bg-soft); color: var(--text); }
|
||||
.lang-option.active { color: var(--blue); font-weight: 600; }
|
||||
.lang-option .check { width: 16px; height: 16px; opacity: 0; transition: opacity .2s; }
|
||||
.lang-option.active .check { opacity: 1; }
|
||||
|
||||
/* ============ Hero ============ */
|
||||
.hero {
|
||||
padding: 72px 0 56px; text-align: center; position: relative; overflow: hidden;
|
||||
@@ -383,17 +411,38 @@ img { max-width: 100%; display: block; }
|
||||
<div class="header-inner">
|
||||
<a href="#" class="logo" onclick="goHome(event)">
|
||||
<span class="logo-mark">✚</span>
|
||||
<span>应用商店</span>
|
||||
<span data-i18n="logo_name">应用商店</span>
|
||||
</a>
|
||||
<nav class="main-nav" id="mainNav">
|
||||
<a href="#" class="nav-link active" data-nav="home" onclick="navTo(event,'home')">首页</a>
|
||||
<a href="#" class="nav-link" data-nav="discover" onclick="navTo(event,'discover')">发现</a>
|
||||
<a href="#" class="nav-link" data-nav="ranking" onclick="navTo(event,'ranking')">排行榜</a>
|
||||
<a href="#" class="nav-link" data-nav="new" onclick="navTo(event,'new')">新上架</a>
|
||||
<a href="#" class="nav-link active" data-nav="home" onclick="navTo(event,'home')" data-i18n="nav_home">首页</a>
|
||||
<a href="#" class="nav-link" data-nav="discover" onclick="navTo(event,'discover')" data-i18n="nav_discover">发现</a>
|
||||
<a href="#" class="nav-link" data-nav="ranking" onclick="navTo(event,'ranking')" data-i18n="nav_ranking">排行榜</a>
|
||||
<a href="#" class="nav-link" data-nav="new" onclick="navTo(event,'new')" data-i18n="nav_new">新上架</a>
|
||||
</nav>
|
||||
<div class="header-search">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input type="text" id="headerSearch" placeholder="搜索应用、游戏..." autocomplete="off">
|
||||
<input type="text" id="headerSearch" data-i18n-placeholder="search_all" placeholder="搜索应用、游戏..." autocomplete="off">
|
||||
</div>
|
||||
<div class="lang-switcher" id="langSwitcher">
|
||||
<button class="lang-btn" id="langBtn" onclick="toggleLangDropdown()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/></svg>
|
||||
<span id="langLabel">中文</span>
|
||||
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 9l6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div class="lang-dropdown" id="langDropdown">
|
||||
<div class="lang-option" data-lang="zh-CN" onclick="switchLang('zh-CN')">
|
||||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
<span>简体中文</span>
|
||||
</div>
|
||||
<div class="lang-option" data-lang="zh-TW" onclick="switchLang('zh-TW')">
|
||||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
<span>繁體中文</span>
|
||||
</div>
|
||||
<div class="lang-option" data-lang="en" onclick="switchLang('en')">
|
||||
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
<span>English</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -401,11 +450,11 @@ img { max-width: 100%; display: block; }
|
||||
<!-- ======================= HERO ======================= -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<span class="hero-eyebrow">全新体验 · 精选推荐</span>
|
||||
<h1>发现精彩应用</h1>
|
||||
<p>探索海量优质应用与游戏,从效率工具到娱乐大作,总有一款适合你。</p>
|
||||
<span class="hero-eyebrow" data-i18n="hero_eyebrow">全新体验 · 精选推荐</span>
|
||||
<h1 data-i18n="hero_title">发现精彩应用</h1>
|
||||
<p data-i18n="hero_desc">探索海量优质应用与游戏,从效率工具到娱乐大作,总有一款适合你。</p>
|
||||
<a href="#library" class="hero-cta" onclick="exploreNow(event)">
|
||||
立即探索
|
||||
<span data-i18n="hero_cta">立即探索</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
@@ -427,10 +476,10 @@ img { max-width: 100%; display: block; }
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2 class="section-title">精选推荐</h2>
|
||||
<p class="section-sub">编辑团队为你精心挑选</p>
|
||||
<h2 class="section-title" data-i18n="featured_title">精选推荐</h2>
|
||||
<p class="section-sub" data-i18n="featured_sub">编辑团队为你精心挑选</p>
|
||||
</div>
|
||||
<a href="#" class="section-more" onclick="seeAllFeatured(event)">查看全部 ›</a>
|
||||
<a href="#" class="section-more" onclick="seeAllFeatured(event)" data-i18n="see_all">查看全部 ›</a>
|
||||
</div>
|
||||
<div class="featured-grid" id="featuredGrid">
|
||||
<div class="feature-big skeleton"></div>
|
||||
@@ -445,10 +494,10 @@ img { max-width: 100%; display: block; }
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2 class="section-title">新上架</h2>
|
||||
<p class="section-sub">最新加入的应用</p>
|
||||
<h2 class="section-title" data-i18n="new_title">新上架</h2>
|
||||
<p class="section-sub" data-i18n="new_sub">最新加入的应用</p>
|
||||
</div>
|
||||
<a href="#" class="section-more" onclick="seeAllNew(event)">查看全部 ›</a>
|
||||
<a href="#" class="section-more" onclick="seeAllNew(event)" data-i18n="see_all">查看全部 ›</a>
|
||||
</div>
|
||||
<div class="h-scroll-wrap">
|
||||
<div class="h-scroll" id="newScroll">
|
||||
@@ -466,26 +515,26 @@ img { max-width: 100%; display: block; }
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2 class="section-title">全部应用</h2>
|
||||
<p class="section-sub">浏览所有应用</p>
|
||||
<h2 class="section-title" data-i18n="library_title">全部应用</h2>
|
||||
<p class="section-sub" data-i18n="library_sub">浏览所有应用</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library-controls">
|
||||
<div class="lib-search">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
<input type="text" id="libSearch" placeholder="搜索应用名称..." autocomplete="off">
|
||||
<input type="text" id="libSearch" data-i18n-placeholder="search_name" placeholder="搜索应用名称..." autocomplete="off">
|
||||
</div>
|
||||
<div class="sort-dropdown">
|
||||
<button class="sort-btn" id="sortBtn" onclick="toggleSort(event)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M6 12h12M10 18h4"/></svg>
|
||||
<span id="sortLabel">默认排序</span>
|
||||
<span id="sortLabel" data-i18n="sort_default">默认排序</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 9l6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div class="sort-menu" id="sortMenu">
|
||||
<div class="sort-item active" data-sort="" onclick="setSort('')">默认排序 <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="rating" onclick="setSort('rating')">评分最高 <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="downloads" onclick="setSort('downloads')">下载最多 <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="newest" onclick="setSort('newest')">最新发布 <span class="check">✓</span></div>
|
||||
<div class="sort-item active" data-sort="" onclick="setSort('')"><span data-i18n="sort_default">默认排序</span> <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="rating" onclick="setSort('rating')"><span data-i18n="sort_rating">评分最高</span> <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="downloads" onclick="setSort('downloads')"><span data-i18n="sort_downloads">下载最多</span> <span class="check">✓</span></div>
|
||||
<div class="sort-item" data-sort="newest" onclick="setSort('newest')"><span data-i18n="sort_newest">最新发布</span> <span class="check">✓</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -499,36 +548,36 @@ img { max-width: 100%; display: block; }
|
||||
<div class="footer-inner">
|
||||
<div class="footer-top">
|
||||
<div class="footer-brand">
|
||||
<div class="logo"><span class="logo-mark">✚</span><span>应用商店</span></div>
|
||||
<p>发现、下载并体验海量优质应用与游戏。我们致力于为用户提供安全、丰富的应用生态。</p>
|
||||
<div class="logo"><span class="logo-mark">✚</span><span data-i18n="logo_name">应用商店</span></div>
|
||||
<p data-i18n="footer_desc">发现、下载并体验海量优质应用与游戏。我们致力于为用户提供安全、丰富的应用生态。</p>
|
||||
</div>
|
||||
<div class="footer-cols">
|
||||
<div class="footer-col">
|
||||
<h5>探索</h5>
|
||||
<a href="#" onclick="navTo(event,'home')">首页</a>
|
||||
<a href="#" onclick="navTo(event,'discover')">发现</a>
|
||||
<a href="#" onclick="navTo(event,'ranking')">排行榜</a>
|
||||
<a href="#" onclick="navTo(event,'new')">新上架</a>
|
||||
<h5 data-i18n="footer_explore">探索</h5>
|
||||
<a href="#" onclick="navTo(event,'home')" data-i18n="nav_home">首页</a>
|
||||
<a href="#" onclick="navTo(event,'discover')" data-i18n="nav_discover">发现</a>
|
||||
<a href="#" onclick="navTo(event,'ranking')" data-i18n="nav_ranking">排行榜</a>
|
||||
<a href="#" onclick="navTo(event,'new')" data-i18n="nav_new">新上架</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h5>关于</h5>
|
||||
<a href="#">关于我们</a>
|
||||
<a href="#">隐私政策</a>
|
||||
<a href="#">服务条款</a>
|
||||
<a href="#">联系方式</a>
|
||||
<h5 data-i18n="footer_about">关于</h5>
|
||||
<a href="#" data-i18n="footer_about_us">关于我们</a>
|
||||
<a href="#" data-i18n="footer_privacy">隐私政策</a>
|
||||
<a href="#" data-i18n="footer_terms">服务条款</a>
|
||||
<a href="#" data-i18n="footer_contact">联系方式</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h5>开发者</h5>
|
||||
<a href="#">开发者中心</a>
|
||||
<a href="#">上架指南</a>
|
||||
<a href="#">API 文档</a>
|
||||
<a href="#">合作申请</a>
|
||||
<h5 data-i18n="footer_dev">开发者</h5>
|
||||
<a href="#" data-i18n="footer_dev_center">开发者中心</a>
|
||||
<a href="#" data-i18n="footer_listing_guide">上架指南</a>
|
||||
<a href="#" data-i18n="footer_api_docs">API 文档</a>
|
||||
<a href="#" data-i18n="footer_cooperation">合作申请</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<span>© 2026 应用商店. 保留所有权利.</span>
|
||||
<span>仅供学习展示用途</span>
|
||||
<span data-i18n="footer_copyright">© 2026 应用商店. 保留所有权利.</span>
|
||||
<span data-i18n="footer_disclaimer">仅供学习展示用途</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -536,7 +585,7 @@ img { max-width: 100%; display: block; }
|
||||
<!-- ======================= MODAL ======================= -->
|
||||
<div class="modal-backdrop" id="modalBackdrop" onclick="closeModalOnBackdrop(event)">
|
||||
<div class="modal" id="appModal">
|
||||
<button class="modal-close" onclick="closeModal()" aria-label="关闭">×</button>
|
||||
<button class="modal-close" onclick="closeModal()" aria-label="关闭" data-i18n-aria="modal_close">×</button>
|
||||
<div class="modal-body" id="modalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -546,6 +595,253 @@ img { max-width: 100%; display: block; }
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
/* =========================================================================
|
||||
i18n 国际化引擎
|
||||
========================================================================= */
|
||||
var I18N_LOCALES = {
|
||||
'zh-CN': {
|
||||
logo_name: '应用商店',
|
||||
nav_home: '首页',
|
||||
nav_discover: '发现',
|
||||
nav_ranking: '排行榜',
|
||||
nav_new: '新上架',
|
||||
search_all: '搜索应用、游戏...',
|
||||
search_name: '搜索应用名称...',
|
||||
hero_eyebrow: '全新体验 · 精选推荐',
|
||||
hero_title: '发现精彩应用',
|
||||
hero_desc: '探索海量优质应用与游戏,从效率工具到娱乐大作,总有一款适合你。',
|
||||
hero_cta: '立即探索',
|
||||
cat_all: '全部',
|
||||
featured_title: '精选推荐',
|
||||
featured_sub: '编辑团队为你精心挑选',
|
||||
featured_tag: '编辑精选',
|
||||
featured_recommended: '推荐',
|
||||
see_all: '查看全部',
|
||||
view_detail: '查看详情',
|
||||
new_title: '新上架',
|
||||
new_sub: '最新加入的应用',
|
||||
library_title: '全部应用',
|
||||
library_sub: '浏览所有应用',
|
||||
sort_default: '默认排序',
|
||||
sort_rating: '评分最高',
|
||||
sort_downloads: '下载最多',
|
||||
sort_newest: '最新发布',
|
||||
download: '下载',
|
||||
downloads_label: '下载量',
|
||||
reviews_label: '评分数',
|
||||
size_label: '大小',
|
||||
rating_count: '评分',
|
||||
app_intro: '应用介绍',
|
||||
app_tags: '标签',
|
||||
app_features: '功能特性',
|
||||
app_screenshots: '应用截图',
|
||||
version_label: '版本:',
|
||||
update_label: '更新日期:',
|
||||
platform_label: '平台:',
|
||||
category_label: '分类:',
|
||||
empty_featured: '暂无精选应用',
|
||||
empty_new: '暂无新应用',
|
||||
empty_apps: '没有找到匹配的应用',
|
||||
empty_app_detail: '应用信息不存在',
|
||||
modal_close: '关闭',
|
||||
toast_downloading: '开始下载「',
|
||||
toast_downloading_end: '」',
|
||||
footer_desc: '发现、下载并体验海量优质应用与游戏。我们致力于为用户提供安全、丰富的应用生态。',
|
||||
footer_explore: '探索',
|
||||
footer_about: '关于',
|
||||
footer_about_us: '关于我们',
|
||||
footer_privacy: '隐私政策',
|
||||
footer_terms: '服务条款',
|
||||
footer_contact: '联系方式',
|
||||
footer_dev: '开发者',
|
||||
footer_dev_center: '开发者中心',
|
||||
footer_listing_guide: '上架指南',
|
||||
footer_api_docs: 'API 文档',
|
||||
footer_cooperation: '合作申请',
|
||||
footer_copyright: '\u00a9 2026 应用商店. 保留所有权利.',
|
||||
footer_disclaimer: '仅供学习展示用途'
|
||||
},
|
||||
'zh-TW': {
|
||||
logo_name: '應用商店',
|
||||
nav_home: '首頁',
|
||||
nav_discover: '發現',
|
||||
nav_ranking: '排行榜',
|
||||
nav_new: '新上架',
|
||||
search_all: '搜尋應用、遊戲...',
|
||||
search_name: '搜尋應用名稱...',
|
||||
hero_eyebrow: '全新體驗 · 精選推薦',
|
||||
hero_title: '發現精彩應用',
|
||||
hero_desc: '探索海量優質應用與遊戲,從效率工具到娛樂大作,總有一款適合你。',
|
||||
hero_cta: '立即探索',
|
||||
cat_all: '全部',
|
||||
featured_title: '精選推薦',
|
||||
featured_sub: '編輯團隊為你精心挑選',
|
||||
featured_tag: '編輯精選',
|
||||
featured_recommended: '推薦',
|
||||
see_all: '查看全部',
|
||||
view_detail: '查看详情',
|
||||
new_title: '新上架',
|
||||
new_sub: '最新加入的應用',
|
||||
library_title: '全部應用',
|
||||
library_sub: '瀏覽所有應用',
|
||||
sort_default: '預設排序',
|
||||
sort_rating: '評分最高',
|
||||
sort_downloads: '下載最多',
|
||||
sort_newest: '最新發佈',
|
||||
download: '下載',
|
||||
downloads_label: '下載量',
|
||||
reviews_label: '評分數',
|
||||
size_label: '大小',
|
||||
rating_count: '評分',
|
||||
app_intro: '應用介紹',
|
||||
app_tags: '標籤',
|
||||
app_features: '功能特性',
|
||||
app_screenshots: '應用截圖',
|
||||
version_label: '版本:',
|
||||
update_label: '更新日期:',
|
||||
platform_label: '平台:',
|
||||
category_label: '分類:',
|
||||
empty_featured: '暫無精選應用',
|
||||
empty_new: '暫無新應用',
|
||||
empty_apps: '沒有找到匹配的應用',
|
||||
empty_app_detail: '應用資訊不存在',
|
||||
modal_close: '關閉',
|
||||
toast_downloading: '開始下載「',
|
||||
toast_downloading_end: '」',
|
||||
footer_desc: '發現、下載並體驗海量優質應用與遊戲。我們致力於為使用者提供安全、豐富的應用生態。',
|
||||
footer_explore: '探索',
|
||||
footer_about: '關於',
|
||||
footer_about_us: '關於我們',
|
||||
footer_privacy: '隱私政策',
|
||||
footer_terms: '服務條款',
|
||||
footer聯絡: '聯絡方式',
|
||||
footer_dev: '開發者',
|
||||
footer_dev_center: '開發者中心',
|
||||
footer_listing_guide: '上架指南',
|
||||
footer_api_docs: 'API 文件',
|
||||
footer_cooperation: '合作申請',
|
||||
footer_copyright: '\u00a9 2026 應用商店. 保留所有權利.',
|
||||
footer_disclaimer: '僅供學習展示用途'
|
||||
},
|
||||
'en': {
|
||||
logo_name: 'App Store',
|
||||
nav_home: 'Home',
|
||||
nav_discover: 'Discover',
|
||||
nav_ranking: 'Rankings',
|
||||
nav_new: 'New',
|
||||
search_all: 'Search apps, games...',
|
||||
search_name: 'Search app name...',
|
||||
hero_eyebrow: 'All-New Experience · Curated Picks',
|
||||
hero_title: 'Discover Amazing Apps',
|
||||
hero_desc: 'Explore a vast collection of quality apps and games, from productivity tools to entertainment hits — there\'s something for everyone.',
|
||||
hero_cta: 'Explore Now',
|
||||
cat_all: 'All',
|
||||
featured_title: 'Featured',
|
||||
featured_sub: 'Handpicked by our editorial team',
|
||||
featured_tag: 'Editor\'s Choice',
|
||||
featured_recommended: 'Recommended',
|
||||
see_all: 'See All',
|
||||
view_detail: 'View Details',
|
||||
new_title: 'New Arrivals',
|
||||
new_sub: 'Recently added apps',
|
||||
library_title: 'All Apps',
|
||||
library_sub: 'Browse all apps',
|
||||
sort_default: 'Default',
|
||||
sort_rating: 'Top Rated',
|
||||
sort_downloads: 'Most Downloaded',
|
||||
sort_newest: 'Newest',
|
||||
download: 'Get',
|
||||
downloads_label: 'Downloads',
|
||||
reviews_label: 'Ratings',
|
||||
size_label: 'Size',
|
||||
rating_count: 'ratings',
|
||||
app_intro: 'App Description',
|
||||
app_tags: 'Tags',
|
||||
app_features: 'Features',
|
||||
app_screenshots: 'Screenshots',
|
||||
version_label: 'Version: ',
|
||||
update_label: 'Updated: ',
|
||||
platform_label: 'Platform: ',
|
||||
category_label: 'Category: ',
|
||||
empty_featured: 'No featured apps',
|
||||
empty_new: 'No new apps',
|
||||
empty_apps: 'No matching apps found',
|
||||
empty_app_detail: 'App information not available',
|
||||
modal_close: 'Close',
|
||||
toast_downloading: 'Downloading "',
|
||||
toast_downloading_end: '"',
|
||||
footer_desc: 'Discover, download, and enjoy a vast collection of quality apps and games. We are dedicated to providing a safe and rich app ecosystem.',
|
||||
footer_explore: 'Explore',
|
||||
footer_about: 'About',
|
||||
footer_about_us: 'About Us',
|
||||
footer_privacy: 'Privacy Policy',
|
||||
footer_terms: 'Terms of Service',
|
||||
footer_contact: 'Contact',
|
||||
footer_dev: 'Developers',
|
||||
footer_dev_center: 'Developer Center',
|
||||
footer_listing_guide: 'Listing Guide',
|
||||
footer_api_docs: 'API Docs',
|
||||
footer_cooperation: 'Partnerships',
|
||||
footer_copyright: '\u00a9 2026 App Store. All rights reserved.',
|
||||
footer_disclaimer: 'For demonstration purposes only'
|
||||
}
|
||||
};
|
||||
|
||||
var LANG_LABELS = {'zh-CN': '中文', 'zh-TW': '繁中', 'en': 'EN'};
|
||||
|
||||
var currentLang = (function(){
|
||||
try {
|
||||
var u = new URLSearchParams(location.search).get('lang');
|
||||
if (u && I18N_LOCALES[u]) return u;
|
||||
} catch(e) {}
|
||||
try { var s = localStorage.getItem('appstore_lang'); if (s && I18N_LOCALES[s]) return s; } catch(e) {}
|
||||
var b = navigator.language || navigator.userLanguage || '';
|
||||
if (b.startsWith('zh-TW') || b.startsWith('zh-Hant')) return 'zh-TW';
|
||||
if (b.startsWith('zh')) return 'zh-CN';
|
||||
return 'en';
|
||||
})();
|
||||
|
||||
function t(key) {
|
||||
return (I18N_LOCALES[currentLang] && I18N_LOCALES[currentLang][key]) || (I18N_LOCALES['zh-CN'][key]) || key;
|
||||
}
|
||||
|
||||
function applyI18n() {
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
var key = el.getAttribute('data-i18n');
|
||||
el.textContent = t(key);
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el) {
|
||||
var key = el.getAttribute('data-i18n-placeholder');
|
||||
el.placeholder = t(key);
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-aria]').forEach(function(el) {
|
||||
var key = el.getAttribute('data-i18n-aria');
|
||||
el.setAttribute('aria-label', t(key));
|
||||
});
|
||||
document.documentElement.lang = currentLang === 'zh-TW' ? 'zh-TW' : currentLang === 'zh-CN' ? 'zh-CN' : 'en';
|
||||
document.getElementById('langLabel').textContent = LANG_LABELS[currentLang] || currentLang;
|
||||
document.querySelectorAll('.lang-option').forEach(function(opt) {
|
||||
opt.classList.toggle('active', opt.getAttribute('data-lang') === currentLang);
|
||||
});
|
||||
}
|
||||
|
||||
function switchLang(lang) {
|
||||
if (!I18N_LOCALES[lang]) return;
|
||||
currentLang = lang;
|
||||
try { localStorage.setItem('appstore_lang', lang); } catch(e) {}
|
||||
applyI18n();
|
||||
if (typeof onLangChange === 'function') onLangChange(lang);
|
||||
document.getElementById('langDropdown').classList.remove('open');
|
||||
}
|
||||
|
||||
function toggleLangDropdown() {
|
||||
document.getElementById('langDropdown').classList.toggle('open');
|
||||
}
|
||||
document.addEventListener('click', function(e) {
|
||||
var sw = document.getElementById('langSwitcher');
|
||||
if (sw && !sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open');
|
||||
});
|
||||
|
||||
/* =========================================================================
|
||||
配置 & 工具函数
|
||||
========================================================================= */
|
||||
@@ -555,15 +851,15 @@ var PAGE_SIZE = 12;
|
||||
/* 状态 */
|
||||
var state = {
|
||||
categories: [],
|
||||
currentCat: '', // 当前分类 id('' 表示全部)
|
||||
currentCat: '',
|
||||
sort: '',
|
||||
keyword: '',
|
||||
page: 1,
|
||||
total: 0,
|
||||
cache: {} // app id -> app detail cache
|
||||
cache: {}
|
||||
};
|
||||
|
||||
/* ---- 工具:格式化大数字 56000000 -> "5600万" ---- */
|
||||
/* ---- 工具:格式化大数字 ---- */
|
||||
function formatNum(n) {
|
||||
if (n == null || isNaN(n)) return '0';
|
||||
n = Number(n);
|
||||
@@ -572,17 +868,23 @@ function formatNum(n) {
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
function formatNumI18n(n) {
|
||||
if (n == null || isNaN(n)) return '0';
|
||||
n = Number(n);
|
||||
if (currentLang === 'en') {
|
||||
if (n >= 1000000000) return (n / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
|
||||
if (n >= 1000000) return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
||||
if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
|
||||
return n.toString();
|
||||
}
|
||||
if (n >= 100000000) return (n / 100000000).toFixed(1).replace(/\.0$/, '') + '亿';
|
||||
if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + '万';
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
/* ---- 工具:星级 HTML ---- */
|
||||
function starsHtml(rating) {
|
||||
rating = parseFloat(rating) || 0;
|
||||
var full = Math.floor(rating);
|
||||
var half = (rating - full) >= 0.5 ? 1 : 0;
|
||||
var empty = 5 - full - half;
|
||||
var s = '';
|
||||
for (var i = 0; i < full; i++) s += '\u2605';
|
||||
if (half) s += '\u2605'; // 简化用实心星表示半星区域
|
||||
for (var j = 0; j < empty; j++) s += '\u2606';
|
||||
// 全实心更美观,这里全部用实心 + 透明度控制空星
|
||||
var html = '';
|
||||
for (var k = 0; k < 5; k++) {
|
||||
var fill = Math.max(0, Math.min(1, rating - k));
|
||||
@@ -618,7 +920,6 @@ function gradFromName(name) {
|
||||
}
|
||||
function initials(name) {
|
||||
name = name || '?';
|
||||
// 取首字符(兼容中文/英文)
|
||||
return name.charAt(0);
|
||||
}
|
||||
|
||||
@@ -637,7 +938,7 @@ function isIconURL(icon) {
|
||||
return v.indexOf('/uploads/') === 0 || v.indexOf('http://') === 0 || v.indexOf('https://') === 0;
|
||||
}
|
||||
|
||||
/* ---- 工具:生成应用图标 HTML(有 URL 用真实图片,否则渐变占位) ---- */
|
||||
/* ---- 工具:生成应用图标 HTML ---- */
|
||||
function appIconHTML(app, cls) {
|
||||
var grad = gradFromName(app.name);
|
||||
var clsAttr = 'app-icon' + (cls ? ' ' + cls : '');
|
||||
@@ -672,7 +973,7 @@ function showToast(msg) {
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
Mock 数据(当后端 API 不可用时自动回退使用)
|
||||
Mock 数据
|
||||
========================================================================= */
|
||||
var MOCK = (function () {
|
||||
var catDefs = [
|
||||
@@ -687,6 +988,36 @@ var MOCK = (function () {
|
||||
{ id: 9, name: '财务', icon: '\ud83d\udcb0', sort: 9 }
|
||||
];
|
||||
|
||||
var catDefsEN = [
|
||||
{ id: 1, name: 'Recommended', icon: '\u2b50', sort: 1 },
|
||||
{ id: 2, name: 'Games', icon: '\ud83c\udfae', sort: 2 },
|
||||
{ id: 3, name: 'Productivity', icon: '\u26a1', sort: 3 },
|
||||
{ id: 4, name: 'Social', icon: '\ud83d\udc65', sort: 4 },
|
||||
{ id: 5, name: 'Tools', icon: '\ud83d\udee0', sort: 5 },
|
||||
{ id: 6, name: 'Media', icon: '\ud83c\udfa7', sort: 6 },
|
||||
{ id: 7, name: 'Education', icon: '\ud83d\udcda', sort: 7 },
|
||||
{ id: 8, name: 'Lifestyle', icon: '\ud83c\udfe1', sort: 8 },
|
||||
{ id: 9, name: 'Finance', icon: '\ud83d\udcb0', sort: 9 }
|
||||
];
|
||||
|
||||
var catDefsTW = [
|
||||
{ id: 1, name: '推薦', icon: '\u2b50', sort: 1 },
|
||||
{ id: 2, name: '遊戲', icon: '\ud83c\udfae', sort: 2 },
|
||||
{ id: 3, name: '效率', icon: '\u26a1', sort: 3 },
|
||||
{ id: 4, name: '社交', icon: '\ud83d\udc65', sort: 4 },
|
||||
{ id: 5, name: '工具', icon: '\ud83d\udee0', sort: 5 },
|
||||
{ id: 6, name: '影音', icon: '\ud83c\udfa7', sort: 6 },
|
||||
{ id: 7, name: '教育', icon: '\ud83d\udcda', sort: 7 },
|
||||
{ id: 8, name: '生活', icon: '\ud83c\udfe1', sort: 8 },
|
||||
{ id: 9, name: '財務', icon: '\ud83d\udcb0', sort: 9 }
|
||||
];
|
||||
|
||||
function getLocalizedCats() {
|
||||
if (currentLang === 'en') return catDefsEN;
|
||||
if (currentLang === 'zh-TW') return catDefsTW;
|
||||
return catDefs;
|
||||
}
|
||||
|
||||
var appDefs = [
|
||||
{ name: '极光笔记', dev: 'Aurora Studio', cat: 3, desc: '一款支持 Markdown 与思维导图的智能笔记应用,让灵感随手记录。', tags: ['笔记','Markdown','同步'], features: ['实时云端同步','支持思维导图','富文本与双链','端到端加密'], rating: 4.9, reviews: 128340, downloads: 56000000, size: '85 MB', ver: '3.2.1', date: '2026-06-20', plat: 'iOS / Android', price: '免费' },
|
||||
{ name: '星海航海家', dev: 'Cosmos Games', cat: 2, desc: '在浩瀚星海中探索未知星球,建立你的星际舰队,征服宇宙。', tags: ['冒险','策略','科幻'], features: ['开放宇宙地图','多人实时对战','超过 200 种舰船','精美 3D 画面'], rating: 4.7, reviews: 89021, downloads: 23000000, size: '1.2 GB', ver: '2.0.5', date: '2026-07-01', plat: 'iOS / Android', price: '免费' },
|
||||
@@ -715,7 +1046,8 @@ var MOCK = (function () {
|
||||
];
|
||||
|
||||
var apps = appDefs.map(function (a, i) {
|
||||
var catName = (catDefs.find(function (c) { return c.id === a.cat; }) || {}).name || '应用';
|
||||
var localizedCats = getLocalizedCats();
|
||||
var catName = (localizedCats.find(function (c) { return c.id === a.cat; }) || {}).name || (getLocalizedCats()[0] || {}).name || '应用';
|
||||
return {
|
||||
id: i + 1,
|
||||
name: a.name,
|
||||
@@ -733,7 +1065,7 @@ var MOCK = (function () {
|
||||
platform: a.plat,
|
||||
price: a.price,
|
||||
tags: JSON.stringify(a.tags),
|
||||
screenshots: JSON.stringify([a.name + ' 截图 1', a.name + ' 截图 2', a.name + ' 截图 3']),
|
||||
screenshots: JSON.stringify([a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 1', a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 2', a.name + ' ' + (currentLang === 'en' ? 'Screenshot' : '截图') + ' 3']),
|
||||
features: JSON.stringify(a.features),
|
||||
is_featured: i < 5,
|
||||
is_new: i >= (appDefs.length - 6),
|
||||
@@ -745,7 +1077,7 @@ var MOCK = (function () {
|
||||
function ok(data) { return { code: 0, message: 'ok', data: data }; }
|
||||
|
||||
return {
|
||||
categories: function () { return ok(catDefs.slice()); },
|
||||
categories: function () { return ok(getLocalizedCats().slice()); },
|
||||
featured: function (limit) {
|
||||
limit = limit || 8;
|
||||
return ok(apps.filter(function (a) { return a.is_featured; }).slice(0, limit));
|
||||
@@ -778,7 +1110,7 @@ var MOCK = (function () {
|
||||
},
|
||||
home: function () {
|
||||
return ok({
|
||||
categories: catDefs.slice(),
|
||||
categories: getLocalizedCats().slice(),
|
||||
featured: apps.filter(function (a) { return a.is_featured; }).slice(0, 5),
|
||||
new: apps.filter(function (a) { return a.is_new; }).sort(function (x, y) {
|
||||
return new Date(y.update_date) - new Date(x.update_date);
|
||||
@@ -789,7 +1121,7 @@ var MOCK = (function () {
|
||||
})();
|
||||
|
||||
/* =========================================================================
|
||||
API 调用层(先尝试真实后端,失败则回退 mock)
|
||||
API 调用层
|
||||
========================================================================= */
|
||||
function apiGet(path, useMockFn) {
|
||||
var url = API_BASE + path;
|
||||
@@ -803,8 +1135,7 @@ function apiGet(path, useMockFn) {
|
||||
return res;
|
||||
})
|
||||
.catch(function (err) {
|
||||
// 回退到 mock
|
||||
console.warn('[API] 请求失败,使用本地数据:', url, err.message);
|
||||
console.warn('[API] Request failed, using local data:', url, err.message);
|
||||
return useMockFn();
|
||||
});
|
||||
}
|
||||
@@ -838,7 +1169,7 @@ function fetchDetail(id) {
|
||||
function renderCategories(cats) {
|
||||
state.categories = cats;
|
||||
var bar = document.getElementById('catBar');
|
||||
var html = '<button class="cat-pill active" data-cat="" onclick="selectCat(\'\')"><span class="cat-emoji">\u2728</span>全部</button>';
|
||||
var html = '<button class="cat-pill active" data-cat="" onclick="selectCat(\'\')"><span class="cat-emoji">\u2728</span>' + esc(t('cat_all')) + '</button>';
|
||||
cats.forEach(function (c) {
|
||||
html += '<button class="cat-pill" data-cat="' + c.id + '" onclick="selectCat(' + c.id + ')">'
|
||||
+ '<span class="cat-emoji">' + esc(c.icon || '\u25cf') + '</span>' + esc(c.name) + '</button>';
|
||||
@@ -849,13 +1180,11 @@ function renderCategories(cats) {
|
||||
function selectCat(catId) {
|
||||
state.currentCat = catId;
|
||||
state.page = 1;
|
||||
// 更新激活态
|
||||
var pills = document.querySelectorAll('#catBar .cat-pill');
|
||||
pills.forEach(function (p) {
|
||||
p.classList.toggle('active', String(p.getAttribute('data-cat')) === String(catId));
|
||||
});
|
||||
loadLibrary();
|
||||
// 滚动到应用库
|
||||
var lib = document.getElementById('library');
|
||||
if (lib) lib.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
@@ -865,33 +1194,31 @@ function selectCat(catId) {
|
||||
========================================================================= */
|
||||
function renderFeatured(list) {
|
||||
var grid = document.getElementById('featuredGrid');
|
||||
if (!list || !list.length) { grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;"><p>暂无精选应用</p></div>'; return; }
|
||||
if (!list || !list.length) { grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;"><p>' + esc(t('empty_featured')) + '</p></div>'; return; }
|
||||
var html = '';
|
||||
list.forEach(function (app, idx) {
|
||||
var grad = gradFromName(app.name);
|
||||
if (idx === 0) {
|
||||
// 大卡片
|
||||
html += '<div class="feature-big fade-in" onclick="openApp(' + app.id + ')">'
|
||||
+ '<div class="feature-bg" style="background:' + grad + ';"></div>'
|
||||
+ '<div class="feature-content">'
|
||||
+ '<span class="ftag">编辑精选</span>'
|
||||
+ '<span class="ftag">' + esc(t('featured_tag')) + '</span>'
|
||||
+ '<h3>' + esc(app.name) + '</h3>'
|
||||
+ '<p class="fdesc">' + esc(app.description) + '</p>'
|
||||
+ '<div class="feature-rating"><span class="stars">' + '\u2605'.repeat(5) + '</span> ' + app.rating + ' · ' + formatNum(app.downloads) + ' 下载</div>'
|
||||
+ '<button class="feature-btn" onclick="event.stopPropagation();openApp(' + app.id + ')">查看详情</button>'
|
||||
+ '<div class="feature-rating"><span class="stars">' + '\u2605'.repeat(5) + '</span> ' + app.rating + ' · ' + formatNumI18n(app.downloads) + ' ' + esc(t('downloads_label')) + '</div>'
|
||||
+ '<button class="feature-btn" onclick="event.stopPropagation();openApp(' + app.id + ')">' + esc(t('view_detail')) + '</button>'
|
||||
+ '</div></div>';
|
||||
} else {
|
||||
html += '<div class="feature-card fade-in" onclick="openApp(' + app.id + ')">'
|
||||
+ '<div class="feature-bg" style="background:' + grad + ';"></div>'
|
||||
+ '<div class="feature-content">'
|
||||
+ '<span class="ftag">推荐</span>'
|
||||
+ '<span class="ftag">' + esc(t('featured_recommended')) + '</span>'
|
||||
+ '<h3>' + esc(app.name) + '</h3>'
|
||||
+ '<p class="fdesc">' + esc(app.category_name) + '</p>'
|
||||
+ '<div class="feature-rating"><span class="stars">' + '\u2605'.repeat(5) + '</span> ' + app.rating + '</div>'
|
||||
+ '</div></div>';
|
||||
}
|
||||
});
|
||||
// 补齐到至少 3 个(如果不足)
|
||||
grid.innerHTML = html;
|
||||
observeFadeIn();
|
||||
}
|
||||
@@ -901,7 +1228,7 @@ function renderFeatured(list) {
|
||||
========================================================================= */
|
||||
function renderNew(list) {
|
||||
var wrap = document.getElementById('newScroll');
|
||||
if (!list || !list.length) { wrap.innerHTML = '<div class="empty-state"><p>暂无新应用</p></div>'; return; }
|
||||
if (!list || !list.length) { wrap.innerHTML = '<div class="empty-state"><p>' + esc(t('empty_new')) + '</p></div>'; return; }
|
||||
var html = '';
|
||||
list.forEach(function (app) {
|
||||
var grad = gradFromName(app.name);
|
||||
@@ -929,7 +1256,7 @@ function renderAppGrid(list) {
|
||||
if (!list || !list.length) {
|
||||
grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;">'
|
||||
+ '<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>'
|
||||
+ '<p>没有找到匹配的应用</p></div>';
|
||||
+ '<p>' + esc(t('empty_apps')) + '</p></div>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
@@ -943,7 +1270,7 @@ function renderAppGrid(list) {
|
||||
+ '<p class="ac-desc">' + esc(app.description) + '</p>'
|
||||
+ '<div class="ac-foot">'
|
||||
+ '<span class="ac-rate"><span class="stars">' + '\u2605' + '</span>' + app.rating + '</span>'
|
||||
+ '<button class="ac-dl" onclick="event.stopPropagation();openApp(' + app.id + ')">下载</button>'
|
||||
+ '<button class="ac-dl" onclick="event.stopPropagation();openApp(' + app.id + ')">' + esc(t('download')) + '</button>'
|
||||
+ '</div></div>';
|
||||
});
|
||||
grid.innerHTML = html;
|
||||
@@ -1019,8 +1346,8 @@ function toggleSort(e) {
|
||||
function setSort(sort) {
|
||||
state.sort = sort;
|
||||
state.page = 1;
|
||||
var labels = { '': '默认排序', 'rating': '评分最高', 'downloads': '下载最多', 'newest': '最新发布' };
|
||||
document.getElementById('sortLabel').textContent = labels[sort] || '默认排序';
|
||||
var labelKey = { '': 'sort_default', 'rating': 'sort_rating', 'downloads': 'sort_downloads', 'newest': 'sort_newest' };
|
||||
document.getElementById('sortLabel').textContent = t(labelKey[sort] || 'sort_default');
|
||||
document.querySelectorAll('#sortMenu .sort-item').forEach(function (it) {
|
||||
it.classList.toggle('active', it.getAttribute('data-sort') === sort);
|
||||
});
|
||||
@@ -1068,7 +1395,6 @@ var debouncedSearch = debounce(function (kw) {
|
||||
========================================================================= */
|
||||
function openApp(id) {
|
||||
var body = document.getElementById('modalBody');
|
||||
// 先展示骨架
|
||||
body.innerHTML = '<div style="padding-top:24px;">'
|
||||
+ '<div style="display:flex;gap:20px;align-items:center;">'
|
||||
+ '<div class="skeleton" style="width:88px;height:88px;border-radius:20px;"></div>'
|
||||
@@ -1077,11 +1403,10 @@ function openApp(id) {
|
||||
document.getElementById('modalBackdrop').classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 缓存命中直接渲染
|
||||
if (state.cache[id]) { renderModal(state.cache[id]); return; }
|
||||
|
||||
fetchDetail(id).then(function (app) {
|
||||
if (!app) { body.innerHTML = '<div class="empty-state"><p>应用信息不存在</p></div>'; return; }
|
||||
if (!app) { body.innerHTML = '<div class="empty-state"><p>' + esc(t('empty_app_detail')) + '</p></div>'; return; }
|
||||
state.cache[id] = app;
|
||||
renderModal(app);
|
||||
});
|
||||
@@ -1094,7 +1419,7 @@ function renderModal(app) {
|
||||
var features = parseJSON(app.features, []);
|
||||
var screenshots = parseJSON(app.screenshots, []);
|
||||
|
||||
var tagsHtml = tags.map(function (t) { return '<span class="tag-pill">' + esc(t) + '</span>'; }).join('');
|
||||
var tagsHtml = tags.map(function (tag) { return '<span class="tag-pill">' + esc(tag) + '</span>'; }).join('');
|
||||
var featHtml = features.map(function (f) { return '<li>' + esc(f) + '</li>'; }).join('');
|
||||
var shotsHtml = screenshots.map(function (s, i) {
|
||||
var sg = gradFromName(s + i);
|
||||
@@ -1108,31 +1433,31 @@ function renderModal(app) {
|
||||
+ '<h2>' + esc(app.name) + '</h2>'
|
||||
+ '<div class="mh-dev">' + esc(app.developer) + '</div>'
|
||||
+ '<div class="mh-meta">'
|
||||
+ '<div class="mh-rating"><span class="stars">' + starsHtml(app.rating) + '</span><span class="rnum">' + app.rating + '</span><span class="rcount">(' + formatNum(app.reviews) + ' 评分)</span></div>'
|
||||
+ '<span class="mh-price">' + esc(app.price || '免费') + '</span>'
|
||||
+ '<div class="mh-rating"><span class="stars">' + starsHtml(app.rating) + '</span><span class="rnum">' + app.rating + '</span><span class="rcount">(' + formatNumI18n(app.reviews) + ' ' + esc(t('rating_count')) + ')</span></div>'
|
||||
+ '<span class="mh-price">' + esc(app.price || t('download')) + '</span>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<button class="modal-dl-btn" onclick="downloadApp(' + app.id + ',\'' + esc(app.name).replace(/'/g, "\\'") + '\')">下载</button>'
|
||||
+ '<button class="modal-dl-btn" onclick="downloadApp(' + app.id + ',\'' + esc(app.name).replace(/'/g, "\\'") + '\')">' + esc(t('download')) + '</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="modal-stats">'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + formatNum(app.downloads) + '</div><div class="ms-label">下载量</div></div>'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + formatNum(app.reviews) + '</div><div class="ms-label">评分数</div></div>'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + esc(app.size || '-') + '</div><div class="ms-label">大小</div></div>'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + formatNumI18n(app.downloads) + '</div><div class="ms-label">' + esc(t('downloads_label')) + '</div></div>'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + formatNumI18n(app.reviews) + '</div><div class="ms-label">' + esc(t('reviews_label')) + '</div></div>'
|
||||
+ '<div class="modal-stat"><div class="ms-val">' + esc(app.size || '-') + '</div><div class="ms-label">' + esc(t('size_label')) + '</div></div>'
|
||||
+ '</div>'
|
||||
+ '<div class="modal-section"><h4>应用介绍</h4><p>' + esc(app.description) + '</p></div>'
|
||||
+ (tags.length ? '<div class="modal-section"><h4>标签</h4><div class="tag-row">' + tagsHtml + '</div></div>' : '')
|
||||
+ (features.length ? '<div class="modal-section"><h4>功能特性</h4><ul class="feature-list">' + featHtml + '</ul></div>' : '')
|
||||
+ (screenshots.length ? '<div class="modal-section"><h4>应用截图</h4><div class="shots-row">' + shotsHtml + '</div></div>' : '')
|
||||
+ '<div class="modal-section"><h4>' + esc(t('app_intro')) + '</h4><p>' + esc(app.description) + '</p></div>'
|
||||
+ (tags.length ? '<div class="modal-section"><h4>' + esc(t('app_tags')) + '</h4><div class="tag-row">' + tagsHtml + '</div></div>' : '')
|
||||
+ (features.length ? '<div class="modal-section"><h4>' + esc(t('app_features')) + '</h4><ul class="feature-list">' + featHtml + '</ul></div>' : '')
|
||||
+ (screenshots.length ? '<div class="modal-section"><h4>' + esc(t('app_screenshots')) + '</h4><div class="shots-row">' + shotsHtml + '</div></div>' : '')
|
||||
+ '<div class="modal-version">'
|
||||
+ '<span><b>版本:</b>' + esc(app.version || '-') + '</span>'
|
||||
+ '<span><b>更新日期:</b>' + esc(app.update_date || '-') + '</span>'
|
||||
+ '<span><b>平台:</b>' + esc(app.platform || '-') + '</span>'
|
||||
+ '<span><b>分类:</b>' + esc(app.category_name || '-') + '</span>'
|
||||
+ '<span><b>' + esc(t('version_label')) + '</b>' + esc(app.version || '-') + '</span>'
|
||||
+ '<span><b>' + esc(t('update_label')) + '</b>' + esc(app.update_date || '-') + '</span>'
|
||||
+ '<span><b>' + esc(t('platform_label')) + '</b>' + esc(app.platform || '-') + '</span>'
|
||||
+ '<span><b>' + esc(t('category_label')) + '</b>' + esc(app.category_name || '-') + '</span>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
function downloadApp(id, name) {
|
||||
showToast('\u5f00\u59cb\u4e0b\u8f7d\u300c' + name + '\u300d'); // 开始下载「name」
|
||||
showToast(t('toast_downloading') + name + t('toast_downloading_end'));
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
@@ -1183,7 +1508,7 @@ function navTo(e, key) {
|
||||
} else if (key === 'ranking') {
|
||||
state.sort = 'downloads';
|
||||
state.page = 1;
|
||||
document.getElementById('sortLabel').textContent = '下载最多';
|
||||
document.getElementById('sortLabel').textContent = t('sort_downloads');
|
||||
document.querySelectorAll('#sortMenu .sort-item').forEach(function (it) {
|
||||
it.classList.toggle('active', it.getAttribute('data-sort') === 'downloads');
|
||||
});
|
||||
@@ -1198,11 +1523,26 @@ function exploreNow(e) { if (e) e.preventDefault(); document.getElementById('lib
|
||||
function seeAllFeatured(e) { if (e) e.preventDefault(); document.getElementById('featuredSection').scrollIntoView({ behavior: 'smooth' }); }
|
||||
function seeAllNew(e) { if (e) e.preventDefault(); document.getElementById('newSection').scrollIntoView({ behavior: 'smooth' }); }
|
||||
|
||||
/* =========================================================================
|
||||
语言切换回调:重新渲染动态内容
|
||||
========================================================================= */
|
||||
var lastHomeData = null;
|
||||
function onLangChange() {
|
||||
applyI18n();
|
||||
loadLibrary();
|
||||
if (lastHomeData) {
|
||||
renderCategories(lastHomeData.categories || []);
|
||||
renderFeatured(lastHomeData.featured || []);
|
||||
renderNew(lastHomeData.new || []);
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
初始化
|
||||
========================================================================= */
|
||||
function init() {
|
||||
// 搜索框绑定
|
||||
applyI18n();
|
||||
|
||||
document.getElementById('headerSearch').addEventListener('input', function (e) {
|
||||
var kw = e.target.value;
|
||||
document.getElementById('libSearch').value = kw;
|
||||
@@ -1214,21 +1554,18 @@ function init() {
|
||||
debouncedSearch(kw);
|
||||
});
|
||||
|
||||
// 首次加载应用库
|
||||
loadLibrary();
|
||||
|
||||
// 加载首页数据(分类 + 精选 + 新上架)
|
||||
// 尝试 home 聚合接口,失败则分别请求
|
||||
apiGet('/home', function () { return MOCK.home(); }).then(function (res) {
|
||||
var d = res.data;
|
||||
lastHomeData = d;
|
||||
renderCategories(d.categories || []);
|
||||
renderFeatured(d.featured || []);
|
||||
renderNew(d.new || []);
|
||||
}).catch(function () {
|
||||
// 兜底分别加载
|
||||
fetchCategories().then(renderCategories);
|
||||
fetchFeatured(5).then(renderFeatured);
|
||||
fetchNew(4).then(renderNew);
|
||||
fetchCategories().then(function (cats) { lastHomeData = lastHomeData || {}; lastHomeData.categories = cats; renderCategories(cats); });
|
||||
fetchFeatured(5).then(function (feat) { lastHomeData = lastHomeData || {}; lastHomeData.featured = feat; renderFeatured(feat); });
|
||||
fetchNew(4).then(function (nw) { lastHomeData = lastHomeData || {}; lastHomeData.new = nw; renderNew(nw); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user