🚩 新增插件模板功能

This commit is contained in:
AOAOSTAR 2022-03-02 13:55:01 +08:00
parent acc7e9a3d9
commit 7e35db195e
32 changed files with 206 additions and 21 deletions

View File

@ -3,6 +3,7 @@
### 🎉 What's this
这是一款`在线工具箱`程序,您可以通过安装扩展增强她的功能
通过插件模板的功能,您也可以把她当做网页导航来使用~
觉得该项目不错的可以给个`Star`~
### 😺 演示地址
@ -17,7 +18,9 @@
> 严禁用于非法用途
### 😺 文档
[插件编写](docs/Plugin.md)
[插件编写](docs/Plugin.md)
[Github Oauth 配置](docs/Github_Oauth.md)
[Plugin Template 使用](docs/Plugin_Template.md)
### 🎊 环境要求

View File

@ -80,6 +80,7 @@ class Plugin extends BaseController
'title',
'enable',
'weight',
'template',
])->data($params)->save();
return msg('ok', 'success', $plugin);
}
@ -117,6 +118,7 @@ class Plugin extends BaseController
'title',
'enable',
'weight',
'template',
])->data($params)->save();
return msg('ok', 'success', $plugin);
}

View File

@ -72,8 +72,18 @@ class System extends BaseController
array_push($arr, basename($v));
}
}
return msg('ok', 'success', $arr);
}
public function plugin_templates()
{
$glob = glob(template_path_get() . '/template/*');
$arr = [];
foreach ($glob as $v) {
if (is_file($v)) {
array_push($arr, basename($v,'.html'));
}
}
return msg('ok', 'success', $arr);
}

View File

