安全(P0) - 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、 ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签 (ywxapp.framework_package_verify,默认开启) - JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret() 校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET - /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验 - 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流 - TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive - 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php) - 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis - paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG 工程化 / 交付 - 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本 - 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过 - 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障 - .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess 结构 - appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
448 lines
22 KiB
HTML
448 lines
22 KiB
HTML
<!--
|
||
* @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="price-card">
|
||
<div class="price {if $addon.price <= 0}free{/if}">
|
||
{if $addon.price > 0}¥{$addon.price|number_format=2}{else/}免费{/if}
|
||
</div>
|
||
<div class="sub">
|
||
{if $addon.price <= 0}
|
||
免费插件:安装与升级均无需授权
|
||
{else/}
|
||
{if $license.policy.license_period > 0}订阅制:{$license.policy.license_period} 天为一期,到期需续费<br>{/if}
|
||
{if $license.policy.trial_days > 0}支持免费试用 {$license.policy.trial_days} 天<br>{/if}
|
||
{if $license.policy.seats > 1}一份授权可绑定 {$license.policy.seats} 个站点<br>{/if}
|
||
{if $license.policy.upgrade_policy == 1}跨大版本需按原价重新购买
|
||
{elseif $license.policy.upgrade_policy == 2 /}跨大版本可按优惠价升级(¥{$license.policy.upgrade_price|number_format=2})
|
||
{else/}买断式:已购覆盖全部版本,升级不再收费{/if}
|
||
{/if}
|
||
</div>
|
||
|
||
<a class="buy-btn" id="btnBuy" href="javascript:void(0)" data-id="{$addon.id}">
|
||
{if $addon.price <= 0}免费获取
|
||
{elseif $license.action == 'owned' /}已在授权内
|
||
{elseif $license.action == 'trialing' /}试用中 · 立即购买
|
||
{elseif $license.action == 'trial' /}立即购买
|
||
{elseif $license.action == 'renew' /}续费 ¥{$license.pay_amount|number_format=2}
|
||
{elseif $license.action == 'upgrade' /}升级授权 · ¥{$license.pay_amount|number_format=2}
|
||
{elseif $license.action == 'revoked' /}重新购买
|
||
{elseif $license.action == 'free' /}免费获取
|
||
{else/}立即购买 · ¥{$addon.price|number_format=2}{/if}
|
||
</a>
|
||
|
||
{if $addon.price > 0 && $license.policy.trial_days > 0 && !$license.owned}
|
||
<a class="buy-btn ghost" id="btnTrial" href="javascript:void(0)" data-id="{$addon.id}">
|
||
免费试用 {$license.policy.trial_days} 天
|
||
</a>
|
||
{/if}
|
||
<div id="buyBox"></div>
|
||
<div id="buyMsg" style="margin-top:8px;font-size:12px;color:#e34d59;"></div>
|
||
</div>
|
||
|
||
{if $license.owned}
|
||
<div class="kv" style="margin-top:12px;">
|
||
<div class="row"><span class="k">当前状态</span><span class="v">
|
||
{if $license.trial_end > 0}试用中({:date('Y-m-d', $license.trial_end)} 到期)
|
||
{elseif $license.expire_time > 0 /}授权有效至 {:date('Y-m-d', $license.expire_time)}
|
||
{elseif $license.action == 'owned' /}买断拥有,可升级到任意版本
|
||
{else/}{$license.message}{/if}
|
||
</span></div>
|
||
{if $license.max_major > 0}
|
||
<div class="row"><span class="k">覆盖版本</span><span class="v">≤ {$license.max_major}.x</span></div>
|
||
{/if}
|
||
{if $license.seats > 1}
|
||
<div class="row"><span class="k">站点座席</span><span class="v">{$license.seats} 个</span></div>
|
||
{/if}
|
||
{if $license.bound_domain}
|
||
<div class="row"><span class="k">绑定域名</span><span class="v">{$license.bound_domain}</span></div>
|
||
{/if}
|
||
</div>
|
||
{else/}
|
||
<div class="kv" style="margin-top:12px;">
|
||
<div class="row"><span class="k">授权提示</span><span class="v">{$license.message|default='购买后即可安装'}</span></div>
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
|
||
<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 style="color:#999;">({$addon.review_count|default='0'} 条评价)</span>
|
||
</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>
|
||
<a href="#reviews">用户评价</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 用户评价:已审核通过的评论 + 提交表单(POST /appmall/api/addon/review,先审后发) -->
|
||
<section id="reviews" class="panel" style="margin:24px auto;max-width:1200px;padding:20px;">
|
||
<h3 style="margin:0 0 16px;">用户评价
|
||
<small style="font-weight:400;color:#999;">({$addon.review_count|default='0'} 条 · 均分 {$addon.rating})</small>
|
||
</h3>
|
||
|
||
{empty name="addon.reviews"}
|
||
<p style="color:#999;">暂无评价,安装使用后回来写第一条吧。</p>
|
||
{else /}
|
||
{foreach $addon.reviews as $rv}
|
||
<div style="padding:12px 0;border-bottom:1px dashed #eee;">
|
||
<div style="display:flex;align-items:center;gap:8px;">
|
||
<span class="stars">
|
||
{for start="1" end="6" name="j"}
|
||
{if $rv.rating >= $j}<i class="layui-icon layui-icon-rate-solid"></i>{else/}<i class="layui-icon layui-icon-rate off"></i>{/if}
|
||
{/for}
|
||
</span>
|
||
<b>{$rv.user}</b>
|
||
<span style="color:#999;font-size:12px;">{$rv.date}{if $rv.version} · v{$rv.version}{/if}</span>
|
||
</div>
|
||
{if $rv.content}<div style="margin-top:6px;">{$rv.content}</div>{/if}
|
||
</div>
|
||
{/foreach}
|
||
{/empty}
|
||
|
||
<div style="margin-top:20px;">
|
||
<div class="stars pick" style="font-size:22px;cursor:pointer;">
|
||
{for start="1" end="6" name="k"}
|
||
<i class="layui-icon layui-icon-rate-solid" data-v="{$k}"></i>
|
||
{/for}
|
||
</div>
|
||
<textarea id="rvContent" maxlength="500" rows="3"
|
||
placeholder="说说使用感受(选填,最多 500 字)"
|
||
style="width:100%;margin-top:10px;padding:8px;"></textarea>
|
||
<button class="layui-btn layui-btn-sm" style="margin-top:10px;" onclick="submitReview()">提交评价</button>
|
||
<span id="rvMsg" style="margin-left:8px;color:#999;font-size:12px;"></span>
|
||
</div>
|
||
</section>
|
||
|
||
<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' }); }
|
||
});
|
||
});
|
||
|
||
// ===== 用户评价:星星选择 + 提交 =====
|
||
// 注意 Market 控制器契约是 code=1 成功(与常规 Result 的 code=0 相反)
|
||
var picked = 5;
|
||
(function initStars() {
|
||
var stars = document.querySelectorAll('.stars.pick i');
|
||
function paint() {
|
||
stars.forEach(function (im, idx) {
|
||
im.style.color = idx < picked ? '#ffb800' : '#d2d2d2';
|
||
});
|
||
}
|
||
stars.forEach(function (el) {
|
||
el.addEventListener('click', function () {
|
||
picked = parseInt(el.getAttribute('data-v'), 10) || 5;
|
||
paint();
|
||
});
|
||
});
|
||
paint();
|
||
})();
|
||
|
||
function submitReview() {
|
||
var msg = document.getElementById('rvMsg');
|
||
var content = document.getElementById('rvContent').value;
|
||
msg.textContent = '提交中…';
|
||
fetch('/appmall/api/addon/review', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
|
||
body: 'name=' + encodeURIComponent('{$addon.name}')
|
||
+ '&rating=' + picked
|
||
+ '&content=' + encodeURIComponent(content)
|
||
+ '&version=' + encodeURIComponent('{$addon.version}')
|
||
}).then(function (r) { return r.json(); }).then(function (res) {
|
||
if (res.code === 1) {
|
||
msg.textContent = res.message || '提交成功,审核通过后将公开展示';
|
||
document.getElementById('rvContent').value = '';
|
||
} else {
|
||
msg.textContent = res.message || '提交失败';
|
||
}
|
||
}).catch(function () {
|
||
msg.textContent = '提交失败:网络异常';
|
||
});
|
||
}
|
||
|
||
// ===== 购买 / 试用 =====
|
||
// 买断、订阅续费、跨大版本升级都走同一个 /appmall/store/buy,
|
||
// 服务端按当前授权状态决定金额与订单类型(见 LicensePolicy::decide)。
|
||
function postLicense(url, id) {
|
||
var tip = document.getElementById('buyMsg');
|
||
tip.style.color = '#e34d59';
|
||
tip.textContent = '处理中…';
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
|
||
body: 'id=' + encodeURIComponent(id)
|
||
}).then(function (r) { return r.json(); }).then(function (res) {
|
||
if (!res || res.code !== 1) {
|
||
tip.textContent = (res && res.msg) || '操作失败';
|
||
return;
|
||
}
|
||
var d = res.data || {};
|
||
if (d.pay_params) {
|
||
// 支付表单(支付宝/微信 SDK 返回的自动提交表单):注入后提交跳转收银台
|
||
document.getElementById('buyBox').innerHTML = d.pay_params;
|
||
var form = document.querySelector('#buyBox form');
|
||
if (form) { form.submit(); return; }
|
||
tip.textContent = '支付参数异常,请联系管理员';
|
||
return;
|
||
}
|
||
tip.style.color = '#16a34a';
|
||
tip.textContent = res.msg || '操作成功';
|
||
setTimeout(function () { location.reload(); }, 1200);
|
||
}).catch(function () {
|
||
tip.textContent = '请求失败:网络异常';
|
||
});
|
||
}
|
||
|
||
var btnBuy = document.getElementById('btnBuy');
|
||
if (btnBuy) {
|
||
btnBuy.addEventListener('click', function () { postLicense('/appmall/store/buy', this.getAttribute('data-id')); });
|
||
}
|
||
var btnTrial = document.getElementById('btnTrial');
|
||
if (btnTrial) {
|
||
btnTrial.addEventListener('click', function () { postLicense('/appmall/store/trial', this.getAttribute('data-id')); });
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|