feat: 导航页SEO/GEO优化 + 手机端完全适配 + App候选码 + 业务系统板块
- SEO: 添加meta description/keywords/Open Graph/结构化数据/地理信息 - GEO: 添加geo.region/geo.position/ICBM等地理标记 - 手机端完全重写响应式: 咨询表单/导航卡片/hero区/标签全部适配 - 新增App候选码板块: 后台管理+导航页动态展示(支持录屏视频/下载链接) - 新增业务系统板块: 进销存/OA/财务/工单等,支持按年/按月/定制定价 - 后台新增App候选码管理页面(增删改查+上下架) - 后台新增业务系统管理页面(增删改查+上下架) - 导航页设置新增新板块标题配置 - 公开API: GET /api/v1/showcase/apps + /api/v1/showcase/systems
This commit is contained in:
223
web/dist/admin/index.html
vendored
223
web/dist/admin/index.html
vendored
@@ -276,6 +276,12 @@ tbody tr:last-child td{border-bottom:none}
|
||||
<a class="sidebar-link" onclick="navigate('navsettings')" data-page="navsettings">
|
||||
<span class="icon">📝</span> 导航页设置
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('appshowcase')" data-page="appshowcase">
|
||||
<span class="icon">📱</span> App候选码
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('bizsystems')" data-page="bizsystems">
|
||||
<span class="icon">💼</span> 业务系统
|
||||
</a>
|
||||
<a class="sidebar-link" onclick="navigate('settings')" data-page="settings">
|
||||
<span class="icon">⚙️</span> 系统设置
|
||||
</a>
|
||||
@@ -458,7 +464,7 @@ function navigate(page, params = {}) {
|
||||
document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active'));
|
||||
const link = document.querySelector(`[data-page="${page}"]`);
|
||||
if (link) link.classList.add('active');
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置', navsettings: '导航页设置' };
|
||||
const titles = { users: '用户管理', content: '内容管理', settings: '系统设置', sites: '站点导航', accesslog: '访问日志', webhook: 'Webhook配置', navsettings: '导航页设置', appshowcase: 'App候选码', bizsystems: '业务系统' };
|
||||
document.getElementById('pageTitle').textContent = titles[page] || page;
|
||||
if (page === 'users') renderUsers();
|
||||
else if (page === 'content') renderContent(params.table);
|
||||
@@ -467,6 +473,8 @@ function navigate(page, params = {}) {
|
||||
else if (page === 'accesslog') renderAccessLogs();
|
||||
else if (page === 'webhook') renderWebhook();
|
||||
else if (page === 'navsettings') renderNavSettings();
|
||||
else if (page === 'appshowcase') renderAppShowcase();
|
||||
else if (page === 'bizsystems') renderBizSystems();
|
||||
if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');
|
||||
}
|
||||
|
||||
@@ -555,7 +563,7 @@ function openUserModal(id) {
|
||||
</select>
|
||||
</div>` : ''}
|
||||
`;
|
||||
document.getElementById('modalSaveBtn').onclick = saveUser;
|
||||
_modalSaveFn = saveUser;
|
||||
showModal();
|
||||
}
|
||||
|
||||
@@ -1342,6 +1350,14 @@ async function renderNavSettings() {
|
||||
<div class="form-group"><label>音乐平台 - 标题</label><input type="text" id="ns_sec_music_title" value="${configs.nav_sec_music_title||''}" placeholder="音乐平台"></div>
|
||||
<div class="form-group"><label>音乐平台 - 副标题</label><input type="text" id="ns_sec_music_sub" value="${configs.nav_sec_music_sub||''}" placeholder="3 套风格"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>App候选码 - 标题</label><input type="text" id="ns_sec_appshow_title" value="${configs.nav_sec_appshow_title||''}" placeholder="App 候选码"></div>
|
||||
<div class="form-group"><label>App候选码 - 副标题</label><input type="text" id="ns_sec_appshow_sub" value="${configs.nav_sec_appshow_sub||''}" placeholder="精选应用"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>业务系统 - 标题</label><input type="text" id="ns_sec_bizsys_title" value="${configs.nav_sec_bizsys_title||''}" placeholder="业务系统"></div>
|
||||
<div class="form-group"><label>业务系统 - 副标题</label><input type="text" id="ns_sec_bizsys_sub" value="${configs.nav_sec_bizsys_sub||''}" placeholder="定制 / 年付"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card" style="margin-top:16px">
|
||||
@@ -1366,6 +1382,7 @@ async function saveNavSettings() {
|
||||
'sec_enterprise_title','sec_enterprise_sub','sec_shop_title','sec_shop_sub',
|
||||
'sec_food_title','sec_food_sub','sec_game_title','sec_game_sub',
|
||||
'sec_app_title','sec_app_sub','sec_music_title','sec_music_sub',
|
||||
'sec_appshow_title','sec_appshow_sub','sec_bizsys_title','sec_bizsys_sub',
|
||||
'consult_title','consult_desc','consult_success','footer_text','company_name'
|
||||
];
|
||||
const items = fields.map(f => ({
|
||||
@@ -1382,6 +1399,203 @@ async function saveNavSettings() {
|
||||
} catch (e) { toast('保存失败', 'error'); }
|
||||
}
|
||||
|
||||
// ===== App 候选码管理 =====
|
||||
let appShowPage = 1;
|
||||
async function renderAppShowcase(page) {
|
||||
if (page) appShowPage = page;
|
||||
const el = document.getElementById('content');
|
||||
el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
||||
try {
|
||||
const data = await api('/admin/showcase/apps?page=' + appShowPage + '&page_size=20');
|
||||
const d = data.data;
|
||||
el.innerHTML = `
|
||||
<div class="table-card">
|
||||
<div class="table-toolbar">
|
||||
<h4 style="font-size:15px">📱 App 候选码管理</h4>
|
||||
<button class="btn btn-primary btn-sm" onclick="openAppShowModal()">+ 新增App</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>分类</th><th>平台</th><th>版本</th><th>评分</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
${d.list.length===0?'<tr class="empty-row"><td colspan="8">暂无数据</td></tr>':d.list.map(a=>`
|
||||
<tr>
|
||||
<td>${a.id}</td>
|
||||
<td><strong>${a.name}</strong></td>
|
||||
<td>${a.category||'-'}</td>
|
||||
<td>${a.platform||'-'}</td>
|
||||
<td>${a.version||'-'}</td>
|
||||
<td>⭐${a.rating}</td>
|
||||
<td><span class="badge-status ${a.status===1?'badge-active':'badge-inactive'}">${a.status===1?'上架':'下架'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-outline btn-sm" onclick="openAppShowModal(${a.id})">编辑</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteAppShow(${a.id})">删除</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${renderPagination(d,'renderAppShowcase')}
|
||||
</div>`;
|
||||
} catch(e) { el.innerHTML = '<div class="loading">加载失败</div>'; }
|
||||
}
|
||||
|
||||
let appShowData = [];
|
||||
async function openAppShowModal(id) {
|
||||
if (!appShowData.length) { try { const r=await api('/admin/showcase/apps?page_size=100'); appShowData=r.data.list||[]; } catch(e){} }
|
||||
const item = id ? appShowData.find(a=>a.id===id) : null;
|
||||
document.getElementById('modalTitle').textContent = id ? '编辑App' : '新增App';
|
||||
document.getElementById('modalBody').innerHTML = `
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>App名称 *</label><input type="text" id="m_name" value="${item?.name||''}"></div>
|
||||
<div class="form-group"><label>分类</label><input type="text" id="m_category" value="${item?.category||''}" placeholder="工具/社交/教育/生活/娱乐"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>平台</label><input type="text" id="m_platform" value="${item?.platform||''}" placeholder="Android/iOS/跨平台"></div>
|
||||
<div class="form-group"><label>版本</label><input type="text" id="m_version" value="${item?.version||''}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>简介</label><textarea id="m_description" rows="2">${item?.description||''}</textarea></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>图标URL</label><input type="text" id="m_icon" value="${item?.icon||''}"></div>
|
||||
<div class="form-group"><label>下载链接</label><input type="text" id="m_download_url" value="${item?.download_url||''}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>录屏视频URL</label><input type="text" id="m_video_url" value="${item?.video_url||''}"></div>
|
||||
<div class="form-group"><label>包大小</label><input type="text" id="m_size" value="${item?.size||''}"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>评分</label><input type="number" id="m_rating" value="${item?.rating||5}" step="0.1" min="0" max="5"></div>
|
||||
<div class="form-group"><label>排序</label><input type="number" id="m_sort" value="${item?.sort||0}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>状态</label><select id="m_status"><option value="1" ${item?.status!==0?'selected':''}>上架</option><option value="0" ${item?.status===0?'selected':''}>下架</option></select></div>`;
|
||||
editingId = id;
|
||||
_modalSaveFn = saveAppShow;
|
||||
openModal();
|
||||
}
|
||||
|
||||
async function saveAppShow() {
|
||||
const body = {
|
||||
name: document.getElementById('m_name').value,
|
||||
category: document.getElementById('m_category').value,
|
||||
platform: document.getElementById('m_platform').value,
|
||||
version: document.getElementById('m_version').value,
|
||||
description: document.getElementById('m_description').value,
|
||||
icon: document.getElementById('m_icon').value,
|
||||
download_url: document.getElementById('m_download_url').value,
|
||||
video_url: document.getElementById('m_video_url').value,
|
||||
size: document.getElementById('m_size').value,
|
||||
rating: parseFloat(document.getElementById('m_rating').value)||5,
|
||||
sort: parseInt(document.getElementById('m_sort').value)||0,
|
||||
status: parseInt(document.getElementById('m_status').value),
|
||||
};
|
||||
try {
|
||||
if (editingId) {
|
||||
await api('/admin/showcase/apps/' + editingId, { method:'PUT', body:JSON.stringify(body) });
|
||||
} else {
|
||||
await api('/admin/showcase/apps', { method:'POST', body:JSON.stringify(body) });
|
||||
}
|
||||
closeModal(); toast('保存成功','success'); renderAppShowcase();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function deleteAppShow(id) {
|
||||
if (!confirm('确定删除?')) return;
|
||||
try { await api('/admin/showcase/apps/'+id,{method:'DELETE'}); toast('删除成功','success'); renderAppShowcase(); } catch(e){}
|
||||
}
|
||||
|
||||
// ===== 业务系统管理 =====
|
||||
let bizSysPage = 1;
|
||||
async function renderBizSystems(page) {
|
||||
if (page) bizSysPage = page;
|
||||
const el = document.getElementById('content');
|
||||
el.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
|
||||
try {
|
||||
const data = await api('/admin/showcase/systems?page=' + bizSysPage + '&page_size=20');
|
||||
const d = data.data;
|
||||
el.innerHTML = `
|
||||
<div class="table-card">
|
||||
<div class="table-toolbar">
|
||||
<h4 style="font-size:15px">💼 业务系统管理</h4>
|
||||
<button class="btn btn-primary btn-sm" onclick="openBizSysModal()">+ 新增系统</button>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>分类</th><th>价格类型</th><th>价格</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
${d.list.length===0?'<tr class="empty-row"><td colspan="7">暂无数据</td></tr>':d.list.map(s=>`
|
||||
<tr>
|
||||
<td>${s.id}</td>
|
||||
<td><strong>${s.name}</strong></td>
|
||||
<td>${s.category||'-'}</td>
|
||||
<td>${s.price_type||'-'}</td>
|
||||
<td>${s.price||'-'}</td>
|
||||
<td><span class="badge-status ${s.status===1?'badge-active':'badge-inactive'}">${s.status===1?'上线':'下线'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-outline btn-sm" onclick="openBizSysModal(${s.id})">编辑</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteBizSys(${s.id})">删除</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${renderPagination(d,'renderBizSystems')}
|
||||
</div>`;
|
||||
} catch(e) { el.innerHTML = '<div class="loading">加载失败</div>'; }
|
||||
}
|
||||
|
||||
let bizSysData = [];
|
||||
async function openBizSysModal(id) {
|
||||
if (!bizSysData.length) { try { const r=await api('/admin/showcase/systems?page_size=100'); bizSysData=r.data.list||[]; } catch(e){} }
|
||||
const item = id ? bizSysData.find(s=>s.id===id) : null;
|
||||
document.getElementById('modalTitle').textContent = id ? '编辑系统' : '新增系统';
|
||||
document.getElementById('modalBody').innerHTML = `
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>系统名称 *</label><input type="text" id="m_name" value="${item?.name||''}"></div>
|
||||
<div class="form-group"><label>分类</label><input type="text" id="m_category" value="${item?.category||''}" placeholder="进销存/OA/财务/工单/ERP/CRM"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>一句话介绍</label><input type="text" id="m_description" value="${item?.description||''}"></div>
|
||||
<div class="form-group"><label>详细说明</label><textarea id="m_detail" rows="3">${item?.detail||''}</textarea></div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>价格类型</label><select id="m_price_type"><option value="按年" ${item?.price_type==='按年'?'selected':''}>按年</option><option value="按月" ${item?.price_type==='按月'?'selected':''}>按月</option><option value="一次性" ${item?.price_type==='一次性'?'selected':''}>一次性</option><option value="定制" ${item?.price_type==='定制'?'selected':''}>定制开发</option></select></div>
|
||||
<div class="form-group"><label>价格描述</label><input type="text" id="m_price" value="${item?.price||''}" placeholder="如 3000元/年"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label>演示链接</label><input type="text" id="m_demo_url" value="${item?.demo_url||''}"></div>
|
||||
<div class="form-group"><label>排序</label><input type="number" id="m_sort" value="${item?.sort||0}"></div>
|
||||
</div>
|
||||
<div class="form-group"><label>状态</label><select id="m_status"><option value="1" ${item?.status!==0?'selected':''}>上线</option><option value="0" ${item?.status===0?'selected':''}>下线</option></select></div>`;
|
||||
editingId = id;
|
||||
_modalSaveFn = saveBizSys;
|
||||
openModal();
|
||||
}
|
||||
|
||||
async function saveBizSys() {
|
||||
const body = {
|
||||
name: document.getElementById('m_name').value,
|
||||
category: document.getElementById('m_category').value,
|
||||
description: document.getElementById('m_description').value,
|
||||
detail: document.getElementById('m_detail').value,
|
||||
price_type: document.getElementById('m_price_type').value,
|
||||
price: document.getElementById('m_price').value,
|
||||
demo_url: document.getElementById('m_demo_url').value,
|
||||
sort: parseInt(document.getElementById('m_sort').value)||0,
|
||||
status: parseInt(document.getElementById('m_status').value),
|
||||
};
|
||||
try {
|
||||
if (editingId) {
|
||||
await api('/admin/showcase/systems/' + editingId, { method:'PUT', body:JSON.stringify(body) });
|
||||
} else {
|
||||
await api('/admin/showcase/systems', { method:'POST', body:JSON.stringify(body) });
|
||||
}
|
||||
closeModal(); toast('保存成功','success'); renderBizSystems();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function deleteBizSys(id) {
|
||||
if (!confirm('确定删除?')) return;
|
||||
try { await api('/admin/showcase/systems/'+id,{method:'DELETE'}); toast('删除成功','success'); renderBizSystems(); } catch(e){}
|
||||
}
|
||||
|
||||
// ===== 通用工具 =====
|
||||
function formatDate(d) {
|
||||
if (!d) return '-';
|
||||
@@ -1449,7 +1663,12 @@ function renderPagination(d, fnName) {
|
||||
}
|
||||
|
||||
function showModal() { document.getElementById('modalOverlay').classList.add('show'); }
|
||||
function openModal() { showModal(); }
|
||||
function closeModal() { document.getElementById('modalOverlay').classList.remove('show'); editingId = null; }
|
||||
function saveModal() {
|
||||
if (typeof _modalSaveFn === 'function') _modalSaveFn();
|
||||
}
|
||||
let _modalSaveFn = null;
|
||||
|
||||
// ===== 初始化 =====
|
||||
document.getElementById('modalOverlay').addEventListener('click', (e) => {
|
||||
|
||||
Reference in New Issue
Block a user