chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
|
||||
<div class="layui-card" style="margin:20px;">
|
||||
<div class="layui-card-header">插件审核(开发者上架申请)</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-form layui-row" style="margin-bottom:12px;">
|
||||
<div class="layui-inline">
|
||||
<select id="statusFilter">
|
||||
<option value="">全部状态</option>
|
||||
<option value="0">待审核</option>
|
||||
<option value="1">已发布</option>
|
||||
<option value="2">已驳回</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button class="layui-btn" id="searchBtn">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="toolTpl">
|
||||
{{# if(d.status == 0){ }}
|
||||
<a class="layui-btn layui-btn-xs" lay-event="audit">通过</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="reject">驳回</a>
|
||||
{{# } else { }}
|
||||
<span class="layui-badge {{ d.status == 1 ? 'layui-bg-green' : '' }}">
|
||||
{{ d.status == 1 ? '已发布' : '已驳回' }}
|
||||
</span>
|
||||
{{# } }}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'jquery', 'layer'], function () {
|
||||
var table = layui.table, $ = layui.jquery, layer = layui.layer;
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: '{:url("/appmall/backend/addonreview/index")}',
|
||||
page: true,
|
||||
response: { statusName: 'code', msgName: 'message', countName: 'count', dataName: 'data' },
|
||||
parseData: function (res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
},
|
||||
where: { status: $('#statusFilter').val() },
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 70},
|
||||
{field: 'title', title: '插件名称', width: 160},
|
||||
{field: 'name', title: '标识', width: 140},
|
||||
{field: 'version', title: '版本', width: 90},
|
||||
{field: 'developer_id', title: '开发者ID', width: 100},
|
||||
{field: 'price', title: '价格', width: 90, templet: function(d){return d.price ? d.price : '免费';}},
|
||||
{field: 'status', title: '状态', width: 90, templet: function(d){
|
||||
if(d.status==0) return '<span class="layui-badge">待审核</span>';
|
||||
if(d.status==1) return '<span class="layui-badge layui-bg-green">已发布</span>';
|
||||
return '<span class="layui-badge layui-bg-red">已驳回</span>';
|
||||
}},
|
||||
{field: 'reject_reason', title: '驳回原因', width: 160},
|
||||
{field: 'create_at', title: '提交时间', width: 170, templet: function(d){return layui.util.toDateString(d.create_at*1000);}},
|
||||
{title: '操作', width: 160, templet: '#toolTpl', fixed: 'right'}
|
||||
]]
|
||||
});
|
||||
|
||||
$('#searchBtn').on('click', function () {
|
||||
table.reload('dataTable', { where: { status: $('#statusFilter').val() }, page: { curr: 1 } });
|
||||
});
|
||||
|
||||
table.on('tool(dataTable)', function (obj) {
|
||||
var d = obj.data;
|
||||
if (obj.event === 'audit') {
|
||||
layer.confirm('确认通过该插件上架申请?', function (index) {
|
||||
$.post('{:url("/appmall/backend/addonreview/audit")}', { id: d.id }, function (r) {
|
||||
layer.msg(r.msg || '操作完成');
|
||||
if (r.code === 0) { table.reload('dataTable'); }
|
||||
layer.close(index);
|
||||
}, 'json');
|
||||
});
|
||||
} else if (obj.event === 'reject') {
|
||||
layer.prompt({ title: '填写驳回原因', formType: 2 }, function (val, index) {
|
||||
$.post('{:url("/appmall/backend/addonreview/reject")}', { id: d.id, reason: val }, function (r) {
|
||||
layer.msg(r.msg || '操作完成');
|
||||
if (r.code === 0) { table.reload('dataTable'); }
|
||||
layer.close(index);
|
||||
}, 'json');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
{block name="title"}<title>{$title | default='YwxApp'}</title>{/block}
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
.layui-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.layui-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.layui-table-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.layui-table-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.layui-table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script type="text/javascript">
|
||||
// 全局路径配置:仅注入部署参数,路由方法来自 layui 的 route 模块。
|
||||
// app 为 ThinkPHP 真实应用目录名(backend/member/frontend/home),对应 public/static/<app> 目录。
|
||||
layui.app= window.APP = {
|
||||
root: "{$site.root|default=''}",
|
||||
assetUrl: "/assets",
|
||||
module: "{$site.module}",
|
||||
realModule: "{$site.app}",
|
||||
routeBase: "{$route_base}",
|
||||
controller: "{$site.controller}",
|
||||
action: "{$site.action}"
|
||||
};
|
||||
</script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{block name="body"}{/block}
|
||||
|
||||
{block name="script"}{/block}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>开发者管理 - 应用市场管理</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/assets/layui/css/layui.css" rel="stylesheet">
|
||||
<link href="/assets/ywxapp/css/wxapp.css" rel="stylesheet">
|
||||
<style>body{background:#f2f3f5;padding:15px;}</style>
|
||||
</head>
|
||||
<body>
|
||||
<form class="layui-form" lay-filter="filterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline" style="width:140px;">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="0">待审核</option>
|
||||
<option value="1">正常</option>
|
||||
<option value="-1">已禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="layui-btn" lay-submit lay-filter="search">搜索</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
|
||||
<script type="text/html" id="statusTpl">
|
||||
{{# if(d.status == 0){ }}<span class="layui-badge layui-bg-orange">待审核</span>
|
||||
{{# } else if(d.status == 1){ }}<span class="layui-badge layui-bg-green">正常</span>
|
||||
{{# } else { }}<span class="layui-badge">已禁用</span>{{# } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="operateTpl">
|
||||
{{# if(d.status == 0){ }}
|
||||
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="approve">通过</a>
|
||||
{{# } }}
|
||||
{{# if(d.status == 1){ }}
|
||||
<a class="layui-btn layui-btn-xs" lay-event="resend">重发令牌</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="reset">重置令牌</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="disable">禁用</a>
|
||||
{{# } }}
|
||||
{{# if(d.status == -1){ }}
|
||||
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="approve">启用</a>
|
||||
{{# } }}
|
||||
</script>
|
||||
|
||||
<script src="/assets/layui/layui.js"></script>
|
||||
<script src="/assets/ywxapp/ywxapp.js" module="{$site.app}"></script>
|
||||
<script>
|
||||
layui.use(['table', 'form', 'layer', 'jquery', 'http'], function () {
|
||||
var table = layui.table, form = layui.form, layer = layui.layer, $ = layui.jquery, http = layui.http;
|
||||
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: 'index',
|
||||
page: true,
|
||||
limits: [10, 20, 50],
|
||||
limit: 10,
|
||||
cols: [[
|
||||
{ field: 'id', title: 'ID', width: 70 },
|
||||
{ field: 'username', title: '开发者', minWidth: 140 },
|
||||
{ field: 'email', title: '邮箱', minWidth: 200 },
|
||||
{ field: 'token', title: '令牌', minWidth: 320, templet: function (d) { return '<span style="word-break:break-all;">' + d.token + '</span>'; } },
|
||||
{ field: 'status', title: '状态', width: 90, templet: '#statusTpl' },
|
||||
{ field: 'created_at', title: '创建时间', width: 170, templet: function (d) { return d.created_at ? new Date(d.created_at * 1000).toLocaleString() : ''; } },
|
||||
{ title: '操作', width: 260, toolbar: '#operateTpl', fixed: 'right' }
|
||||
]],
|
||||
response: { statusName: 'code', statusCode: 0, msgName: 'message', countName: 'count', dataName: 'data' }
|
||||
});
|
||||
|
||||
form.on('submit(search)', function (data) {
|
||||
table.reload('dataTable', { where: data.field, page: { curr: 1 } });
|
||||
return false;
|
||||
});
|
||||
|
||||
table.on('tool(dataTable)', function (elem) {
|
||||
var d = elem.data, id = d.id;
|
||||
if (elem.event === 'approve') {
|
||||
layer.confirm('确认' + (d.status == 0 ? '通过该开发者并邮件下发令牌?' : '启用该开发者?'), function () {
|
||||
http.post('approve', { id: id }).then(function (r) {
|
||||
if (r.code === 0) { layer.msg('操作成功', { icon: 1 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
} else if (elem.event === 'disable') {
|
||||
layer.confirm('确认禁用该开发者?其令牌将立即失效。', function () {
|
||||
http.post('disable', { id: id }).then(function (r) {
|
||||
if (r.code === 0) { layer.msg('已禁用', { icon: 1 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
} else if (elem.event === 'resend') {
|
||||
http.post('resend', { id: id }).then(function (r) {
|
||||
layer.msg(r.message || (r.code === 0 ? '已重发' : '失败'), { icon: r.code === 0 ? 1 : 2 });
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
} else if (elem.event === 'reset') {
|
||||
layer.confirm('确认重置令牌?旧令牌将失效。', function () {
|
||||
http.post('reset', { id: id }).then(function (r) {
|
||||
if (r.code === 0) { layer.msg('已重置' + (r.data && r.data.token ? ',新令牌:' + r.data.token : ''), { icon: 1, time: 4000 }); table.reload('dataTable'); }
|
||||
else { layer.msg(r.message || '操作失败', { icon: 2 }); }
|
||||
}).catch(function () { layer.msg('请求失败', { icon: 2 }); });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">主框架版本管理(升级服务器)</div>
|
||||
<div class="layui-card-body">
|
||||
<blockquote class="layui-elem-quote layui-quote-nm" style="margin-bottom:12px;">
|
||||
同一版本号可先后上传「整包」「增量补丁」「完整安装包」,三者共存于同一条版本记录;
|
||||
客户端升级时自动择优(正好停在补丁基础版本走补丁,否则走整包),完整安装包供全新部署下载。重新上传任一包后需再次「发布」。
|
||||
</blockquote>
|
||||
<form class="layui-form" id="uploadForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">版本号</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="version" placeholder="x.y.z" class="layui-input" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">标题</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="title" placeholder="可选,如 安全修复" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="type" id="pkgType" class="layui-input">
|
||||
<option value="0">整包(full)</option>
|
||||
<option value="1">增量补丁(patch)</option>
|
||||
<option value="2">完整安装包(install)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">基础版本</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="from_version" id="fromVersion" placeholder="仅增量补丁需要,如 1.2.0" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn" id="btnUpload">
|
||||
<i class="layui-icon layui-icon-upload"></i> 上传核心包
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">更新日志</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="changelog" class="layui-textarea" placeholder="更新内容..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'upload', 'jquery', 'layer'], function () {
|
||||
var table = layui.table, upload = layui.upload, $ = layui.jquery, layer = layui.layer;
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: '{:url("/appmall/backend/framework/index")}',
|
||||
page: true,
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 70},
|
||||
{field: 'version', title: '版本号', width: 120},
|
||||
{title: '包', width: 200, templet: function (d) {
|
||||
var h = '';
|
||||
h += d.file_path ? '<span class="layui-badge layui-bg-blue">整包</span> '
|
||||
: '<span class="layui-badge layui-bg-gray">无整包</span> ';
|
||||
h += d.patch_path ? '<span class="layui-badge layui-bg-orange">补丁</span> ' : '';
|
||||
h += d.install_path ? '<span class="layui-badge layui-bg-cyan">安装包</span>' : '';
|
||||
return h;
|
||||
}},
|
||||
{field: 'patch_from', title: '补丁基础版本', width: 110, templet: function (d) {
|
||||
return d.patch_from || d.from_version || '';
|
||||
}},
|
||||
{field: 'title', title: '标题'},
|
||||
{field: 'changelog', title: '更新日志'},
|
||||
{field: 'status', title: '状态', width: 90, templet: function (d) {
|
||||
return d.status == 1
|
||||
? '<span class="layui-badge layui-bg-green">已发布</span>'
|
||||
: '<span class="layui-badge">草稿</span>';
|
||||
}},
|
||||
{field: 'download_count', title: '下载数', width: 90},
|
||||
{title: '操作', width: 360, templet: function (d) {
|
||||
var h = '';
|
||||
if (d.status != 1) h += '<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="publish">发布</a>';
|
||||
if (d.file_path) h += '<a class="layui-btn layui-btn-xs" lay-event="download">下载整包</a>';
|
||||
if (d.patch_path) h += '<a class="layui-btn layui-btn-xs layui-btn-warm" lay-event="downloadPatch">下载补丁</a>';
|
||||
if (d.install_path) h += '<a class="layui-btn layui-btn-xs layui-bg-cyan" lay-event="downloadInstall">下载安装包</a>';
|
||||
h += '<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>';
|
||||
return h;
|
||||
}}
|
||||
]]
|
||||
});
|
||||
upload.render({
|
||||
elem: '#btnUpload',
|
||||
url: '{:url("/appmall/backend/framework/upload")}',
|
||||
accept: 'file',
|
||||
exts: 'zip',
|
||||
before: function () {
|
||||
if (!$('input[name="version"]').val()) {
|
||||
layer.msg('请先填写版本号'); return false;
|
||||
}
|
||||
if ($('#pkgType').val() == '1' && !$('#fromVersion').val()) {
|
||||
layer.msg('增量补丁必须填写基础版本'); return false;
|
||||
}
|
||||
},
|
||||
data: {
|
||||
version: function () { return $('input[name="version"]').val(); },
|
||||
title: function () { return $('input[name="title"]').val(); },
|
||||
changelog: function () { return $('textarea[name="changelog"]').val(); },
|
||||
type: function () { return $('#pkgType').val(); },
|
||||
from_version: function () { return $('#fromVersion').val(); }
|
||||
},
|
||||
done: function (res) {
|
||||
layer.msg(res.message || '完成');
|
||||
if (res.code === 0) table.reload('dataTable');
|
||||
},
|
||||
error: function () { layer.msg('上传失败'); }
|
||||
});
|
||||
table.on('tool(dataTable)', function (obj) {
|
||||
var d = obj.data;
|
||||
if (obj.event === 'publish') {
|
||||
$.post('{:url("/appmall/backend/framework/publish")}', {id: d.id}, function (r) {
|
||||
layer.msg(r.message); if (r.code === 0) table.reload('dataTable');
|
||||
});
|
||||
} else if (obj.event === 'download') {
|
||||
location.href = '{:url("/appmall/backend/framework/download")}?id=' + d.id + '&type=full';
|
||||
} else if (obj.event === 'downloadPatch') {
|
||||
location.href = '{:url("/appmall/backend/framework/download")}?id=' + d.id + '&type=patch';
|
||||
} else if (obj.event === 'downloadInstall') {
|
||||
location.href = '{:url("/appmall/backend/framework/download")}?id=' + d.id + '&type=install';
|
||||
} else if (obj.event === 'del') {
|
||||
layer.confirm('确定删除该版本?', function () {
|
||||
$.post('{:url("/appmall/backend/framework/delete")}', {id: d.id}, function (r) {
|
||||
layer.msg(r.message); if (r.code === 0) table.reload('dataTable');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
<div class="layui-card" style="margin:20px;">
|
||||
<div class="layui-card-header">收益账本(开发者分红)</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-row" id="summary" style="margin-bottom:12px;"></div>
|
||||
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'jquery'], function () {
|
||||
var table = layui.table, $ = layui.jquery;
|
||||
var summaryData = null;
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: '{:url("/appmall/backend/revenue/index")}',
|
||||
page: true,
|
||||
// Result::success 返回 code:0 / message / data:{list,summary} / count
|
||||
response: { statusName: 'code', msgName: 'message', countName: 'count', dataName: 'data' },
|
||||
parseData: function (res) {
|
||||
summaryData = (res.data && res.data.summary) ? res.data.summary : null;
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.message,
|
||||
count: res.count || 0,
|
||||
data: (res.data && res.data.list) ? res.data.list : []
|
||||
};
|
||||
},
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 70},
|
||||
{field: 'developer_id', title: '开发者ID', width: 100},
|
||||
{field: 'aid', title: '商品ID', width: 90},
|
||||
{field: 'uid', title: '购买用户', width: 90},
|
||||
{field: 'amount', title: '订单金额', width: 100},
|
||||
{field: 'commission', title: '平台抽成', width: 100},
|
||||
{field: 'income', title: '开发者收益', width: 110, templet: function(d){return '<b>'+d.income+'</b>';}},
|
||||
{field: 'status', title: '结算', width: 90, templet: function(d){return d.status==1?'<span class="layui-badge layui-bg-green">已结算</span>':'<span class="layui-badge">待结算</span>';}},
|
||||
{field: 'create_at', title: '时间', width: 170, templet: function(d){return layui.util.toDateString(d.create_at*1000);}}
|
||||
]],
|
||||
done: function () {
|
||||
if (summaryData) {
|
||||
$('#summary').html(
|
||||
'总销售额:<b>' + summaryData.total_amount + '</b> | ' +
|
||||
'平台抽成:<b>' + summaryData.total_commission + '</b> | ' +
|
||||
'开发者收益:<b style="color:#16a34a;">' + summaryData.total_income + '</b>'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
<div class="layui-card" style="margin:20px;">
|
||||
<div class="layui-card-header">提现管理(开发者分红结算)</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'jquery', 'layer'], function () {
|
||||
var table = layui.table, $ = layui.jquery, layer = layui.layer;
|
||||
table.render({
|
||||
elem: '#dataTable',
|
||||
url: '{:url("/appmall/backend/revenue/withdrawals")}',
|
||||
page: true,
|
||||
cols: [[
|
||||
{field: 'id', title: 'ID', width: 70},
|
||||
{field: 'developer_id', title: '开发者ID', width: 100},
|
||||
{field: 'amount', title: '提现金额', width: 110},
|
||||
{field: 'channel', title: '渠道', width: 90},
|
||||
{field: 'account', title: '收款账号', width: 180},
|
||||
{field: 'status', title: '状态', width: 100, templet: function(d){
|
||||
return d.status==1?'<span class="layui-badge layui-bg-green">已打款</span>'
|
||||
: (d.status==-1?'<span class="layui-badge layui-bg-orange">已驳回</span>'
|
||||
:'<span class="layui-badge">待处理</span>');
|
||||
}},
|
||||
{field: 'create_at', title: '申请时间', width: 170, templet: function(d){return layui.util.toDateString(d.create_at*1000);}},
|
||||
{title: '操作', width: 160, templet: function(d){
|
||||
if(d.status!=0) return '-';
|
||||
return '<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="settle">打款</a>'
|
||||
+ '<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="reject">驳回</a>';
|
||||
}}
|
||||
]]
|
||||
});
|
||||
table.on('tool(dataTable)', function(obj){
|
||||
var d = obj.data;
|
||||
if(obj.event==='settle'){
|
||||
layer.confirm('确认已向开发者打款?', function(){
|
||||
$.post('{:url("revenue/settle")}', {id:d.id}, function(r){layer.msg(r.message||r.msg);if(r.code===0)table.reload('dataTable');});
|
||||
});
|
||||
} else if(obj.event==='reject'){
|
||||
layer.prompt({title:'驳回原因'}, function(val, index){
|
||||
$.post('{:url("revenue/reject")}', {id:d.id, reason:val}, function(r){layer.msg(r.message||r.msg);if(r.code===0)table.reload('dataTable');layer.close(index);});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user