@ -119,6 +119,7 @@ class Plugin
$model->config = [];
$model->category_id = 0;
$model->request_count = 0;
$model->template = 'default';
}
if (is_file($this->pluginPath . '/logo.png')) {
$logoFilename = plugin_logo_path_get($this->pluginClass);

View File

@ -6,6 +6,7 @@ namespace app\model;
/**
* @property int $id
* @property int $weight 权重
* @property string $create_time 安装时间
* @property string $title 标题
* @property string $update_time 更新时间

View File

@ -10,14 +10,17 @@ use think\Model;
* Class app\model\Plugin
*
* @property int $category_id 分类
* @property int $enable 是否启用
* @property int $id
* @property int $request_count 接口请求次数
* @property int $weight 权重
* @property string $alias 插件名
* @property string $class 插件类
* @property string $config 配置信息
* @property string $create_time 安装时间
* @property string $desc 插件描述
* @property string $logo 插件logo
* @property string $template
* @property string $title 插件标题
* @property string $update_time 更新时间
* @property string $version 版本

View File

@ -12,6 +12,7 @@ namespace app\model;
* @property int $request_count 接口请求次数
* @property string $create_time 安装时间
* @property string $update_time 更新时间
* @property-read \app\model\Plugin $plugin
*/
class Request extends Base
{

View File

@ -3,4 +3,4 @@
// | 版本号
// +----------------------------------------------------------------------
define('VERSION', 'v1.2');
define('VERSION', 'v1.3');

12
docs/Github_Oauth.md Normal file
View File

@ -0,0 +1,12 @@
## Github Oauth 配置
* 打开Github并且登录你的github账号
* 打开https://github.com/settings/developers
### 创建一个Oauth APP
![](images/github_oauth_1.png)
#### 填写APP信息
![](images/github_oauth_2.png)
#### 获取`Client ID`和`Client secrets`
![](images/github_oauth_3.png)
#### 最后在安装界面填入刚刚得到的的`Client ID`和`Client secrets`即可完成`Oauth`配置

20
docs/Plugin_Template.md Normal file
View File

@ -0,0 +1,20 @@
## Plugin Template 使用
### 添加插件
![](images/plugin_template_1.png)
### 插件配置信息填入下方`JSON`数据
```json
{
"url":"https://www.aoaostar.com"
}
```
### 成功演示
![](images/plugin_template_2.png)
#### `redirect`亦是如此
## 添加新模板
### 在`view/index/default/template`添加`pluto.html`
![](images/plugin_template_3.png)
### 添加成功之后,即可在后台看到新增的模板
![](images/plugin_template_4.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 979 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -80,6 +80,7 @@ CREATE TABLE `toolbox_plugin` (
`enable` int(11) NOT NULL DEFAULT 1 COMMENT '是否启用',
`request_count` int(11) NOT NULL DEFAULT 0 COMMENT '接口请求次数',
`category_id` int(11) NOT NULL DEFAULT 0 COMMENT '分类',
`template` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'default',
`create_time` datetime NOT NULL COMMENT '安装时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
@ -90,7 +91,7 @@ CREATE TABLE `toolbox_plugin` (
-- ----------------------------
-- Records of toolbox_plugin
-- ----------------------------
INSERT INTO `toolbox_plugin` VALUES (1, 'HelloPluto', '/static/icons/aoaostar_com_example.png', 'If you see this message, it means that your program is running properly', 'example', 'aoaostar_com\\example', '{}', 'v1.0', 0, 1, 0, 1, '2021-12-22 18:38:35', '2021-12-25 13:50:14');
INSERT INTO `toolbox_plugin` VALUES (1, 'HelloPluto', '/static/icons/aoaostar_com_example.png', 'If you see this message, it means that your program is running properly', 'example', 'aoaostar_com\\example', '{}', 'v1.0', 0, 1, 0, 1, 'default', '2021-12-22 18:38:35', '2021-12-25 13:50:14');
-- ----------------------------
-- Table structure for toolbox_request

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>傲星工具箱 - 后台管理中心</title>
<title>后台管理中心 - 傲星工具箱</title>
<meta name="keywords" content="傲星工具箱,在线工具箱,aoaostar,pluto">
<meta name="description" content="这是一个非常Nice的在线工具箱">
<meta name="renderer" content="webkit">

View File

@ -149,6 +149,11 @@ const system_get = () => {
const templates_get = () => {
return httpGet('/master/system/templates')
}
const plugin_templates_get = () => {
return httpGet('/master/system/plugin_templates')
}
const ota_check = () => {
return httpGet('/master/ota/check')
}

View File

@ -21,6 +21,11 @@
padding: .5rem;
}
.ota-console {
max-width: 600px;
max-height: 400px;
}
</style>
</head>
<body>
@ -112,7 +117,8 @@
layer.open({
type: 1,
title: '在线更新',
area: ['600px', '400px'],
area: ['85vw', '400px'],
skin: 'ota-console',
shade: 0.8,
id: 'ota_update',
content: `<div class="console" id="console"></div>`,

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>layui</title>
<title>插件管理</title>
<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">
@ -118,6 +118,7 @@
return '默认分组'
}
},
{field: 'template', width: 100, title: '模板', sort: true, align: 'center'},
{
field: 'config', width: 150, title: '配置信息', align: 'center', templet: function (res) {
return JSON.stringify(res.config, null, 4)

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>layui</title>
<title>添加插件</title>
<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">
@ -29,7 +29,7 @@
<div class="layui-form-item">
<label class="layui-form-label">logo:</label>
<div class="layui-input-block">
<input type="text" name="logo" placeholder="请输入插件logo" value="/static/images/plugin_default.jpg"
<input type="text" name="logo" placeholder="请输入插件logo" value="/static/images/plugin_default.png"
lay-verify="required" lay-reqtext="插件logo不能为空"
class="layui-input" required>
<tip>插件Logo无需多言</tip>
@ -56,7 +56,7 @@
<div class="layui-form-item">
<label class="layui-form-label">插件版本号:</label>
<div class="layui-input-block">
<input type="text" name="version" placeholder="请填写插件版本号" value=""
<input type="text" name="version" placeholder="请填写插件版本号" value="v1.0"
lay-verify="required" lay-reqtext="插件版本号不能为空"
class="layui-input" required>
<tip>插件的版本号例如v1.0。</tip>
@ -65,7 +65,7 @@
<div class="layui-form-item">
<label class="layui-form-label">插件权重:</label>
<div class="layui-input-block">
<input type="number" name="weight" placeholder="请填写插件权重" value=""
<input type="number" name="weight" placeholder="请填写插件权重" value="0"
lay-verify="required" lay-reqtext="插件权重不能为空"
class="layui-input" required>
<tip>填数字,数字越大排名越前。</tip>
@ -74,7 +74,7 @@
<div class="layui-form-item">
<label class="layui-form-label">是否启用:</label>
<div class="layui-input-block">
<input type="checkbox" value="1" name="enable" lay-skin="switch" lay-filter="switchTest" lay-text="ON|OFF">
<input type="checkbox" value="1" name="enable" lay-skin="switch" lay-filter="switchTest" lay-text="ON|OFF" checked>
<tip>禁用则不会在前台显示。</tip>
</div>
</div>
@ -101,6 +101,15 @@
<tip>插件描述信息,介绍一下这个插件是干什么的吧!</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">模板:</label>
<div class="layui-input-block">
<select lay-verify="required" name="template" id="templates" lay-verify="required" lay-reqtext="请选择模板">
</select>
<tip>默认运行本地插件可通过设置模板实现iframe或redirect功能</tip>
<tip>模板位于view/index/default/template目录</tip>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveBtn">确认保存</button>
@ -115,6 +124,14 @@
{{# }); }}
</select>
</script>
<script id="templates_tpl" type="text/html">
<select lay-verify="required" name="category_id">
<option value="default">default</option>
{{# layui.each(d, function(index, item){ }}
<option value="{{ item }}">{{ item }}</option>
{{# }); }}
</select>
</script>
<script src="../../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script src="../../js/api.js?v=1.0.0" charset="utf-8"></script>
<script src="../../js/common.js?v=1.0.0" charset="utf-8"></script>
@ -125,15 +142,26 @@
layer = layui.layer,
laytpl = layui.laytpl,
$ = layui.$;
layer.load(1)
categories_get().then(res => {
const getTpl = document.getElementById('categoies_tpl').innerHTML,
let tasks = []
tasks.push(categories_get().then(res => {
var getTpl = document.getElementById('categoies_tpl').innerHTML,
view = document.getElementById('categories');
laytpl(getTpl).render(res.data.items, function (html) {
view.innerHTML = html;
form.render();
});
}).finally(() => {
}))
tasks.push(plugin_templates_get().then(res => {
var getTpl = document.getElementById('templates_tpl').innerHTML,
view = document.getElementById('templates');
laytpl(getTpl).render(res.data, function (html) {
view.innerHTML = html;
form.render();
});
}))
Promise.all(tasks).finally(() => {
layer.closeAll('loading')
})
//监听提交

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>layui</title>
<title>编辑插件</title>
<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">
@ -35,7 +35,7 @@
<div class="layui-form-item">
<label class="layui-form-label">logo:</label>
<div class="layui-input-block">
<input type="text" name="logo" placeholder="请输入插件logo" value="/static/images/plugin_default.jpg"
<input type="text" name="logo" placeholder="请输入插件logo" value="/static/images/plugin_default.png"
lay-verify="required" lay-reqtext="插件logo不能为空"
class="layui-input" required>
<tip>插件Logo无需多言</tip>
@ -71,7 +71,7 @@
<div class="layui-form-item">
<label class="layui-form-label">插件权重:</label>
<div class="layui-input-block">
<input type="number" name="weight" placeholder="请填写插件权重" value=""
<input type="number" name="weight" placeholder="请填写插件权重" value="0"
lay-verify="required" lay-reqtext="插件权重不能为空"
class="layui-input" required>
<tip>填数字,数字越大排名越前。</tip>
@ -108,6 +108,15 @@
<tip>插件描述信息,介绍一下这个插件是干什么的吧!</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">模板:</label>
<div class="layui-input-block">
<select lay-verify="required" name="template" id="templates" lay-verify="required" lay-reqtext="请选择模板">
</select>
<tip>默认运行本地插件可通过设置模板实现iframe或redirect功能</tip>
<tip>模板位于view/index/default/template目录</tip>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveBtn">确认保存</button>
@ -122,6 +131,14 @@
{{# }); }}
</select>
</script>
<script id="templates_tpl" type="text/html">
<select lay-verify="required" name="category_id">
<option value="default">default</option>
{{# layui.each(d, function(index, item){ }}
<option value="{{ item }}">{{ item }}</option>
{{# }); }}
</select>
</script>
<script src="../../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script src="../../js/api.js?v=1.0.0" charset="utf-8"></script>
<script src="../../js/common.js?v=1.0.0" charset="utf-8"></script>
@ -142,6 +159,14 @@
form.render();
});
}))
tasks.push(plugin_templates_get().then(res => {
var getTpl = document.getElementById('templates_tpl').innerHTML,
view = document.getElementById('templates');
laytpl(getTpl).render(res.data, function (html) {
view.innerHTML = html;
form.render();
});
}))
tasks.push(
plugin_get(getQueryString('id')).then(res => {
if (res.status === 'ok') {

View File

@ -20,6 +20,7 @@ Route::group('master', function () {
Route::delete('/category', 'Category/delete');
Route::get('/system/templates', 'System/templates');
Route::get('/system/plugin_templates', 'System/plugin_templates');
Route::get('/system/info', 'System/info');
Route::get('/system', 'System/all');
Route::post('/system', 'System/update');

View File

@ -25,9 +25,13 @@ Route::get(':alias', function () {
abort(404, '页面异常');
}
$template = plugin_template_path_get($path);
$model = plugin_info_get($alias);
View::assign([
"plugin" => plugin_info_get($alias)
"plugin" => $model
]);
if ($model->template !== 'default') {
$template = template_path_get() . 'template/' . $model->template . '.html';
}
return view($template);
})->pattern(['alias' => '[\w|\-/]+'])
->middleware(\app\middleware\View::class);

4
runtime/.gitignore vendored
View File

@ -1,2 +1,4 @@
*
!.gitignore
!.gitignore
!update
!update/**

View File

@ -0,0 +1,5 @@
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE `toolbox_plugin` ADD COLUMN `template` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'default' AFTER `category_id`;
SET FOREIGN_KEY_CHECKS=1;

View File

@ -15,7 +15,7 @@
<script src="/static/http.js?v={:get_version()}"></script>
<script src="/static/common.js?v={:get_version()}"></script>
<script src="/static/api.js?v={:get_version()}"></script>
<link rel="stylesheet" href="/static/style.css?v={:time()}">
<link rel="stylesheet" href="/static/style.css?v={:get_version()}">
{block name="head"}{/block}
</head>
<body>

View File

@ -0,0 +1,20 @@
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{$plugin.title} - {$app.title}</title>
<link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="keywords" content="{$plugin.title},{$app.keywords}">
<meta name="description" content="{$plugin.desc}">
<meta name="author" content="Pluto" />
<style>
*{
margin: 0;
}
</style>
</head>
<body>
<iframe src="{$plugin.config->url}" frameborder="0" width="100%" height="100%"></iframe>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="refresh" content="3;url='{$plugin.config->url}';">
<meta charset="utf-8">
<title>页面加载中,请稍候...</title>
<link rel="icon" href="/favicon.ico"/>
<meta name="keywords" content="{$plugin.title},{$app.keywords}">
<meta name="description" content="{$plugin.desc}">
<meta name="author" content="Pluto" />
<script type="text/javascript">
var msg = document.title;
msg = "" + msg;pos = 0;
function scrollMSG() {
document.title = msg.substring(pos, msg.length) + msg.substring(0, pos);
pos++;
if (pos > msg.length) pos = 0
window.setTimeout("scrollMSG()",200);
}
scrollMSG();
</script>
<style>body{overflow:hidden;background:#666}.container{display:flex;justify-content:center;align-items:center;height:100vh;overflow:hidden;animation-delay:1s}.item-1{width:20px;height:20px;background:#f583a1;border-radius:50%;background-color:#eed968;margin:7px;display:flex;justify-content:center;align-items:center}@keyframes scale{0%{transform:scale(1)}50%,75%{transform:scale(3.5)}78%,100%{opacity:0}}.item-1:before{content:'';width:20px;height:20px;border-radius:50%;background-color:#eed968;opacity:.7;animation:scale 2s infinite cubic-bezier(0,0,0.49,1.02);animation-delay:200ms;transition:.5s all ease;transform:scale(1)}.item-2{width:20px;height:20px;background:#f583a1;border-radius:50%;background-color:#eece68;margin:7px;display:flex;justify-content:center;align-items:center}@keyframes scale{0%{transform:scale(1)}50%,75%{transform:scale(3.5)}78%,100%{opacity:0}}.item-2:before{content:'';width:20px;height:20px;border-radius:50%;background-color:#eece68;opacity:.7;animation:scale 2s infinite cubic-bezier(0,0,0.49,1.02);animation-delay:400ms;transition:.5s all ease;transform:scale(1)}.item-3{width:20px;height:20px;background:#f583a1;border-radius:50%;background-color:#eec368;margin:7px;display:flex;justify-content:center;align-items:center}@keyframes scale{0%{transform:scale(1)}50%,75%{transform:scale(3.5)}78%,100%{opacity:0}}.item-3:before{content:'';width:20px;height:20px;border-radius:50%;background-color:#eec368;opacity:.7;animation:scale 2s infinite cubic-bezier(0,0,0.49,1.02);animation-delay:600ms;transition:.5s all ease;transform:scale(1)}.item-4{width:20px;height:20px;background:#f583a1;border-radius:50%;background-color:#eead68;margin:7px;display:flex;justify-content:center;align-items:center}@keyframes scale{0%{transform:scale(1)}50%,75%{transform:scale(3.5)}78%,100%{opacity:0}}.item-4:before{content:'';width:20px;height:20px;border-radius:50%;background-color:#eead68;opacity:.7;animation:scale 2s infinite cubic-bezier(0,0,0.49,1.02);animation-delay:800ms;transition:.5s all ease;transform:scale(1)}.item-5{width:20px;height:20px;background:#f583a1;border-radius:50%;background-color:#ee8c68;margin:7px;display:flex;justify-content:center;align-items:center}@keyframes scale{0%{transform:scale(1)}50%,75%{transform:scale(3.5)}78%,100%{opacity:0}}.item-5:before{content:'';width:20px;height:20px;border-radius:50%;background-color:#ee8c68;opacity:.7;animation:scale 2s infinite cubic-bezier(0,0,0.49,1.02);animation-delay:1000ms;transition:.5s all ease;transform:scale(1)}</style>
</head>
<body>
<div class="container">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
<div class="item-5"></div>
</div>
</body>
</html>