chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>开发者控制台 - YwxApp 应用市场</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<style>
body { background: #f2f3f5; color: #333; }
.dev-box { max-width: 720px; margin: 40px auto; background: #fff; padding: 28px 32px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,.08); }
.dev-box h2 { margin-bottom: 4px; }
.dev-tip { color: #999; font-size: 13px; margin-bottom: 18px; }
.balance-row { display: flex; gap: 16px; margin: 18px 0; }
.balance-card { flex: 1; background: #f7f9fc; border-radius: 8px; padding: 16px; text-align: center; }
.balance-card .label { color: #888; font-size: 13px; }
.balance-card .val { font-size: 24px; font-weight: 700; margin-top: 6px; }
.balance-card .val.ok { color: #18a058; }
.balance-card .val.free { color: #d48806; }
.rev-table { margin-top: 10px; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="dev-box">
<h2>开发者控制台</h2>
<p class="dev-tip">使用开发者令牌查看收益并发起提现(令牌可在「我的令牌」页查询)</p>
<form class="layui-form" id="authForm">
<div class="layui-form-item">
<label class="layui-form-label">开发者令牌</label>
<div class="layui-input-block">
<input type="text" name="token" id="tokenInput" value="{$token|default=''}" required placeholder="粘贴你的开发者令牌" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="load">加载我的收益</button>
<a href="{:url('index')}" class="layui-btn layui-btn-primary">返回注册</a>
</div>
</div>
</form>
<div id="panel" class="hidden">
<div class="balance-row">
<div class="balance-card">
<div class="label">可提现余额</div>
<div class="val ok" id="balance">¥0.00</div>
</div>
<div class="balance-card">
<div class="label">冻结中(提现待打款)</div>
<div class="val free" id="frozen">¥0.00</div>
</div>
</div>
<fieldset class="layui-elem-field layui-field-title" style="margin-top:18px;"><legend>发起提现</legend></fieldset>
<form class="layui-form" id="withdrawForm">
<input type="hidden" name="token" id="wToken">
<div class="layui-form-item">
<label class="layui-form-label">提现金额</label>
<div class="layui-input-inline">
<input type="number" name="amount" step="0.01" min="0.01" required placeholder="0.00" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">不可超过可提现余额</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">提现渠道</label>
<div class="layui-input-inline">
<select name="channel">
<option value="alipay">支付宝</option>
<option value="wechat">微信</option>
<option value="bank">银行卡</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">收款账号</label>
<div class="layui-input-block">
<input type="text" name="account" required placeholder="支付宝/微信账号或银行卡号" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="doWithdraw">提交提现申请</button>
</div>
</div>
</form>
<fieldset class="layui-elem-field layui-field-title" style="margin-top:24px;"><legend>近期收益</legend></fieldset>
<table class="layui-table rev-table">
<thead>
<tr><th>订单号</th><th>销售额</th><th>抽成</th><th>应得</th><th>状态</th><th>时间</th></tr>
</thead>
<tbody id="revBody">
<tr><td colspan="6" style="text-align:center;color:#999;">暂无收益记录</td></tr>
</tbody>
</table>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script>
layui.use(['form', 'jquery', 'layer', 'http'], function () {
var form = layui.form, $ = layui.jquery, layer = layui.layer, http = layui.http;
form.on('submit(load)', function (data) {
http.post('{:url("/appmall/developer/earnings")}', data.field).then(function (r) {
if (r.code !== 0) { layer.msg(r.message || '加载失败', { icon: 2 }); return; }
var d = r.data || {};
$('#balance').text('¥' + parseFloat(d.balance || 0).toFixed(2));
$('#frozen').text('¥' + parseFloat(d.frozen_balance || 0).toFixed(2));
$('#wToken').val(data.field.token);
renderRevenues(d.revenues || []);
$('#panel').removeClass('hidden');
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
return false;
});
form.on('submit(doWithdraw)', function (data) {
var amount = parseFloat(data.field.amount);
if (!(amount > 0)) { layer.msg('请输入正确的提现金额', { icon: 2 }); return false; }
var avail = parseFloat($('#balance').text().replace(/[^\d.]/g, '')) || 0;
if (amount > avail) { layer.msg('提现金额超过可提现余额', { icon: 2 }); return false; }
http.post('{:url("/appmall/developer/withdraw")}', data.field).then(function (r) {
layer.msg(r.message || '提交完成', { icon: r.code === 0 ? 1 : 2 });
if (r.code === 0) { $('#withdrawForm')[0].reset(); form.render(); }
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
return false;
});
function renderRevenues(list) {
var body = $('#revBody');
if (!list.length) {
body.html('<tr><td colspan="6" style="text-align:center;color:#999;">暂无收益记录</td></tr>');
return;
}
var html = '';
list.forEach(function (it) {
var st = it.status == 1 ? '<span style="color:#18a058;">已结算</span>' : '<span style="color:#d48806;">待结算</span>';
html += '<tr>'
+ '<td>' + (it.order_id || '-') + '</td>'
+ '<td>¥' + parseFloat(it.amount || 0).toFixed(2) + '</td>'
+ '<td>¥' + parseFloat(it.commission || 0).toFixed(2) + '</td>'
+ '<td>¥' + parseFloat(it.income || 0).toFixed(2) + '</td>'
+ '<td>' + st + '</td>'
+ '<td>' + (it.create_at ? new Date(it.create_at * 1000).toLocaleString() : '-') + '</td>'
+ '</tr>';
});
body.html(html);
}
// 若 URL 携带 token,自动加载
var tok = $('#tokenInput').val();
if (tok) { $('#authForm').find('button[lay-filter="load"]').click(); }
});
</script>
</body>
</html>
@@ -0,0 +1,137 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Description: 主框架发布/下载展示页(公开,仅中心站)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>主框架下载 - {$market_name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<style>
:root {
--brand: #4f46e5; --brand-2: #7c3aed; --ink: #1f2329;
--muted: #8a8f99; --line: #eceef2; --bg: #f5f6fa;
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased;
}
.brandbar {
height: 56px; display: flex; align-items: center; gap: 10px;
padding: 0 24px; background: #fff; border-bottom: 1px solid var(--line);
position: sticky; top: 0; z-index: 20;
}
.brandbar .logo {
width: 30px; height: 30px; border-radius: 8px;
background: linear-gradient(135deg, var(--brand), var(--brand-2));
display: grid; place-items: center; color: #fff; font-weight: 700;
}
.brandbar .name { font-weight: 600; font-size: 16px; }
.brandbar .nav { margin-left: auto; display: flex; gap: 22px; font-size: 14px; }
.brandbar .nav a { color: var(--muted); text-decoration: none; transition: color .2s; }
.brandbar .nav a:hover, .brandbar .nav a.on { color: var(--brand); }
.hero {
padding: 54px 24px 46px; text-align: center; color: #fff;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 55%, #9333ea 100%);
position: relative; overflow: hidden;
}
.hero::after {
content: ""; position: absolute; inset: 0;
background:
radial-gradient(420px 220px at 12% 0%, rgba(255,255,255,.18), transparent 60%),
radial-gradient(380px 200px at 88% 100%, rgba(255,255,255,.12), transparent 60%);
}
.hero h1 { margin: 0 0 10px; font-size: 32px; position: relative; }
.hero p { margin: 0; opacity: .92; font-size: 15px; position: relative; }
.wrap { max-width: 960px; margin: 36px auto 60px; padding: 0 24px; }
.ver-card {
background: #fff; border: 1px solid var(--line); border-radius: 16px;
padding: 22px 26px; margin-bottom: 18px;
transition: transform .22s, box-shadow .22s, border-color .22s;
}
.ver-card:hover { transform: translateY(-4px); box-shadow: 0 16px 36px rgba(79,70,229,.14); border-color: transparent; }
.ver-top { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
.ver-title { font-size: 20px; font-weight: 600; }
.ver-title .v { color: #16a34a; }
.ver-date { color: var(--muted); font-size: 13px; }
.ver-sub { color: #6b7280; font-size: 14px; margin: 10px 0 14px; }
.ver-sub b { color: var(--ink); }
.ver-log {
background: #f7f8fa; border: 1px solid var(--line); border-radius: 10px;
padding: 12px 14px; font-size: 13px; color: #555; white-space: pre-wrap;
margin-bottom: 16px; max-height: 160px; overflow: auto;
}
.dl-btns { display: flex; gap: 10px; flex-wrap: wrap; }
.dl-btns a {
text-decoration: none; font-size: 13px; color: #fff; padding: 9px 16px; border-radius: 9px;
background: linear-gradient(135deg, var(--brand), var(--brand-2)); transition: opacity .2s, transform .1s;
}
.dl-btns a:hover { opacity: .9; }
.dl-btns a:active { transform: scale(.97); }
.dl-btns a.install { background: linear-gradient(135deg, #722ed1, #9254de); }
.empty { text-align: center; color: var(--muted); padding: 60px 0; }
footer { text-align: center; color: var(--muted); font-size: 12px; padding: 26px; }
</style>
</head>
<body>
<div class="brandbar">
<div class="logo">Y</div>
<div class="name">{$market_name}</div>
<div class="nav">
<a href="/appmall/store">应用市场</a>
<a href="/appmall/framework" class="on">主框架</a>
</div>
</div>
<div class="hero">
<h1>YwxApp 主框架下载</h1>
<p>完整安装包用于全新部署,整包 / 补丁用于已安装站点的在线升级。</p>
</div>
<div class="wrap">
{if empty($list)}
<div class="empty">暂未发布任何框架版本。</div>
{else}
{foreach $list as $item}
<div class="ver-card">
<div class="ver-top">
<div class="ver-title">{$item.title|default='主框架'} <span class="v">v{$item.version}</span></div>
<div class="ver-date">发布于 {$item.create_at_fmt} · 下载 {$item.download_count} 次</div>
</div>
{if $item.patch_from}
<div class="ver-sub">增量补丁适用基础版本:<b>{$item.patch_from}</b></div>
{/if}
{if $item.changelog}
<div class="ver-log">{$item.changelog}</div>
{/if}
<div class="dl-btns">
{if $item.has_full}
<a href="/appmall/api/framework/download?version={$item.version}&type=full">
<i class="layui-icon layui-icon-download-circle"></i> 下载整包(升级)
</a>
{/if}
{if $item.has_patch}
<a href="/appmall/api/framework/download?version={$item.version}&type=patch">
<i class="layui-icon layui-icon-download-circle"></i> 下载补丁(升级)
</a>
{/if}
{if $item.has_install}
<a class="install" href="/appmall/api/framework/download?version={$item.version}&type=install">
<i class="layui-icon layui-icon-download-circle"></i> 下载完整安装包(全新部署)
</a>
{/if}
</div>
</div>
{/foreach}
{/if}
</div>
<footer>© {:date('Y')} {$market_name} · 由 YwxApp 框架强力驱动</footer>
</body>
</html>
@@ -0,0 +1,257 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Description: 插件详情页(公开,仅中心站)· 参考 FastAdmin 插件市场详情版式
* 布局:Hero 标题区 → 左主内容(功能介绍/预览截图/更新日志) + 右 sticky 信息栏(价格/目录)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>{$addon.title} - 应用市场</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<style>
:root {
--brand: #4f46e5; --brand-2: #7c3aed; --ink: #1f2329;
--muted: #8a8f99; --line: #eceef2; --bg: #f5f6fa;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased; }
a { text-decoration: none; }
.brandbar { height: 56px; display: flex; align-items: center; gap: 10px;
padding: 0 24px; background: #fff; border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 30; }
.brandbar .logo { width: 30px; height: 30px; border-radius: 8px;
background: linear-gradient(135deg, var(--brand), var(--brand-2)); display: grid; place-items: center; color: #fff; font-weight: 700; }
.brandbar .name { font-weight: 600; font-size: 16px; }
.brandbar .nav { margin-left: auto; display: flex; gap: 22px; font-size: 14px; }
.brandbar .nav a { color: var(--muted); transition: color .2s; }
.brandbar .nav a:hover, .brandbar .nav a.on { color: var(--brand); }
.crumb { max-width: 1120px; margin: 16px auto 0; padding: 0 24px; font-size: 13px; color: var(--muted); }
.crumb a { color: var(--muted); }
.crumb a:hover { color: var(--brand); }
/* Hero */
.hero { max-width: 1120px; margin: 14px auto 0; padding: 30px 36px; background: #fff; border: 1px solid var(--line); border-radius: 18px;
display: flex; gap: 24px; align-items: center; position: relative; overflow: hidden; }
.hero::before { content: ""; position: absolute; right: -60px; top: -60px; width: 240px; height: 240px; border-radius: 50%;
background: radial-gradient(circle, rgba(124,58,237,.10), transparent 70%); }
.hero .ava { width: 88px; height: 88px; border-radius: 20px; flex: none; overflow: hidden; background: #f0f1f5;
display: grid; place-items: center; color: var(--brand); font-size: 34px; font-weight: 700; box-shadow: 0 8px 22px rgba(79,70,229,.18); }
.hero .ava img { width: 100%; height: 100%; object-fit: cover; }
.hero .h-main { min-width: 0; flex: 1; }
.hero .h-title { font-size: 26px; font-weight: 700; display: flex; align-items: center; gap: 12px; }
.hero .h-title .ver { font-size: 13px; color: #16a34a; background: rgba(22,163,74,.1); border-radius: 6px; padding: 3px 9px; font-weight: 500; }
.hero .h-desc { color: #5b606b; font-size: 14px; margin-top: 8px; line-height: 1.7; }
.hero .h-actions { display: flex; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.btn-primary { display: inline-flex; align-items: center; gap: 6px; color: #fff; font-size: 14px; font-weight: 600;
padding: 11px 22px; border-radius: 11px; background: linear-gradient(135deg, var(--brand), var(--brand-2)); transition: .2s, transform .1s; }
.btn-primary:hover { opacity: .92; }
.btn-primary:active { transform: scale(.98); }
.btn-ghost { display: inline-flex; align-items: center; gap: 6px; color: var(--brand); font-size: 14px; font-weight: 600;
padding: 11px 22px; border-radius: 11px; background: #fff; border: 1px solid var(--brand); transition: .2s; }
.btn-ghost:hover { background: rgba(79,70,229,.06); }
/* 主体双栏 */
.main { max-width: 1120px; margin: 24px auto 60px; padding: 0 24px; display: grid; gap: 24px;
grid-template-columns: 1fr 320px; align-items: start; }
.col-right { position: sticky; top: 76px; display: flex; flex-direction: column; gap: 18px; }
.panel { background: #fff; border: 1px solid var(--line); border-radius: 16px; padding: 24px; }
.panel + .panel { margin-top: 24px; }
.panel h3 { margin: 0 0 16px; font-size: 17px; display: flex; align-items: center; gap: 9px; }
.panel h3::before { content: ""; width: 4px; height: 17px; border-radius: 2px; background: linear-gradient(var(--brand), var(--brand-2)); }
.prose { color: #4b515c; font-size: 14px; line-height: 1.9; white-space: pre-wrap; }
/* 截图 */
.cover { width: 100%; aspect-ratio: 16/9; border-radius: 14px; overflow: hidden; background: #f0f1f5;
display: grid; place-items: center; color: var(--brand); font-size: 36px; font-weight: 700; }
.cover img { width: 100%; height: 100%; object-fit: cover; }
.thumbs { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.thumbs img { width: 104px; height: 64px; object-fit: cover; border-radius: 9px; border: 2px solid transparent; cursor: pointer; transition: .18s; }
.thumbs img:hover, .thumbs img.on { border-color: var(--brand); }
/* 更新日志时间线 */
.timeline { position: relative; padding-left: 22px; }
.timeline::before { content: ""; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }
.tl-item { position: relative; padding-bottom: 22px; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before { content: ""; position: absolute; left: -21px; top: 5px; width: 12px; height: 12px; border-radius: 50%;
background: #fff; border: 3px solid var(--brand); }
.tl-item.first::before { background: var(--brand); box-shadow: 0 0 0 4px rgba(79,70,229,.18); }
.tl-head { display: flex; align-items: center; gap: 10px; }
.tl-ver { font-weight: 700; font-size: 14px; }
.tl-badge { font-size: 11px; color: #16a34a; background: rgba(22,163,74,.1); border-radius: 6px; padding: 2px 8px; }
.tl-date { color: var(--muted); font-size: 12px; margin-left: auto; }
.tl-note { color: #5b606b; font-size: 13px; line-height: 1.7; margin-top: 6px; white-space: pre-wrap; }
/* 右栏:价格卡 + 信息 + 目录 */
.price-card { background: linear-gradient(135deg, rgba(79,70,229,.07), rgba(124,58,237,.07));
border: 1px solid rgba(124,58,237,.18); border-radius: 14px; padding: 18px 20px; }
.price-card .price { font-size: 30px; font-weight: 800; color: var(--brand); }
.price-card .price.free { color: #16a34a; }
.price-card .sub { color: var(--muted); font-size: 12px; margin-top: 2px; }
.buy-btn { display: block; text-align: center; color: #fff; font-weight: 600; font-size: 15px; padding: 13px;
border-radius: 11px; background: linear-gradient(135deg, var(--brand), var(--brand-2)); margin-top: 14px; transition: .2s, transform .1s; }
.buy-btn:hover { opacity: .92; }
.buy-btn:active { transform: scale(.98); }
.buy-btn.ghost { background: #fff; color: var(--brand); border: 1px solid var(--brand); margin-top: 10px; }
.kv { font-size: 13px; }
.kv .row { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px dashed var(--line); }
.kv .row:last-child { border-bottom: 0; }
.kv .k { color: var(--muted); }
.kv .v { color: var(--ink); text-align: right; }
.stars { color: #f5a623; letter-spacing: 1px; }
.stars .off { color: #e2e4ea; }
.tags { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.tags span { font-size: 12px; color: var(--brand); background: rgba(79,70,229,.1); border-radius: 6px; padding: 4px 10px; }
.toc { font-size: 13px; }
.toc .t { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.toc a { display: block; color: #5b606b; padding: 7px 0; border-left: 2px solid transparent; padding-left: 12px; transition: .18s; }
.toc a:hover { color: var(--brand); border-left-color: var(--brand); }
footer { text-align: center; color: var(--muted); font-size: 12px; padding: 26px; }
@media (max-width: 900px) {
.main { grid-template-columns: 1fr; }
.col-right { position: static; }
}
</style>
</head>
<body>
<div class="brandbar">
<div class="logo">Y</div>
<div class="name">{$market_name}</div>
<div class="nav">
<a href="/appmall/store" class="on">应用市场</a>
<a href="/appmall/framework">主框架</a>
</div>
</div>
<div class="crumb">
<a href="/appmall/store">应用市场</a> / <span>{$addon.title}</span>
</div>
<!-- Hero -->
<div class="hero">
<div class="ava">
{if $addon.logo}<img src="{$addon.logo}" alt="{$addon.title}">{else}{$addon.title|substr=0,1}{/if}
</div>
<div class="h-main">
<div class="h-title">{$addon.title}<span class="ver">v{$addon.version}</span></div>
<div class="h-desc">{if $addon.author}by {$addon.author} · {/if}{$addon.category|default='未分类'}</div>
<div class="h-actions">
<a class="btn-primary" href="#"><i class="layui-icon layui-icon-play"></i> 立即体验</a>
<a class="btn-ghost" href="#"><i class="layui-icon layui-icon-cart"></i> 立即购买</a>
</div>
</div>
</div>
<div class="main">
<!-- 左主内容 -->
<div class="col-left">
<div class="panel" id="intro">
<h3>功能介绍</h3>
<div class="prose">{$addon.intro|default='暂无介绍'}</div>
</div>
{if $addon.screenshots}
<div class="panel" id="shots">
<h3>预览截图</h3>
<div class="cover" id="cover">
<img src="{$addon.screenshots.0}" alt="{$addon.title}">
</div>
{if count($addon.screenshots) > 1}
<div class="thumbs" id="thumbs">
{foreach $addon.screenshots as $s}
<img src="{$s}" class="{if $s == $addon.screenshots.0}on{/if}" onclick="swap('{$s}', this)">
{/foreach}
</div>
{/if}
</div>
{/if}
<div class="panel" id="log">
<h3>更新日志</h3>
{if empty($addon.versions)}
<div class="prose" style="color:var(--muted)">暂无版本记录</div>
{else}
<div class="timeline">
{foreach $addon.versions as $k=>$v}
<div class="tl-item {if $k == 0}first{/if}">
<div class="tl-head">
<span class="tl-ver">v{$v.version}</span>
{if $k == 0}<span class="tl-badge">最新版本</span>{/if}
<span class="tl-date">{$v.update_fmt}</span>
</div>
<div class="tl-note">本版本定价:{$v.price_text}</div>
</div>
{/foreach}
</div>
{/if}
</div>
</div>
<!-- 右 sticky 信息栏 -->
<div class="col-right">
<div class="panel">
<div class="kv">
<div class="row"><span class="k">当前版本</span><span class="v">v{$addon.version}</span></div>
<div class="row"><span class="k">更新时间</span><span class="v">{$addon.update_fmt}</span></div>
<div class="row"><span class="k">下载量</span><span class="v">{$addon.downloads}</span></div>
<div class="row"><span class="k">评分</span>
<span class="v">
<span class="stars">
{for start="1" end="6" name="i"}
{if $addon.rating >= $i}<i class="layui-icon layui-icon-rate-solid"></i>{else/}<i class="layui-icon layui-icon-rate off"></i>{/if}
{/for}
</span>
{$addon.rating}
</span>
</div>
<div class="row"><span class="k">开发者</span><span class="v">{$addon.author|default='官方'}</span></div>
<div class="row"><span class="k">分类</span><span class="v">{$addon.category|default='未分类'}</span></div>
</div>
{if $addon.tags}
<div class="tags">
{foreach explode(',', $addon.tags) as $t}
<span>{$t}</span>
{/foreach}
</div>
{/if}
</div>
<div class="panel">
<div class="toc">
<div class="t">本页目录</div>
<a href="#intro">功能介绍</a>
{if $addon.screenshots}<a href="#shots">预览截图</a>{/if}
<a href="#log">更新日志</a>
</div>
</div>
</div>
</div>
<footer>© {:date('Y')} {$market_name} · 由 YwxApp 框架强力驱动</footer>
<script>
function swap(src, el) {
document.querySelector('#cover img').src = src;
document.querySelectorAll('#thumbs img').forEach(function (im) { im.classList.remove('on'); });
el.classList.add('on');
}
// 锚点平滑滚动 + 目录高亮
document.querySelectorAll('.toc a').forEach(function (a) {
a.addEventListener('click', function (e) {
var id = this.getAttribute('href');
var target = document.querySelector(id);
if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
});
});
</script>
</body>
</html>
@@ -0,0 +1,230 @@
<!--
* @Author: YwxApp <ywx@ywxapp.cn>
* @Description: 应用商店前台(公开,仅中心站)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>应用市场 - {$market_name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/layui/css/layui.css" rel="stylesheet">
<style>
:root {
--brand: #4f46e5;
--brand-2: #7c3aed;
--ink: #1f2329;
--muted: #8a8f99;
--line: #eceef2;
--bg: #f5f6fa;
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased;
}
.brandbar {
height: 56px; display: flex; align-items: center; gap: 10px;
padding: 0 24px; background: #fff; border-bottom: 1px solid var(--line);
position: sticky; top: 0; z-index: 20;
}
.brandbar .logo {
width: 30px; height: 30px; border-radius: 8px;
background: linear-gradient(135deg, var(--brand), var(--brand-2));
display: grid; place-items: center; color: #fff; font-weight: 700;
}
.brandbar .name { font-weight: 600; font-size: 16px; }
.brandbar .nav { margin-left: auto; display: flex; gap: 22px; font-size: 14px; }
.brandbar .nav a { color: var(--muted); text-decoration: none; transition: color .2s; }
.brandbar .nav a:hover, .brandbar .nav a.on { color: var(--brand); }
.hero {
padding: 54px 24px 46px; text-align: center; color: #fff;
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 55%, #9333ea 100%);
position: relative; overflow: hidden;
}
.hero::after {
content: ""; position: absolute; inset: 0;
background:
radial-gradient(420px 220px at 12% 0%, rgba(255,255,255,.18), transparent 60%),
radial-gradient(380px 200px at 88% 100%, rgba(255,255,255,.12), transparent 60%);
}
.hero h1 { margin: 0 0 10px; font-size: 32px; letter-spacing: .5px; position: relative; }
.hero p { margin: 0; opacity: .92; font-size: 15px; position: relative; }
.toolbar {
max-width: 1180px; margin: -26px auto 0; padding: 0 24px; position: relative; z-index: 5;
}
.toolbar .inner {
background: #fff; border-radius: 14px; box-shadow: 0 8px 30px rgba(31,35,41,.08);
padding: 14px 18px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.search {
flex: 1; min-width: 220px; display: flex; align-items: center; gap: 8px;
background: #f5f6fa; border-radius: 10px; padding: 9px 14px; color: var(--muted);
}
.search input {
border: 0; background: transparent; outline: none; flex: 1; font-size: 14px; color: var(--ink);
}
.cats { display: flex; gap: 8px; flex-wrap: wrap; }
.cat {
border: 1px solid var(--line); background: #fff; color: var(--muted);
border-radius: 999px; padding: 6px 14px; font-size: 13px; cursor: pointer; transition: .18s;
}
.cat:hover { border-color: var(--brand); color: var(--brand); }
.cat.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.grid {
max-width: 1180px; margin: 28px auto 60px; padding: 0 24px;
display: grid; gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.card {
background: #fff; border-radius: 16px; border: 1px solid var(--line);
padding: 20px; display: flex; flex-direction: column; gap: 12px;
transition: transform .22s ease, box-shadow .22s ease, border-color .22s;
}
.card:hover {
transform: translateY(-6px);
box-shadow: 0 18px 40px rgba(79,70,229,.16);
border-color: transparent;
}
.card .head { display: flex; align-items: center; gap: 12px; }
.card .ava {
width: 52px; height: 52px; border-radius: 14px; flex: none;
background: #f0f1f5; object-fit: cover; display: grid; place-items: center;
font-weight: 700; color: var(--brand); font-size: 20px; overflow: hidden;
}
.card .ava img { width: 100%; height: 100%; object-fit: cover; }
.card .titles { min-width: 0; }
.card .titles .t { font-weight: 600; font-size: 16px; line-height: 1.3; }
.card .titles .a { color: var(--muted); font-size: 12px; margin-top: 2px; }
.card .desc {
color: #5b606b; font-size: 13px; line-height: 1.6; min-height: 42px;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card .meta { display: flex; align-items: center; gap: 14px; font-size: 12px; color: var(--muted); }
.stars { color: #f5a623; letter-spacing: 1px; }
.stars .off { color: #e2e4ea; }
.dl { display: flex; align-items: center; gap: 5px; }
.card .foot { display: flex; align-items: center; justify-content: space-between; margin-top: auto; }
.price {
font-weight: 700; font-size: 16px; color: var(--brand);
}
.price.free { color: #16a34a; }
.tag {
font-size: 11px; color: var(--brand); background: rgba(79,70,229,.1);
border-radius: 6px; padding: 3px 8px;
}
.card .btn {
text-decoration: none; font-size: 13px; color: #fff;
background: linear-gradient(135deg, var(--brand), var(--brand-2));
padding: 8px 16px; border-radius: 9px; transition: opacity .2s, transform .1s;
}
.card .btn:hover { opacity: .9; }
.card .btn:active { transform: scale(.97); }
.empty { grid-column: 1 / -1; text-align: center; padding: 70px 0; color: var(--muted); }
.empty .ic { font-size: 48px; opacity: .5; }
.empty p { margin-top: 12px; }
footer { text-align: center; color: var(--muted); font-size: 12px; padding: 26px; }
</style>
</head>
<body>
<div class="brandbar">
<div class="logo">Y</div>
<div class="name">{$market_name}</div>
<div class="nav">
<a href="/appmall/store" class="on">应用市场</a>
<a href="/appmall/framework">主框架</a>
</div>
</div>
<div class="hero">
<h1>发现优质扩展,一键点亮你的站点</h1>
<p>插件、模板、工具,覆盖建站全场景 · 由 {$market_name} 官方精选</p>
</div>
<div class="toolbar">
<div class="inner">
<div class="search">
<i class="layui-icon layui-icon-search"></i>
<input type="text" placeholder="搜索插件名称、作者或功能…" oninput="filterCards(this.value)">
</div>
<div class="cats" id="cats">
<span class="cat on" data-cat="">全部</span>
{foreach $categories as $c}
<span class="cat" data-cat="{$c}">{$c}</span>
{/foreach}
</div>
</div>
</div>
<div class="grid" id="grid">
{if empty($list)}
<div class="empty">
<div class="ic"><i class="layui-icon layui-icon-template-1"></i></div>
<p>市场暂无插件,敬请期待。</p>
</div>
{else}
{foreach $list as $p}
<div class="card" data-name="{$p.name|lower}" data-author="{$p.author|lower}" data-cat="{$p.category}">
<div class="head">
<div class="ava">
{if $p.logo}
<img src="{$p.logo}" alt="{$p.title}">
{else}
{$p.title|substr=0,1}
{/if}
</div>
<div class="titles">
<div class="t">{$p.title}{if $p.version} <span style="font-weight:400;color:#aab;font-size:12px;">v{$p.version}</span>{/if}</div>
<div class="a">by {$p.author|default='官方'}</div>
</div>
</div>
<div class="desc">{$p.desc|default='暂无描述'}</div>
<div class="meta">
<span class="stars" title="评分 {$p.rating}">
{for start="1" end="6" name="i"}
{if $p.rating >= $i}<i class="layui-icon layui-icon-rate-solid"></i>{else/}<i class="layui-icon layui-icon-rate off"></i>{/if}
{/for}
</span>
<span class="dl"><i class="layui-icon layui-icon-download-circle"></i>{$p.downloads} 下载</span>
</div>
<div class="foot">
<div>
<span class="price {if $p.price==0}free{/if}">{$p.price_text}</span>
{if $p.category}<span class="tag">{$p.category}</span>{/if}
</div>
<a class="btn" href="/appmall/store/detail/{$p.name}">查看详情</a>
</div>
</div>
{/foreach}
{/if}
</div>
<footer>© {:date('Y')} {$market_name} · 由 YwxApp 框架强力驱动</footer>
<script>
function filterCards(kw) {
kw = (kw || '').toLowerCase().trim();
document.querySelectorAll('#grid .card').forEach(function (el) {
var hit = !kw || el.dataset.name.indexOf(kw) > -1 || el.dataset.author.indexOf(kw) > -1;
el.style.display = hit ? '' : 'none';
});
}
document.getElementById('cats').addEventListener('click', function (e) {
var cat = e.target.closest('.cat');
if (!cat) return;
document.querySelectorAll('#cats .cat').forEach(function (c) { c.classList.remove('on'); });
cat.classList.add('on');
var v = cat.dataset.cat;
document.querySelectorAll('#grid .card').forEach(function (el) {
el.style.display = (!v || el.dataset.cat === v) ? '' : 'none';
});
});
</script>
</body>
</html>