第三段完善 代理商绑定 激活等 正式上线的版本

This commit is contained in:
huangzhenpc
2025-02-13 15:25:19 +08:00
parent abfa783907
commit d1b0a26dd2
31 changed files with 2729 additions and 641 deletions

View File

@@ -56,22 +56,51 @@ class Config extends Controller
*/
public function index()
{
$this->title = '系统参数配置';
$this->files = Storage::types();
$this->plugins = Plugin::get(null, true);
$this->issuper = AdminService::isSuper();
$this->systemid = ModuleService::getRunVar('uni');
$this->framework = ModuleService::getLibrarys('topthink/framework');
$this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library');
if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) {
$url = url('admin/index/pass', ['id' => AdminService::getUserId()]);
$this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>", [$url]);
if ($this->request->isGet()) {
$this->title = '系统参数配置';
$this->files = Storage::types();
$this->plugins = Plugin::get(null, true);
$this->issuper = AdminService::isSuper();
$this->systemid = ModuleService::getRunVar('uni');
$this->framework = ModuleService::getLibrarys('topthink/framework');
$this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library');
if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) {
$url = url('admin/index/pass', ['id' => AdminService::getUserId()]);
$this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即<a data-modal='%s'>修改密码</a>", [$url]);
}
uasort($this->plugins, static function ($a, $b) {
if ($a['space'] === $b['space']) return 0;
return $a['space'] > $b['space'] ? 1 : -1;
});
$this->fetch();
} else {
$data = $this->request->post();
foreach ($data as $k => $v) {
sysconf($k, $v);
}
$this->success('参数修改成功!');
}
uasort($this->plugins, static function ($a, $b) {
if ($a['space'] === $b['space']) return 0;
return $a['space'] > $b['space'] ? 1 : -1;
});
$this->fetch();
}
/**
* 获取系统配置参数
* @param string $name 配置名称
* @return mixed
*/
public function get($name)
{
return sysconf($name);
}
/**
* 修改系统配置参数
* @param string $name 配置名称
* @param mixed $value 配置值
* @return mixed
*/
public function set($name, $value)
{
return sysconf($name, $value);
}
/**

View File

@@ -82,18 +82,48 @@ class Account extends Controller
]);
}
// 检查设备是否在冷却期
$cooldownKey = "device_cooldown_{$machineId}";
$isInCooldown = \think\facade\Cache::get($cooldownKey);
// 优先检查缓存中是否有该设备最近使用的账号
$cacheKey = "device_account_{$machineId}";
$cachedAccount = \think\facade\Cache::get($cacheKey);
if ($cachedAccount) {
// 检查账号是否仍然可用
$account = Db::name('cursor_accounts')
->where('id', $cachedAccount['id'])
->find();
if ($account) {
// 更新缓存时间
\think\facade\Cache::set($cacheKey, $account, 600); // 10分钟缓存
// 查询设备激活状态用于计算过期时间
$activations = Db::name('cursor_activation_codes')
->where('used_by', '=', $machineId)
->where('is_used', '=', 1)
->order('used_at desc')
->select()
->toArray();
if ($isInCooldown) {
return json([
'code' => 429,
'msg' => '请求过于频繁,请稍后再试',
'data' => [
'cooldown_expires' => date('Y-m-d H:i:s', $isInCooldown)
]
]);
if (!empty($activations)) {
$totalDays = array_sum(array_column($activations, 'days'));
$firstActivationTime = strtotime($activations[count($activations)-1]['used_at']);
$expireTime = $firstActivationTime + ($totalDays * 24 * 3600);
// 返回账号信息
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'email' => $account['email'],
'password' => $account['password'],
'access_token' => $account['access_token'],
'refresh_token' => $account['refresh_token'],
'expire_time' => date('Y-m-d H:i:s', $expireTime),
'days_left' => ceil(($expireTime - time()) / 86400)
]
]);
}
}
}
// 查询设备激活状态
@@ -124,39 +154,18 @@ class Account extends Controller
]);
}
// 检查缓存中是否有该设备最近使用的账号
$cacheKey = "device_account_{$machineId}";
$cachedAccount = \think\facade\Cache::get($cacheKey);
if ($cachedAccount) {
// 检查账号是否仍然可用
$account = Db::name('cursor_accounts')
->where('id', $cachedAccount['id'])
->find();
if ($account) {
// 更新缓存时间
\think\facade\Cache::set($cacheKey, $account, 600); // 10分钟缓存
// 返回账号信息
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'email' => $account['email'],
'password' => $account['password'],
'access_token' => $account['access_token'],
'refresh_token' => $account['refresh_token'],
'expire_time' => date('Y-m-d H:i:s', $expireTime),
'days_left' => ceil(($expireTime - time()) / 86400)
]
]);
}
// 在分配新账号前检查冷却期
$cooldownKey = "device_cooldown_{$machineId}";
$isInCooldown = \think\facade\Cache::get($cooldownKey);
if ($isInCooldown) {
return json([
'code' => 429,
'msg' => '请求过于频繁,请稍后再试',
'data' => [
'cooldown_expires' => date('Y-m-d H:i:s', intval($isInCooldown))
]
]);
}
// 记录冷却期
$cooldownExpires = time() + 1800; // 30分钟冷却期
\think\facade\Cache::set($cooldownKey, $cooldownExpires, 1800);
// 开启事务
Db::startTrans();
@@ -195,6 +204,10 @@ class Account extends Controller
'used_by' => $machineId
]);
// 设置冷却期(仅在成功分配新账号时)
$cooldownExpires = time() + 1800; // 30分钟冷却期
\think\facade\Cache::set($cooldownKey, $cooldownExpires, 1800);
Db::commit();
// 缓存新账号信息

View File

@@ -45,6 +45,17 @@ class Mail extends Controller
'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36',
'MAIL_SERVER' => 'https://tempmail.plus'
]
] ,
[
'name' => '备用配置3',
'config' => [
'DOMAIN' => 'jxyweb.site',
'TEMP_MAIL' => 'exvet',
'TEMP_MAIL_EXT' => '@mailto.plus',
'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36',
'MAIL_SERVER' => 'https://tempmail.plus',
'TEMP_MAIL_EPIN'=>'889944'
]
]
];

View File

@@ -205,16 +205,31 @@ class Member extends Controller
return json(['code' => 400, 'msg' => '设备ID不能为空']);
}
try {
// 获取Redis实例
$redis = cache('redis');
$cacheKey = "device_status:{$machineId}";
// 尝试获取缓存
if ($redis && $statusInfo = $redis->get($cacheKey)) {
return json(json_decode($statusInfo, true));
}
} catch (\Exception $e) {
// Redis异常时记录日志但继续执行
trace("Redis异常: " . $e->getMessage(), 'error');
}
// 获取该设备所有的激活记录
$activations = Db::name('cursor_activation_codes')
->where('used_by', '=', $machineId)
->where('is_used', '=', 1)
->field(['days', 'used_at', 'code', 'device_info'])
->order('used_at desc')
->select()
->toArray();
if (empty($activations)) {
return json([
$result = [
'code' => 401,
'msg' => '未激活',
'data' => [
@@ -224,7 +239,16 @@ class Member extends Controller
'days_left' => 0,
'activation_records' => []
]
]);
];
// 未激活状态缓存1分钟
try {
if ($redis) {
$redis->set($cacheKey, json_encode($result), 60);
}
} catch (\Exception $e) {
trace("Redis缓存写入异常: " . $e->getMessage(), 'error');
}
return json($result);
}
// 计算总天数
@@ -232,13 +256,13 @@ class Member extends Controller
// 计算最终到期时间
$now = time();
$firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); // 第一次激活时间
$expireTime = $firstActivationTime + ($totalDays * 24 * 3600); // 总天数转换为秒数
$firstActivationTime = strtotime($activations[count($activations)-1]['used_at']);
$expireTime = $firstActivationTime + ($totalDays * 24 * 3600);
$daysLeft = ceil(($expireTime - $now) / 86400);
// 判断是否过期
if ($daysLeft <= 0) {
return json([
$result = [
'code' => 401,
'msg' => '已过期',
'data' => [
@@ -256,11 +280,20 @@ class Member extends Controller
];
}, $activations)
]
]);
];
// 过期状态缓存1分钟
try {
if ($redis) {
$redis->set($cacheKey, json_encode($result), 60);
}
} catch (\Exception $e) {
trace("Redis缓存写入异常: " . $e->getMessage(), 'error');
}
return json($result);
}
// 返回正常状态
return json([
// 正常状态返回
$result = [
'code' => 200,
'msg' => '正常',
'data' => [
@@ -278,12 +311,21 @@ class Member extends Controller
];
}, $activations)
]
]);
];
// 正常状态缓存5分钟
try {
if ($redis) {
$redis->set($cacheKey, json_encode($result), 300);
}
} catch (\Exception $e) {
trace("Redis缓存写入异常: " . $e->getMessage(), 'error');
}
return json($result);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => $e->getMessage()
'msg' => '系统异常,请稍后重试'
]);
}
}

View File

@@ -0,0 +1,137 @@
<?php
declare (strict_types = 1);
namespace app\admin\controller\api;
use app\admin\model\Version as VersionModel;
use think\admin\Controller;
use think\facade\Request;
/**
* 版本管理接口
* @auth true
*
* 接口说明:
* ====================================================
* 接口域名:{:sysconf('site_domain')}
*
* 公共返回参数:
* - code: 错误码0表示成功非0表示失败
* - msg: 提示信息
* - data: 返回的数据,请求失败时可能为空
*
* 错误码说明:
* - 0: 成功
* - 1: 一般性错误(具体错误信息见msg)
* - 401: 未授权或授权失败
* - 404: 请求的资源不存在
* - 500: 服务器内部错误
*
* 版本号格式x.x.x (例如: 3.4.1)
* 平台类型:
* - all: 全平台
* - windows: Windows平台
* - mac: Mac平台
* - linux: Linux平台
* ====================================================
*
* 1. 获取最新版本 [GET] /admin/api.version/latest
* 请求参数:
* - platform: 平台类型(all|windows|mac|linux), 默认为all
* 返回数据:
* {
* "code": 0,
* "msg": "获取成功",
* "data": {
* "id": "1",
* "version_no": "3.4.1.4",
* "version_name": "听泉cursor助手",
* "download_url": "http://domain/upload/xxx.exe",
* "is_force": 1, // 是否强制更新(1是,0否)
* "min_version": "3.4.0.0", // 最低要求版本
* "platform": "all", // 平台类型
* "description": "版本描述", // 版本描述
* "status": 1, // 状态(1启用,0禁用)
* "create_time": "2024-03-20 10:00:00"
* }
* }
*
* 2. 检查版本更新 [GET] /admin/api.version/check
* 请求参数:
* - version: 当前版本号(必填)
* - platform: 平台类型(all|windows|mac|linux), 默认为all
* 返回数据:
* {
* "code": 0,
* "msg": "检查完成",
* "data": {
* "has_update": true, // 是否有更新
* "is_force": 1, // 是否强制更新
* "version_info": { // 新版本信息(has_update为true时返回)
* // 同上面的版本信息
* }
* }
* }
*
* 错误返回示例:
* {
* "code": 1,
* "msg": "请提供当前版本号",
* "data": null
* }
*/
class Version extends Controller
{
/**
* 获取最新版本
* @return void
*/
public function latest()
{
$platform = $this->request->param('platform', 'all');
$version = VersionModel::mk()->getLatestVersion($platform);
if (!$version) {
$this->error('暂无版本信息');
}
// 处理下载地址
$version['download_url'] = $this->getFullUrl($version['download_url']);
$this->success('获取成功', $version);
}
/**
* 检查更新
* @return void
*/
public function check()
{
$currentVersion = $this->request->param('version');
$platform = $this->request->param('platform', 'all');
if (empty($currentVersion)) {
$this->error('请提供当前版本号');
}
$result = VersionModel::mk()->checkUpdate($currentVersion, $platform);
if (isset($result['error'])) {
$this->error($result['error']);
}
// 如果有更新,处理下载地址
if ($result['has_update'] && isset($result['version_info'])) {
$result['version_info']['download_url'] = $this->getFullUrl($result['version_info']['download_url']);
}
$this->success('检查完成', $result);
}
/**
* 获取完整的下载地址
* @param string $path 相对路径
* @return string
*/
protected function getFullUrl($path)
{
return sysconf('site_domain') . $path;
}
}

167
app/admin/model/Version.php Normal file
View File

@@ -0,0 +1,167 @@
<?php
declare (strict_types = 1);
namespace app\admin\model;
use think\admin\Model;
class Version extends Model
{
protected $name = 'version';
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 版本状态
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
/**
* 获取下一个版本号
* @param string $currentVersion 当前版本号
* @return string
*/
public static function getNextVersion($currentVersion = '')
{
if (empty($currentVersion)) {
// 获取最新版本号
$latest = self::order('version_no DESC')->value('version_no');
$currentVersion = $latest ?: '1.0.0';
}
// 拆分版本号
$parts = explode('.', $currentVersion);
$major = intval($parts[0] ?? 1);
$minor = intval($parts[1] ?? 0);
$patch = intval($parts[2] ?? 0);
// 增加修订号
$patch++;
// 如果修订号超过99,增加次版本号
if ($patch > 99) {
$minor++;
$patch = 0;
}
// 如果次版本号超过99,增加主版本号
if ($minor > 99) {
$major++;
$minor = 0;
}
return sprintf('%d.%d.%d', $major, $minor, $patch);
}
/**
* 生成版本名称
* @param string $version_no 版本号
* @param string $filename 文件名
* @return string
*/
public static function generateVersionName($version_no, $filename = '')
{
// 如果有文件名,尝试从文件名中提取有意义的部分
if ($filename) {
// 移除扩展名
$name = pathinfo($filename, PATHINFO_FILENAME);
// 移除版本号部分 (匹配v后面的所有数字和点)
$name = preg_replace('/v\d+[\d\.]+/', '', $name);
$name = trim($name);
if (!empty($name)) {
return $name;
}
}
// 默认使用版本号作为名称
return sprintf('版本 %s', $version_no);
}
/**
* 从文件名解析版本号
* @param string $filename
* @return string
*/
public static function parseVersionFromFilename($filename)
{
// 移除扩展名
$name = pathinfo($filename, PATHINFO_FILENAME);
// 匹配版本号 (匹配v后面的所有数字和点)
if (preg_match('/v(\d+[\d\.]+)/', $name, $matches)) {
return $matches[1];
}
// 如果没有找到版本号,使用当前最新版本号加1
return self::getNextVersion();
}
/**
* 获取最新版本信息
* @param string $platform 平台
* @return array|null
*/
public function getLatestVersion($platform = 'all')
{
return $this->where([
['status', '=', self::STATUS_ENABLED],
['platform', 'in', [$platform, 'all']]
])->order('version_no DESC')->find();
}
/**
* 检查版本更新
* @param string $currentVersion 当前版本
* @param string $platform 平台
* @return array
*/
public function checkUpdate($currentVersion, $platform = 'all')
{
$latest = $this->getLatestVersion($platform);
if (!$latest) {
return ['has_update' => false];
}
// 版本号比较
if (version_compare($latest['version_no'], $currentVersion) > 0) {
// 检查是否满足最低版本要求
if ($latest['min_version'] && version_compare($currentVersion, $latest['min_version']) < 0) {
return [
'has_update' => false,
'error' => '当前版本过低,请先更新到' . $latest['min_version'] . '版本'
];
}
return [
'has_update' => true,
'is_force' => $latest['is_force'],
'version_info' => $latest
];
}
return ['has_update' => false];
}
/**
* 检查版本权限
* @param int $versionId 版本ID
* @param int $targetId 目标ID
* @param string $targetType 目标类型
* @return bool
*/
public function checkVersionPermission($versionId, $targetId, $targetType)
{
$control = \think\facade\Db::name('version_control')
->where([
['version_id', '=', $versionId],
['target_id', '=', $targetId],
['target_type', '=', $targetType],
['expire_time', '>', date('Y-m-d H:i:s')]
])
->find();
if (!$control) {
return true;
}
return $control['control_type'] === 'white_list';
}
}

View File

@@ -11,224 +11,234 @@
{/block}
{block name="content"}
<!--{notempty name='issuper'}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('运行模式')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<form onsubmit="return false;" data-auto="true" method="post" class='layui-form layui-card' autocomplete="off">
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
<!--{if $app->isDebug()}-->
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到生产模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=1">{:lang('以生产模式运行')}</a>
<!--{else}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到开发模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=0">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以生产模式运行')}</a>
<!--{/if}-->
</div>
<div class="margin-top-20">
<p><b>{:lang('开发模式')}</b>{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}</p>
<p><b>{:lang('生产模式')}</b>{:lang('项目正式部署上线后使用,系统异常时统一显示 “%s”只记录重要的异常日志信息强烈推荐上线后使用此模式。',[config('app.error_message')])}</p>
</div>
</div>
</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('富编辑器')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<div class="layui-card-body layui-clear">
<div class="layui-btn-group shadow-mini">
{if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if}
{foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k}
{if auth('storage')}<a data-title="配置{$v}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-action="{:url('admin/api.system/editor')}" data-value="editor#{$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width pull-left">
<p><b>CKEditor4</b>{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}</p>
<p><b>CKEditor5</b>{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}</p>
<p><b>wangEditor</b>{:lang('国产优质富文本编辑器对于小程序及App内容支持会更友好推荐使用。')}</p>
<p><b>{:lang('自适应模式')}</b>{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}</p>
</div>
</div>
</div>
<!--{/notempty}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('存储引擎')}</b>( {:lang('文件默认存储方式')} )
</span>
</div>
<!-- 初始化存储配置 -->
{if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if}
{if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
{if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
{if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if}
{if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if}
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
{foreach $files as $k => $v}{if sysconf('storage.type') eq $k}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width">
<p><b>{:lang('本地服务器存储')}</b>{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}</p>
<p><b>{:lang('自建Alist存储')}</b>{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}</p>
<p><b>{:lang('七牛云对象存储')}</b>{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('又拍云USS存储')}</b>{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('阿里云OSS存储')}</b>{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('腾讯云COS存储')}</b>{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
</div>
</div>
</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统参数')}</b>( {:lang('当前系统配置参数')} )
</span>
</div>
<div class="layui-card-body">
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站名称')}</b>Website</div>
<label class="relative block">
<input readonly value="{:sysconf('site_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序名称')}</b>Name</div>
<label class="relative block">
<input readonly value="{:sysconf('app_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序名称,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序版本')}</b>Version</div>
<label class="relative block">
<input readonly value="{:sysconf('app_version')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_version')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序版本,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('公安备案号')}</b>Beian</div>
<label class="relative block">
<input readonly value="{:sysconf('beian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('beian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<p class="help-block">
{:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['<a target="_blank" href="https://www.beian.gov.cn/portal/registerSystemInfo">www.beian.gov.cn</a>'])}
</p>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站备案号')}</b>Miitbeian</div>
<label class="relative block">
<input readonly value="{:sysconf('miitbeian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('miitbeian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">
{:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['<a target="_blank" href="https://beian.miit.gov.cn">beian.miit.gov.cn</a>'])}
<!--{notempty name='issuper'}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('运行模式')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
<!--{if $app->isDebug()}-->
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到生产模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=1">{:lang('以生产模式运行')}</a>
<!--{else}-->
<a class="layui-btn layui-btn-sm layui-btn-primary" data-confirm="{:lang('确定要切换到开发模式运行吗?')}" data-load="{:url('admin/api.system/debug')}?state=0">{:lang('以开发模式运行')}</a>
<a class="layui-btn layui-btn-sm layui-btn-active">{:lang('以生产模式运行')}</a>
<!--{/if}-->
</div>
<div class="margin-top-20">
<p><b>{:lang('开发模式')}</b>{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}</p>
<p><b>{:lang('生产模式')}</b>{:lang('项目正式部署上线后使用,系统异常时统一显示 "%s",只记录重要的异常日志信息,强烈推荐上线后使用此模式。',[config('app.error_message')])}</p>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站版权信息')}</b>Copyright</div>
<label class="relative block">
<input readonly value="{:sysconf('site_copy')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_copy')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('富编辑器')}</b>( {:lang('仅超级管理员可配置')} )
</span>
</div>
<div class="layui-card-body layui-clear">
<div class="layui-btn-group shadow-mini">
{if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if}
{foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k}
{if auth('storage')}<a data-title="配置{$v}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-action="{:url('admin/api.system/editor')}" data-value="editor#{$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width pull-left">
<p><b>CKEditor4</b>{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}</p>
<p><b>CKEditor5</b>{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}</p>
<p><b>wangEditor</b>{:lang('国产优质富文本编辑器对于小程序及App内容支持会更友好推荐使用。')}</p>
<p><b>{:lang('自适应模式')}</b>{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}</p>
</div>
</div>
</div>
<!--{/notempty}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('存储引擎')}</b>( {:lang('文件默认存储方式')} )
</span>
</div>
<!-- 初始化存储配置 -->
{if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if}
{if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if}
{if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if}
{if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if}
{if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if}
<div class="layui-card-body">
<div class="layui-btn-group shadow-mini nowrap">
{foreach $files as $k => $v}{if sysconf('storage.type') eq $k}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-active">{$v}</a>{/if}
{else}
{if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{else}<a class="layui-btn layui-btn-sm layui-btn-primary">{$v}</a>{/if}
{/if}{/foreach}
</div>
<div class="margin-top-20 full-width">
<p><b>{:lang('本地服务器存储')}</b>{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}</p>
<p><b>{:lang('自建Alist存储')}</b>{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}</p>
<p><b>{:lang('七牛云对象存储')}</b>{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('又拍云USS存储')}</b>{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('阿里云OSS存储')}</b>{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
<p><b>{:lang('腾讯云COS存储')}</b>{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}</p>
</div>
</div>
</div>
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统参数')}</b>( {:lang('当前系统配置参数')} )
</span>
</div>
<div class="layui-card-body">
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站名称')}</b>Website</div>
<label class="relative block">
<input readonly value="{:sysconf('site_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序名称')}</b>Name</div>
<label class="relative block">
<input readonly value="{:sysconf('app_name')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_name')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序名称,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('管理程序版本')}</b>Version</div>
<label class="relative block">
<input readonly value="{:sysconf('app_version')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('app_version')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('管理程序版本,将显示在后台左上角标题。')}</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('公安备案号')}</b>Beian</div>
<label class="relative block">
<input readonly value="{:sysconf('beian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('beian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<p class="help-block">
{:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['<a target="_blank" href="https://www.beian.gov.cn/portal/registerSystemInfo">www.beian.gov.cn</a>'])}
</p>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站备案号')}</b>Miitbeian</div>
<label class="relative block">
<input readonly value="{:sysconf('miitbeian')?:'-'}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('miitbeian')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">
{:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['<a target="_blank" href="https://beian.miit.gov.cn">beian.miit.gov.cn</a>'])}
</div>
</div>
<div class="layui-form-item">
<div class="help-label"><b>{:lang('网站版权信息')}</b>Copyright</div>
<label class="relative block">
<input readonly value="{:sysconf('site_copy')}" class="layui-input layui-bg-gray">
<a data-copy="{:sysconf('site_copy')}" class="layui-icon layui-icon-release input-right-icon"></a>
</label>
<div class="help-block">{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}</div>
</div>
</div>
</div>
<!--{if $app->isDebug()}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统信息')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<tbody>
<tr>
<th class="nowrap text-center">{:lang('核心框架')}</th>
<td><a target="_blank" href="https://www.thinkphp.cn">ThinkPHP Version {$framework.version|default='None'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('平台框架')}</th>
<td><a target="_blank" href="https://thinkadmin.top">ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('操作系统')}</th>
<td>{:php_uname()}</td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('运行环境')}</th>
<td>{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}</td>
</tr>
<!-- {notempty name='systemid'} -->
<tr>
<th class="nowrap text-center">{:lang('系统序号')}</th>
<td>{$systemid|default=''}</td>
</tr>
<!-- {/notempty} -->
</tbody>
</table>
</div>
</div>
{notempty name='plugins'}
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('应用插件')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<thead>
<tr>
<th class="nowrap text-center">{:lang('应用名称')}</th>
<th class="nowrap text-center">{:lang('插件名称')}</th>
<th class="nowrap text-left">{:lang('插件包名')}</th>
<th class="nowrap text-center">{:lang('插件版本')}</th>
<th class="nowrap text-center">{:lang('授权协议')}</th>
</tr>
</thead>
<tbody>
{foreach $plugins as $key=>$plugin}
<tr>
<td class="nowrap text-center">{$key}</td>
<td class="nowrap text-center">{$plugin.name|lang}</td>
<td class="nowrap text-left">
{if empty($plugin.install.document)}{$plugin.package}
{else}<a target="_blank" href="{$plugin.install.document}">{$plugin.package}</a>{/if}
</td>
<td class="nowrap text-center">{$plugin.install.version|default='unknow'}</td>
<td class="nowrap text-center">
{if empty($plugin.install.license)} -
{elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###}
{else}{$plugin.install.license|default='-'}{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
<!--{/if}-->
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" type="submit">保存配置</button>
</div>
</div>
</div>
<!--{if $app->isDebug()}-->
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('系统信息')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<tbody>
<tr>
<th class="nowrap text-center">{:lang('核心框架')}</th>
<td><a target="_blank" href="https://www.thinkphp.cn">ThinkPHP Version {$framework.version|default='None'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('平台框架')}</th>
<td><a target="_blank" href="https://thinkadmin.top">ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}</a></td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('操作系统')}</th>
<td>{:php_uname()}</td>
</tr>
<tr>
<th class="nowrap text-center">{:lang('运行环境')}</th>
<td>{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}</td>
</tr>
<!-- {notempty name='systemid'} -->
<tr>
<th class="nowrap text-center">{:lang('系统序号')}</th>
<td>{$systemid|default=''}</td>
</tr>
<!-- {/notempty} -->
</tbody>
</table>
</div>
</div>
{notempty name='plugins'}
<div class="layui-card padding-20 shadow">
<div class="layui-card-header notselect">
<span class="help-label">
<b style="color:#333!important;">{:lang('应用插件')}</b>( {:lang('仅开发模式可见')} )
</span>
</div>
<div class="layui-card-body">
<table class="layui-table" lay-even>
<thead>
<tr>
<th class="nowrap text-center">{:lang('应用名称')}</th>
<th class="nowrap text-center">{:lang('插件名称')}</th>
<th class="nowrap text-left">{:lang('插件包名')}</th>
<th class="nowrap text-center">{:lang('插件版本')}</th>
<th class="nowrap text-center">{:lang('授权协议')}</th>
</tr>
</thead>
<tbody>
{foreach $plugins as $key=>$plugin}
<tr>
<td class="nowrap text-center">{$key}</td>
<td class="nowrap text-center">{$plugin.name|lang}</td>
<td class="nowrap text-left">
{if empty($plugin.install.document)}{$plugin.package}
{else}<a target="_blank" href="{$plugin.install.document}">{$plugin.package}</a>{/if}
</td>
<td class="nowrap text-center">{$plugin.install.version|default='unknow'}</td>
<td class="nowrap text-center">
{if empty($plugin.install.license)} -
{elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###}
{else}{$plugin.install.license|default='-'}{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
{/notempty}
<!--{/if}-->
</form>
{/block}

View File

@@ -99,6 +99,20 @@
<input name="site_copy" required placeholder="请输入版权信息" vali-name="版权信息" value="{:sysconf('site_copy')}" class="layui-input">
</label>
</div>
<div class="layui-form-item margin-bottom-5">
<div class="help-label label-required-prev"><b>站点域名</b></div>
<label class="relative block label-required-null">
<input name="base.site_domain" required placeholder="请输入站点域名" value="{:sysconf('base.site_domain')}" class="layui-input">
</label>
<div class="help-block sub-span-blue">
请设置站点访问域名例如https://www.example.com
</div>
</div>
<div class="layui-col-xs12 help-block padding-top-0">
网站备案号和公安备案号可以在<a target="_blank" href="https://beian.miit.gov.cn">备案管理中心</a>查询并获取,网站上线时必需配置备案号,备案号会链接到信息备案管理系统 ~
</div>

View File

@@ -1,108 +0,0 @@
<?php
namespace app\agent\controller;
use think\admin\Controller;
use think\facade\Db;
/**
* 代理商后台
*/
class Index extends Controller
{
/**
* 生成激活码
*/
public function generate()
{
if ($this->request->isPost()) {
$count = intval(input('count', 1));
$count = max(1, min($count, 100)); // 限制一次最多生成100个
// 获取当前代理商信息
$agentId = session('agent.id');
$agent = Db::name('cursor_agents')->where('id', $agentId)->find();
if (empty($agent)) {
$this->error('代理商信息不存在!');
}
// 开启事务
Db::startTrans();
try {
$codes = [];
$agentCodes = [];
$now = date('Y-m-d H:i:s');
for ($i = 0; $i < $count; $i++) {
// 生成激活码
$codes[] = [
'code' => strtoupper(substr(md5(uniqid() . mt_rand()), 0, 16)),
'days' => 30, // 固定30天
'created_at' => $now,
'is_used' => 0
];
}
// 批量插入激活码
$codeIds = Db::name('cursor_activation_codes')->insertAll($codes);
// 计算佣金
$price = 100; // 固定价格100元
$commission = $price * ($agent['commission_rate'] / 100);
// 如果是二级代理,计算上级佣金
$parentCommission = 0;
if ($agent['level'] == 2 && $agent['parent_id'] > 0) {
$parent = Db::name('cursor_agents')->where('id', $agent['parent_id'])->find();
if ($parent) {
$parentCommission = $price * ($parent['commission_rate'] / 100);
}
}
// 准备代理商激活码数据
foreach ($codes as $index => $code) {
$agentCodes[] = [
'agent_id' => $agentId,
'code_id' => $codeIds[$index],
'price' => $price,
'commission' => $commission,
'parent_commission' => $parentCommission,
'status' => 0,
'created_at' => $now,
'updated_at' => $now
];
}
// 批量插入代理商激活码关联
Db::name('cursor_agent_codes')->insertAll($agentCodes);
Db::commit();
$this->success('生成成功!');
} catch (\Exception $e) {
Db::rollback();
$this->error('生成失败:' . $e->getMessage());
}
} else {
$this->fetch();
}
}
/**
* 我的激活码列表
*/
public function codes()
{
$this->title = '我的激活码';
$agentId = session('agent.id');
$query = $this->_query('cursor_agent_codes')->alias('ac')
->join('cursor_activation_codes c', 'c.id = ac.code_id')
->where('ac.agent_id', $agentId)
->field('ac.*, c.code, c.days, c.is_used, c.used_at, c.used_by');
// 数据列表处理
$query->equal('c.is_used')->equal('ac.status');
// 列表排序并显示
$query->order('ac.id desc')->page();
}
}

View File

@@ -1,83 +0,0 @@
{extend name="../../admin/view/main"}
{block name="button"}
<button class='layui-btn layui-btn-sm layui-btn-primary' data-modal='{:url("generate")}'>生成激活码</button>
{/block}
{block name="content"}
<div class="think-box-shadow">
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用状态</label>
<div class="layui-input-inline">
<select name='is_used' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
<option value='0' {if isset($get.is_used) and $get.is_used=='0'}selected{/if}>未使用</option>
<option value='1' {if isset($get.is_used) and $get.is_used=='1'}selected{/if}>已使用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">结算状态</label>
<div class="layui-input-inline">
<select name='status' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
<option value='0' {if isset($get.status) and $get.status=='0'}selected{/if}>未结算</option>
<option value='1' {if isset($get.status) and $get.status=='1'}selected{/if}>已结算</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
<table class="layui-table margin-top-10" lay-skin="line">
{notempty name='list'}
<thead>
<tr>
<th class='text-left'>激活码</th>
<th class='text-left'>有效天数</th>
<th class='text-left'>销售价格</th>
<th class='text-left'>佣金</th>
<th class='text-left'>使用状态</th>
<th class='text-left'>使用时间</th>
<th class='text-left'>使用者</th>
<th class='text-left'>结算状态</th>
<th class='text-left'>生成时间</th>
</tr>
</thead>
{/notempty}
<tbody>
{foreach $list as $key=>$vo}
<tr>
<td class='text-left'>{$vo.code}</td>
<td class='text-left'>{$vo.days} </td>
<td class='text-left'>{$vo.price}</td>
<td class='text-left'>{$vo.commission}</td>
<td class='text-left'>
{if $vo.is_used eq 1}
<span class="color-red">已使用</span>
{else}
<span class="color-green">未使用</span>
{/if}
</td>
<td class='text-left'>{$vo.used_at|format_datetime|default='--'}</td>
<td class='text-left'>{$vo.used_by|default='--'}</td>
<td class='text-left'>
{if $vo.status eq 1}
<span class="color-blue">已结算</span>
{else}
<span class="color-red">未结算</span>
{/if}
</td>
<td class='text-left'>{$vo.created_at|format_datetime}</td>
</tr>
{/foreach}
</tbody>
</table>
{empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
</div>
{/block}

View File

@@ -1,20 +0,0 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">生成数量</label>
<div class="layui-input-block">
<input name="count" value="1" required placeholder="请输入要生成的激活码数量" class="layui-input">
<tip class="help-block">一次最多可生成100个激活码每个激活码售价100元</tip>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" type="submit">确定生成</button>
<button class="layui-btn layui-btn-danger" type="button" data-close>取消</button>
</div>
</form>
{/block}

View File

@@ -3,6 +3,7 @@
namespace app\manager\controller;
use think\admin\Controller;
use think\admin\service\AdminService;
/**
* Cursor账号管理
@@ -41,7 +42,7 @@ class Account extends Controller
$price = floatval(input('price', 0));
// 参数验证
if ($count < 1 || $count > 100) {
if ($count < 1 || $count > 500) {
$this->error('生成数量必须在1-100之间');
}
if ($days < 1) {
@@ -60,7 +61,8 @@ class Account extends Controller
'code' => strtoupper(substr(md5(uniqid() . mt_rand()), 0, 16)),
'days' => $days,
'created_at' => $now,
'is_used' => 0
'is_used' => 0,
'status' => 1 // 设置默认状态为正常
];
}
@@ -153,7 +155,7 @@ class Account extends Controller
->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id')
->leftJoin('cursor_agents a', 'ac.agent_id = a.id')
->field([
'c.*',
'c.*', // 包含所有激活码字段,包括 status
'a.id as agent_id',
'a.username as agent_name',
'a.level as agent_level',
@@ -184,6 +186,18 @@ class Account extends Controller
if ($usedBy) {
$query->whereLike('c.used_by', "%{$usedBy}%");
}
// 添加天数筛选
$days = input('days', '');
if ($days !== '') {
$query->where('c.days', '=', intval($days));
}
// 添加状态筛选
$status = input('status', '');
if ($status !== '') {
$query->where('c.status', '=', $status);
}
});
// 列表排序并显示
@@ -220,6 +234,36 @@ class Account extends Controller
$this->_delete('cursor_activation_codes');
}
/**
* 批量删除激活码
* @auth true
*/
public function batchRemoveCodes()
{
$this->_applyFormToken();
$ids = $this->request->post('ids', '');
// 如果是字符串,转换为数组
if (is_string($ids)) {
$ids = explode(',', $ids);
}
// 确保ids是数组且不为空
if (empty($ids) || !is_array($ids)) {
$this->error('请选择要删除的激活码');
}
// 删除激活码
$deleteResult1 = \think\facade\Db::name('cursor_activation_codes')->whereIn('id', $ids)->delete();
// 删除关联的代理商激活码记录
$deleteResult2 = \think\facade\Db::name('cursor_agent_codes')->whereIn('code_id', $ids)->delete();
// 只要有一个删除操作成功就认为是成功的
if ($deleteResult1 || $deleteResult2) {
$this->success('批量删除成功!');
} else {
$this->success('没有需要删除的数据!');
}
}
/**
* 设备账号管理
* @auth true
@@ -228,42 +272,77 @@ class Account extends Controller
public function devices()
{
$this->title = '设备账号管理';
// 创建查询对象
$query = $this->_query('cursor_accounts')
->where('used_by', '<>', '')
->where('is_used', 1);
$query = $this->_query('cursor_activation_codes')->alias('c')
->field([
'c.used_by',
'MIN(c.used_at) as first_activation',
'COUNT(c.id) as total_codes',
'SUM(c.days) as total_days',
'GROUP_CONCAT(DISTINCT a.username) as agent_names'
])
->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id')
->leftJoin('cursor_agents a', 'ac.agent_id = a.id')
->where('c.used_by', '<>', '')
->where('c.is_used', 1)
->group('c.used_by');
// 数据列表处理
$query->like('email,used_by');
// 列表排序并显示
$query->order('used_at desc')->page();
// 获取列表数据
$list = $this->app->db->getList();
// 检查每个设备的缓存状态
foreach ($list as &$item) {
if (!empty($item['used_by'])) {
// 检查账号缓存
$cacheKey = "device_account_{$item['used_by']}";
$cachedAccount = \think\facade\Cache::get($cacheKey);
$item['has_cache'] = !empty($cachedAccount);
// 检查冷却期
$cooldownKey = "device_cooldown_{$item['used_by']}";
$cooldownTime = \think\facade\Cache::get($cooldownKey);
$item['in_cooldown'] = $cooldownTime > time();
$item['cooldown_time'] = $cooldownTime ? date('Y-m-d H:i:s', $cooldownTime) : '';
// 检查缓存账号是否与数据库一致
$item['cache_match'] = false;
if ($cachedAccount) {
$item['cache_match'] = ($cachedAccount['id'] == $item['id']);
}
$query->where(function($query) {
$usedBy = input('used_by', '');
if ($usedBy) {
$query->whereLike('c.used_by', "%{$usedBy}%");
}
});
// 获取数据
$result = $query->order('first_activation desc')->page();
// 处理列表数据
$list = [];
foreach ($result['list'] as $item) {
// 获取当前使用的账号
$account = \think\facade\Db::name('cursor_accounts')
->where('used_by', $item['used_by'])
->where('is_used', 1)
->find();
// 计算到期时间
$firstActivationTime = strtotime($item['first_activation']);
$expireTime = $firstActivationTime + ($item['total_days'] * 24 * 3600);
// 检查缓存状态
$cacheKey = "device_account_{$item['used_by']}";
$cooldownKey = "device_cooldown_{$item['used_by']}";
$cachedAccount = \think\facade\Cache::get($cacheKey);
$cooldownTime = \think\facade\Cache::get($cooldownKey);
// 构建数据
$list[] = [
'used_by' => $item['used_by'],
'email' => $account ? $account['email'] : '--',
'agent_names' => $item['agent_names'] ?: '--',
'total_codes' => $item['total_codes'],
'total_days' => $item['total_days'],
'first_activation' => $item['first_activation'],
'expire_time' => date('Y-m-d H:i:s', $expireTime),
'days_left' => ceil(($expireTime - time()) / 86400),
'has_cache' => !empty($cachedAccount),
'in_cooldown' => $cooldownTime > time(),
'cooldown_time' => $cooldownTime ? date('Y-m-d H:i:s', $cooldownTime) : ''
];
}
$this->list = $list;
// 传递数据到模板
$this->assign([
'list' => $list,
'page' => $result['page']
]);
// 渲染模板
$this->fetch();
}
/**
@@ -301,4 +380,355 @@ class Account extends Controller
$this->error("重置失败:{$e->getMessage()}");
}
}
/**
* 批量切换代理商
* @auth true
*/
public function batchChangeAgent()
{
if ($this->request->isPost()) {
$data = $this->_vali([
'agent_id.require' => '代理商不能为空',
'code_ids.require' => '请选择要切换的激活码',
'price.require' => '销售价格不能为空',
'price.float' => '销售价格必须是数字'
]);
// 检查代理商是否存在
$agent = \think\facade\Db::name('cursor_agents')
->where('id', $data['agent_id'])
->where('status', 1)
->find();
if (empty($agent)) {
$this->error('代理商不存在或已被禁用!');
}
// 解析激活码ID
$codeIds = explode(',', $data['code_ids']);
if (empty($codeIds)) {
$this->error('请选择要切换的激活码!');
}
// 开启事务
\think\facade\Db::startTrans();
$now = date('Y-m-d H:i:s');
// 计算佣金
$commission = $data['price'] * ($agent['commission_rate'] / 100);
// 如果是二级代理,计算上级佣金
$parentCommission = 0;
if ($agent['level'] == 2 && $agent['parent_id'] > 0) {
$parent = \think\facade\Db::name('cursor_agents')
->where('id', $agent['parent_id'])
->find();
if ($parent) {
$parentCommission = $data['price'] * ($parent['commission_rate'] / 100);
}
}
// 删除旧的代理商关联
$deleteResult = \think\facade\Db::name('cursor_agent_codes')
->whereIn('code_id', $codeIds)
->delete();
// 批量插入新的关联
$insertData = [];
foreach ($codeIds as $codeId) {
$insertData[] = [
'agent_id' => $agent['id'],
'code_id' => $codeId,
'price' => $data['price'],
'commission' => $commission,
'parent_commission' => $parentCommission,
'status' => 0,
'created_at' => $now,
'updated_at' => $now
];
}
// 插入新数据
if (!empty($insertData)) {
$insertResult = \think\facade\Db::name('cursor_agent_codes')->insertAll($insertData);
if ($insertResult === false) {
\think\facade\Db::rollback();
$this->error('切换失败:插入新数据失败');
}
}
\think\facade\Db::commit();
$this->success('切换成功!');
} else {
// 获取代理商列表
$this->agents = \think\facade\Db::name('cursor_agents')
->where('status', 1)
->select()
->toArray();
$this->fetch();
}
}
/**
* 代理商激活码管理
* @auth true
* @menu true
*/
public function agentCodes()
{
$this->title = '代理商激活码';
// 获取当前管理员ID和角色
$adminId = session('user.id');
$isSuper = AdminService::isSuper();
if (!$isSuper) {
// 普通管理员,获取关联的代理商信息
$agent = \think\facade\Db::name('cursor_agents')
->where('admin_id', $adminId)
->where('status', 1)
->find();
if (empty($agent)) {
$this->error('未找到关联的代理商账号');
}
}
// 创建查询对象
$query = $this->_query('cursor_activation_codes')->alias('c')
->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id')
->leftJoin('cursor_agents a', 'ac.agent_id = a.id')
->field([
'c.*', // 包含所有激活码字段,包括 status
'ac.price',
'ac.commission',
'ac.status as settle_status', // 重命名代理商激活码的状态字段
'a.username as agent_name',
'a.level as agent_level'
]);
// 如果不是超级管理员,只显示自己的激活码
if (!$isSuper) {
$query->where('ac.agent_id', $agent['id']);
}
// 数据列表处理
$query->where(function($query) use ($isSuper) {
$code = input('code', '');
if ($code) {
$query->whereLike('c.code', "%{$code}%");
}
$isUsed = input('is_used', '');
if ($isUsed !== '') {
$query->where('c.is_used', '=', $isUsed);
}
$status = input('status', '');
if ($status !== '') {
$query->where('c.status', '=', $status);
}
// 添加天数筛选
$days = input('days', '');
if ($days !== '') {
$query->where('c.days', '=', intval($days));
}
// 超级管理员可以按代理商筛选
if ($isSuper) {
$agentId = input('agent_id', '');
if ($agentId !== '') {
$query->where('ac.agent_id', '=', $agentId);
}
}
// 添加时间范围筛选
$createTime = input('create_time', '');
if ($createTime) {
$createTimeArr = explode(' - ', $createTime);
if (count($createTimeArr) == 2) {
$query->whereBetween('c.created_at', [
"{$createTimeArr[0]} 00:00:00",
"{$createTimeArr[1]} 23:59:59"
]);
}
}
// 使用时间范围筛选
$useTime = input('use_time', '');
if ($useTime) {
$useTimeArr = explode(' - ', $useTime);
if (count($useTimeArr) == 2) {
$query->whereBetween('c.used_at', [
"{$useTimeArr[0]} 00:00:00",
"{$useTimeArr[1]} 23:59:59"
]);
}
}
});
// 获取代理商列表(仅超级管理员可见)
if ($isSuper) {
$this->agents = \think\facade\Db::name('cursor_agents')
->where('status', 1)
->select()
->toArray();
}
// 列表排序并显示
$query->order('c.id desc')->page();
}
/**
* 禁用激活码
* @auth true
*/
public function disableCode()
{
$this->_applyFormToken();
$id = input('id');
// 更新激活码状态为禁用 (0)
$result = \think\facade\Db::name('cursor_activation_codes')
->where('id', $id)
->update(['status' => 0]);
if ($result !== false) {
$this->success('禁用成功!');
} else {
$this->error('禁用失败!');
}
}
/**
* 批量禁用激活码
* @auth true
*/
public function batchDisableCodes()
{
$this->_applyFormToken();
$ids = $this->request->post('ids', '');
if (empty($ids)) {
$this->error('请选择要禁用的激活码');
}
// 如果是字符串,转换为数组
if (is_string($ids)) {
$ids = explode(',', $ids);
}
// 批量更新状态为禁用 (0)
$result = \think\facade\Db::name('cursor_activation_codes')
->whereIn('id', $ids)
->update(['status' => 0]);
if ($result !== false) {
$this->success('批量禁用成功!');
} else {
$this->error('批量禁用失败!');
}
}
/**
* 导出激活码到txt文件
* @auth true
*/
public function exportCodes()
{
try {
// 获取当前管理员ID和角色
$adminId = session('user.id');
$isSuper = AdminService::isSuper();
if (!$isSuper) {
// 普通管理员,获取关联的代理商信息
$agent = \think\facade\Db::name('cursor_agents')
->where('admin_id', $adminId)
->where('status', 1)
->find();
if (empty($agent)) {
$this->error('未找到关联的代理商账号');
}
}
// 创建查询对象
$query = \think\facade\Db::name('cursor_activation_codes')->alias('c')
->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id')
->leftJoin('cursor_agents a', 'ac.agent_id = a.id')
->field([
'c.code',
'c.days',
'c.is_used',
'c.status'
]);
// 如果不是超级管理员,只显示自己的激活码
if (!$isSuper) {
$query->where('ac.agent_id', $agent['id']);
}
// 应用筛选条件
$code = input('code', '');
if ($code) {
$query->whereLike('c.code', "%{$code}%");
}
$isUsed = input('is_used', '');
if ($isUsed !== '') {
$query->where('c.is_used', '=', $isUsed);
}
$status = input('status', '');
if ($status !== '') {
$query->where('c.status', '=', $status);
}
$days = input('days', '');
if ($days !== '') {
$query->where('c.days', '=', intval($days));
}
// 超级管理员可以按代理商筛选
if ($isSuper) {
$agentId = input('agent_id', '');
if ($agentId !== '') {
$query->where('ac.agent_id', '=', $agentId);
}
}
// 获取数据
$list = $query->order('c.id desc')->select()->toArray();
// 生成文件内容
$content = '';
foreach ($list as $item) {
// 获取状态文本
$statusText = $item['status'] == 1 ? '正常' : '禁用';
$usedText = $item['is_used'] == 1 ? '已使用' : '未使用';
// 格式化每一行: 激活码 天数 状态
$content .= sprintf("%s\t%d\t%s/%s\n",
$item['code'],
$item['days'],
$usedText,
$statusText
);
}
// 设置响应头
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="activation_codes_' . date('YmdHis') . '.txt"');
// 输出内容
echo $content;
exit;
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
}

View File

@@ -43,6 +43,7 @@ class Agent extends Controller
'parent_id.default' => 0,
'commission_rate.require' => '佣金比例不能为空',
'status.default' => 1,
'admin_id.default' => 0, // 绑定的管理员ID
'remark.default' => ''
]);
@@ -62,6 +63,19 @@ class Agent extends Controller
}
}
// 如果绑定了管理员账号,检查是否存在
if (!empty($data['admin_id'])) {
$admin = Db::name('system_user')->where('id', $data['admin_id'])->find();
if (empty($admin)) {
$this->error('管理员账号不存在!');
}
// 检查管理员账号是否已被绑定
$exists = Db::name('cursor_agents')->where('admin_id', $data['admin_id'])->find();
if ($exists) {
$this->error('该管理员账号已被其他代理商绑定!');
}
}
// 处理密码
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$data['created_at'] = $data['updated_at'] = date('Y-m-d H:i:s');
@@ -77,6 +91,13 @@ class Agent extends Controller
->where('level', 1)
->where('status', 1)
->select();
// 获取管理员列表
$this->admins = Db::name('system_user')
->where('status', 1)
->field('id, username, nickname')
->select();
$this->fetch();
}
}
@@ -87,8 +108,85 @@ class Agent extends Controller
*/
public function edit()
{
$this->_applyFormToken();
$this->_form('cursor_agents', 'form');
if ($this->request->isPost()) {
$data = $this->_vali([
'id.require' => '代理商ID不能为空',
'username.require' => '用户名不能为空',
'password.default' => '',
'nickname.default' => '',
'level.require' => '代理级别不能为空',
'parent_id.default' => 0,
'commission_rate.require' => '佣金比例不能为空',
'status.default' => 1,
'admin_id.default' => 0, // 绑定的管理员ID
'remark.default' => ''
]);
// 检查用户名是否存在
$exists = Db::name('cursor_agents')
->where('username', $data['username'])
->where('id', '<>', $data['id'])
->find();
if ($exists) {
$this->error('用户名已存在!');
}
// 如果是二级代理,检查上级代理
if ($data['level'] == 2) {
if (empty($data['parent_id'])) {
$this->error('请选择上级代理!');
}
$parent = Db::name('cursor_agents')->where('id', $data['parent_id'])->find();
if (empty($parent) || $parent['level'] != 1) {
$this->error('上级代理不存在或不是一级代理!');
}
}
// 如果绑定了管理员账号,检查是否存在
if (!empty($data['admin_id'])) {
$admin = Db::name('system_user')->where('id', $data['admin_id'])->find();
if (empty($admin)) {
$this->error('管理员账号不存在!');
}
// 检查管理员账号是否已被其他代理商绑定
$exists = Db::name('cursor_agents')
->where('admin_id', $data['admin_id'])
->where('id', '<>', $data['id'])
->find();
if ($exists) {
$this->error('该管理员账号已被其他代理商绑定!');
}
}
// 处理密码
if (!empty($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
} else {
unset($data['password']);
}
$data['updated_at'] = date('Y-m-d H:i:s');
if (Db::name('cursor_agents')->update($data) !== false) {
$this->success('更新成功!');
} else {
$this->error('更新失败!');
}
} else {
// 获取一级代理列表(用于选择上级)
$this->agents = Db::name('cursor_agents')
->where('level', 1)
->where('status', 1)
->select();
// 获取管理员列表
$this->admins = Db::name('system_user')
->where('status', 1)
->field('id, username, nickname')
->select();
$this->_form('cursor_agents', 'form');
}
}
/**

View File

@@ -0,0 +1,228 @@
<?php
declare (strict_types = 1);
namespace app\manager\controller;
use app\admin\model\Version as VersionModel;
use think\admin\Controller;
use think\admin\extend\DataExtend;
use think\admin\service\AdminService;
use think\admin\service\SystemService;
use think\admin\Storage;
/**
* 版本管理
* @auth true
* @menu true
*/
class Version extends Controller
{
/**
* 允许上传的文件类型
* @var array
*/
protected $allowExts = ['zip', 'exe', 'dmg', 'pkg', 'deb', 'rpm'];
/**
* 版本列表
* @auth true
* @menu true
*/
public function index()
{
$this->title = '版本管理';
// 创建查询对象
$query = VersionModel::mQuery();
// 数据列表处理
$query->like('version_no,version_name')->equal('platform,status');
// 列表排序处理
$query->order('version_no desc')->page();
}
/**
* 添加版本
* @auth true
*/
public function add()
{
$this->title = '添加版本';
VersionModel::mForm('form');
}
/**
* 编辑版本
* @auth true
*/
public function edit()
{
$this->title = '编辑版本';
VersionModel::mForm('form');
}
/**
* 表单数据处理
*/
protected function _form_filter(&$data)
{
if ($this->request->isGet()) {
// 获取版本日志
if (isset($data['id'])) {
$data['logs'] = \think\facade\Db::name('version_log')
->where(['version_id' => $data['id']])
->order('id asc')
->select()
->toArray();
}
} else {
// 检查版本号是否已存在
if (empty($data['id'])) {
$exists = VersionModel::mk()->where('version_no', $data['version_no'])->find();
if ($exists) {
$this->error('该版本号已存在!');
}
}
// 保存版本日志
if (isset($data['logs'])) {
$logs = [];
foreach ($data['logs']['type'] as $key => $type) {
if (!empty($data['logs']['content'][$key])) {
$logs[] = [
'version_id' => $data['id'] ?? 0,
'type' => $type,
'content' => $data['logs']['content'][$key],
'create_time' => date('Y-m-d H:i:s')
];
}
}
unset($data['logs']);
if (!empty($logs)) {
if (isset($data['id'])) {
\think\facade\Db::name('version_log')->where(['version_id' => $data['id']])->delete();
}
\think\facade\Db::name('version_log')->insertAll($logs);
}
}
}
}
/**
* 删除版本
* @auth true
*/
public function remove()
{
VersionModel::mDelete();
}
/**
* 版本状态
* @auth true
*/
public function state()
{
VersionModel::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 上传更新包
* @auth true
*/
public function upload()
{
if ($this->request->isPost()) {
try {
// 获取上传的文件
$file = $this->request->file('file');
if (empty($file)) {
$this->error('文件上传异常,文件可能过大!');
}
// 检查文件类型
$ext = strtolower($file->getOriginalExtension());
if (!in_array($ext, $this->allowExts)) {
$this->error('不支持的文件类型!');
}
// 获取原始文件名
$originalName = $file->getOriginalName();
// 从文件名解析版本号
$version_no = VersionModel::parseVersionFromFilename($originalName);
// 生成版本名称
$version_name = VersionModel::generateVersionName($version_no, $originalName);
// 执行文件上传
$type = sysconf('storage.type|raw') ?: 'local';
if ($type === 'local') {
$local = \think\admin\storage\LocalStorage::instance();
$distName = dirname($local->path($originalName, false)) . '/' . $originalName;
$file->move(dirname($distName), basename($distName));
$info = [
'url' => '/upload/' . $originalName,
'key' => $originalName
];
// 设置上传文件的权限为755
@chmod($distName, 0755);
} else {
$bina = file_get_contents($file->getPathname());
$info = \think\admin\Storage::instance($type)->set($originalName, $bina, false, $originalName);
}
if (isset($info['url'])) {
$data = [
'code' => 0,
'msg' => '文件上传成功!',
'data' => [
'url' => $info['url'],
'name' => $originalName,
'version_no' => $version_no,
'version_name' => $version_name
]
];
return json($data);
} else {
$this->error('文件上传失败!');
}
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
}
/**
* 从文件名解析版本信息
* @param string $filename
* @return array
*/
protected function parseVersionFromFilename($filename)
{
// 移除扩展名
$name = pathinfo($filename, PATHINFO_FILENAME);
// 匹配版本号 (支持 v3.4.0 或 3.4.0 格式)
if (preg_match('/[v]?(\d+\.\d+\.\d+)/', $name, $matches)) {
$version_no = $matches[1];
// 提取版本名称 (去掉版本号部分)
$version_name = trim(str_replace($matches[0], '', $name));
if (empty($version_name)) {
$version_name = "版本 {$version_no}";
}
} else {
// 如果没有找到版本号,使用时间戳作为版本号
$version_no = date('y.m.d');
$version_name = $name ?: "版本 {$version_no}";
}
return [
'version_no' => $version_no,
'version_name' => $version_name
];
}
}

View File

@@ -23,6 +23,28 @@ class AgentCode extends Model
'updated_at' => 'datetime',
];
// 定义卡类型配置
public static $cardTypes = [
1 => ['days' => 1, 'name' => '1天卡'],
5 => ['days' => 5, 'name' => '5天卡'],
30 => ['days' => 30, 'name' => '30天卡'],
90 => ['days' => 90, 'name' => '三个月卡'],
180 => ['days' => 180, 'name' => '六个月卡'],
365 => ['days' => 365, 'name' => '一年卡'],
];
// 获取所有卡类型
public static function getCardTypes()
{
return self::$cardTypes;
}
// 获取卡类型名称
public static function getCardTypeName($days)
{
return isset(self::$cardTypes[$days]) ? self::$cardTypes[$days]['name'] : "{$days}天卡";
}
// 获取所属代理商
public function agent()
{

View File

@@ -0,0 +1,278 @@
{extend name="../../admin/view/main"}
{block name="button"}
{if auth("batchDisableCodes")}
<button class='layui-btn layui-btn-sm layui-btn-danger' data-batch-confirm="确定要禁用选中的激活码吗?" data-csrf="{:systoken('batchDisableCodes')}" data-action='{:url("batchDisableCodes")}' data-rule="ids#{key}">批量禁用</button>
{/if}
<button class='layui-btn layui-btn-sm layui-btn-warm' id="batchCopyBtn" data-checkbox="true">批量复制激活码</button>
<a class='layui-btn layui-btn-sm layui-btn-normal' href="{:url('exportCodes')}?{:http_build_query($get)}">导出激活码</a>
{/block}
{block name="content"}
<div class="think-box-shadow">
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">激活码</label>
<div class="layui-input-inline">
<input name="code" value="{$get.code|default=''}" placeholder="请输入激活码" class="layui-input">
</div>
</div>
{if $Think.admin.is_super}
<div class="layui-form-item layui-inline">
<label class="layui-form-label">代理商</label>
<div class="layui-input-inline">
<select name='agent_id' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
{foreach $agents as $agent}
<option value='{$agent.id}' {if isset($get.agent_id) and $get.agent_id==$agent.id}selected{/if}>
{$agent.username} ({if $agent.level==1}一级代理{else}二级代理{/if})
</option>
{/foreach}
</select>
</div>
</div>
{/if}
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用状态</label>
<div class="layui-input-inline">
<select name='is_used' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
<option value='0' {if isset($get.is_used) and $get.is_used=='0'}selected{/if}>未使用</option>
<option value='1' {if isset($get.is_used) and $get.is_used=='1'}selected{/if}>已使用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">禁用状态</label>
<div class="layui-input-inline">
<select name='status' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
<option value='1' {if isset($get.status) and $get.status=='1'}selected{/if}>正常</option>
<option value='0' {if isset($get.status) and $get.status=='0'}selected{/if}>已禁用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">卡类型</label>
<div class="layui-input-inline">
<select name='days' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
{foreach :app\manager\model\AgentCode::getCardTypes() as $days => $info}
<option value='{$days}' {if isset($get.days) and $get.days==$days}selected{/if}>{$info.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">生成时间</label>
<div class="layui-input-inline">
<input data-date-range name="create_time" value="{$get.create_time|default=''}" placeholder="请选择生成时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用时间</label>
<div class="layui-input-inline">
<input data-date-range name="use_time" value="{$get.use_time|default=''}" placeholder="请选择使用时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
<table class="layui-table margin-top-10" lay-skin="line">
{notempty name='list'}
<thead>
<tr>
<th class='list-table-check-td think-checkbox'>
<label><input data-auto-none data-check-target='.list-check-box' type='checkbox'></label>
</th>
<th class='text-left'>激活码</th>
<th class='text-left'>有效天数</th>
{if $Think.admin.is_super}
<th class='text-left'>代理商</th>
<th class='text-left'>代理级别</th>
{/if}
<th class='text-left'>销售价格</th>
<th class='text-left'>佣金</th>
<th class='text-left'>使用状态</th>
<th class='text-left'>使用时间</th>
<th class='text-left'>使用者</th>
<th class='text-left'>生成时间</th>
<th class='text-left'>状态</th>
<th></th>
</tr>
</thead>
{/notempty}
<tbody>
{foreach $list as $key=>$vo}
<tr>
<td class='list-table-check-td think-checkbox'>
<label><input class="list-check-box" value='{$vo.id}' type='checkbox'></label>
</td>
<td class='text-left'>{$vo.code}</td>
<td class='text-left'>{$vo.days} </td>
{if $Think.admin.is_super}
<td class='text-left'>{$vo.agent_name|default='--'}</td>
<td class='text-left'>
{if $vo.agent_level eq 1}
<span class="color-blue">一级代理</span>
{elseif $vo.agent_level eq 2}
<span class="color-green">二级代理</span>
{else}
--
{/if}
</td>
{/if}
<td class='text-left'>{$vo.price|default='--'}</td>
<td class='text-left'>{$vo.commission|default='--'}</td>
<td class='text-left'>
{if $vo.is_used eq 1}
<span class="color-red">已使用</span>
{else}
<span class="color-green">未使用</span>
{/if}
</td>
<td class='text-left'>{$vo.used_at|format_datetime|default='--'}</td>
<td class='text-left'>{$vo.used_by|default='--'}</td>
<td class='text-left'>{$vo.created_at|format_datetime}</td>
<td class='text-left'>
{if $vo.status eq 1}
<span class="color-green">正常</span>
{else}
<span class="color-red">已禁用</span>
{/if}
</td>
<td class='text-left'>
{if auth("disableCode") && $vo.status eq 1}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要禁用该激活码吗?" data-csrf="{:systoken('disableCode')}" data-action='{:url("disableCode")}' data-value="id#{$vo.id}">禁用</a>
{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
{empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
</div>
<!-- 复制弹窗 -->
<div id="copyDialog" style="display:none;">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form-item">
<textarea id="codeList" class="layui-textarea" style="height: 500px;margin:10px 0;font-size:14px;line-height:1.8;" readonly></textarea>
</div>
<div class="text-center">
<button class="layui-btn layui-btn-lg" onclick="copyToClipboard()">一键复制</button>
</div>
</div>
</div>
</div>
<script>
// 批量复制按钮点击事件
$('#batchCopyBtn').click(function(){
var codes = [];
$('.list-check-box:checked').each(function(){
var tr = $(this).closest('tr');
var code = tr.find('td:eq(1)').text(); // 激活码
var days = tr.find('td:eq(2)').text(); // 天数
codes.push(code + ' - ' + days);
});
if(codes.length === 0){
layer.msg('请选择要复制的激活码');
return;
}
// 格式化激活码列表
var formattedCodes = codes.map(function(item){
return item.trim();
}).join('\n\n');
// 显示弹窗
layer.open({
type: 1,
title: '复制激活码',
area: ['800px', '700px'],
content: $('#copyDialog'),
offset: '50px',
fixed: false,
resize: true,
shadeClose: true,
shade: [0.3, '#000'],
maxmin: true,
moveOut: true,
anim: 1,
zIndex: layer.zIndex + 1000, // 增加 zIndex 确保显示在最上层
success: function(layero, index){
layer.setTop(layero);
$('#codeList').val(formattedCodes);
// 添加自定义样式
$(layero).css({
'background-color': '#fff',
'box-shadow': '0 2px 8px rgba(0,0,0,.1)'
}).find('.layui-layer-content').css({
'padding': '30px'
});
}
});
});
// 复制到剪贴板
function copyToClipboard() {
var textarea = document.getElementById('codeList');
textarea.select();
try {
document.execCommand('copy');
layer.msg('复制成功!', {icon: 1, zIndex: layer.zIndex + 101});
} catch (err) {
layer.msg('复制失败,请手动复制', {icon: 2, zIndex: layer.zIndex + 101});
}
}
</script>
<style>
/* 美化时间选择器样式 */
.layui-laydate {
border: none !important;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
.layui-laydate-header {
border-bottom: 1px solid #f0f0f0;
}
.layui-laydate-content td:hover,
.layui-laydate-content td.laydate-selected {
border-radius: 2px;
}
.layui-laydate-footer {
border-top: 1px solid #f0f0f0;
}
.laydate-time-list>li:hover {
background-color: #f8f8f8;
}
.layui-laydate-content td.layui-this {
border-radius: 2px;
}
/* 输入框样式优化 */
.layui-input[readonly] {
background-color: #fff !important;
cursor: pointer;
}
.layui-input:hover {
border-color: #D2D2D2 !important;
}
.layui-input:focus {
border-color: #009688 !important;
box-shadow: 0 0 0 2px rgba(0,150,136,.2);
}
</style>
{/block}

View File

@@ -0,0 +1,44 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">选择代理商</label>
<div class="layui-input-block">
<select name='agent_id' class='layui-select' lay-search required>
<option value=''>请选择代理商</option>
{foreach $agents as $agent}
<option value='{$agent.id}'>{$agent.username} ({if $agent.level==1}一级代理{else}二级代理{/if})</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">销售价格</label>
<div class="layui-input-block">
<input name="price" value='' required placeholder="请输入销售价格" class="layui-input">
<tip class="help-block">设置激活码的销售价格,用于计算代理商佣金</tip>
</div>
</div>
<input type='hidden' name='code_ids' value=''>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>保存设置</button>
<button class="layui-btn layui-btn-danger" type='button' data-close>取消</button>
</div>
</form>
<script>
// 获取选中的激活码ID
var ids = [];
parent.$('.list-check-box:checked').map(function(){
ids.push($(this).val());
});
// 设置到隐藏字段
$('input[name="code_ids"]').val(ids.join(','));
</script>
{/block}

View File

@@ -2,6 +2,13 @@
{block name="button"}
<button class='layui-btn layui-btn-sm layui-btn-primary' data-modal='{:url("generate")}'>生成激活码</button>
{if auth("batchRemoveCodes")}
<button class='layui-btn layui-btn-sm layui-btn-danger' data-batch-confirm="确定要删除选中的激活码吗?" data-csrf="{:systoken('batchRemoveCodes')}" data-action='{:url("batchRemoveCodes")}' data-rule="ids#{key}">批量删除</button>
{/if}
{if auth("batchChangeAgent")}
<button class='layui-btn layui-btn-sm layui-btn-normal' data-modal='{:url("batchChangeAgent")}' data-title="批量切换代理商" data-checkbox="true">批量切换代理商</button>
{/if}
<button class='layui-btn layui-btn-sm layui-btn-warm' id="batchCopyBtn" data-checkbox="true">批量复制激活码</button>
{/block}
{block name="content"}
@@ -39,6 +46,29 @@
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">禁用状态</label>
<div class="layui-input-inline">
<select name='status' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
<option value='1' {if isset($get.status) and $get.status=='1'}selected{/if}>正常</option>
<option value='0' {if isset($get.status) and $get.status=='0'}selected{/if}>已禁用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">卡类型</label>
<div class="layui-input-inline">
<select name='days' lay-search class='layui-select'>
<option value=''>-- 全部 --</option>
{foreach :app\manager\model\AgentCode::getCardTypes() as $days => $info}
<option value='{$days}' {if isset($get.days) and $get.days==$days}selected{/if}>{$info.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
@@ -63,6 +93,7 @@
<th class='text-left'>使用时间</th>
<th class='text-left'>使用者</th>
<th class='text-left'>生成时间</th>
<th class='text-left'>状态</th>
<th></th>
</tr>
</thead>
@@ -109,9 +140,19 @@
<td class='text-left'>{$vo.used_at|format_datetime|default='--'}</td>
<td class='text-left'>{$vo.used_by|default='--'}</td>
<td class='text-left'>{$vo.created_at|format_datetime}</td>
<td class='text-left'>
{if $vo.status eq 1}
<span class="color-green">正常</span>
{else}
<span class="color-red">已禁用</span>
{/if}
</td>
<td class='text-left'>
{if auth("removeCode")}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该激活码吗?" data-csrf="{:systoken('removeCode')}" data-action='{:url("removeCode")}' data-value="id#{$vo.id}"> </a>
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该激活码吗?" data-csrf="{:systoken('removeCode')}" data-action='{:url("removeCode")}' data-value="id#{$vo.id}">删除</a>
{/if}
{if auth("disableCode") && $vo.status eq 1}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要禁用该激活码吗?" data-csrf="{:systoken('disableCode')}" data-action='{:url("disableCode")}' data-value="id#{$vo.id}">禁用</a>
{/if}
</td>
</tr>
@@ -120,4 +161,91 @@
</table>
{empty name='list'}<span class="notdata">没有记录哦</span>{else}{$pagehtml|raw|default=''}{/empty}
</div>
<!-- 复制弹窗 -->
<div id="copyDialog" style="display:none;">
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-form-item">
<textarea id="codeList" class="layui-textarea" style="height: 500px;margin:10px 0;font-size:14px;line-height:1.8;" readonly></textarea>
</div>
<div class="text-center">
<button class="layui-btn layui-btn-lg" onclick="copyToClipboard()">一键复制</button>
</div>
</div>
</div>
</div>
<script>
// 批量复制按钮点击事件
$('#batchCopyBtn').click(function(){
var codes = [];
$('.list-check-box:checked').each(function(){
var tr = $(this).closest('tr');
var code = tr.find('td:eq(1)').text(); // 激活码
var days = tr.find('td:eq(2)').text(); // 天数
codes.push(code + ' - ' + days);
});
if(codes.length === 0){
layer.msg('请选择要复制的激活码');
return;
}
// 格式化激活码列表
var formattedCodes = codes.map(function(item){
return item.trim();
}).join('\n\n');
// 显示弹窗
var index = layer.open({
type: 1,
title: '复制激活码',
area: ['800px', '700px'],
content: $('#copyDialog'),
offset: '50px',
fixed: false,
resize: true,
shadeClose: true,
shade: [0.3, '#000'],
maxmin: true,
moveOut: true,
anim: 1,
isOutAnim: false,
zIndex: layer.zIndex + 1000, // 增加 zIndex 确保显示在最上层
success: function(layero, index){
layer.setTop(layero);
$('#codeList').val(formattedCodes);
// 添加自定义样式
$(layero).css({
'background-color': '#fff',
'box-shadow': '0 2px 8px rgba(0,0,0,.1)'
}).find('.layui-layer-content').css({
'padding': '30px'
});
}
});
// 设置最高层级
layer.setTop($('.layui-layer[times="' + index + '"]'));
});
// 复制到剪贴板
function copyToClipboard() {
var textarea = document.getElementById('codeList');
textarea.select();
try {
document.execCommand('copy');
layer.msg('复制成功!', {icon: 1, zIndex: layer.zIndex + 1001});
} catch (err) {
layer.msg('复制失败,请手动复制', {icon: 2, zIndex: layer.zIndex + 1001});
}
}
function setDays(days) {
document.getElementById('daysInput').value = days;
document.querySelector('form.form-search').submit();
}
</script>
{/block}

View File

@@ -26,10 +26,14 @@
{notempty name='list'}
<thead>
<tr>
<th class='text-left'>账号邮箱</th>
<th class='text-left'>设备ID</th>
<th class='text-left'>使用时间</th>
<th class='text-left'>注册时间</th>
<th class='text-left'>账号邮箱</th>
<th class='text-left'>代理商</th>
<th class='text-left'>激活码数量</th>
<th class='text-left'>总天数</th>
<th class='text-left'>首次激活</th>
<th class='text-left'>到期时间</th>
<th class='text-left'>剩余天数</th>
<th class='text-left'>缓存状态</th>
<th class='text-left'>冷却状态</th>
<th class='text-left'>操作</th>
@@ -39,24 +43,30 @@
<tbody>
{foreach $list as $key=>$vo}
<tr>
<td class='text-left'>{$vo.email}</td>
<td class='text-left'>{$vo.used_by}</td>
<td class='text-left'>{$vo.used_at|format_datetime}</td>
<td class='text-left'>{$vo.registration_time|format_datetime}</td>
<td class='text-left'>{$vo.used_by|default='--'}</td>
<td class='text-left'>{if isset($vo.email)}{$vo.email}{else}--{/if}</td>
<td class='text-left'>{if isset($vo.agent_names)}{$vo.agent_names}{else}--{/if}</td>
<td class='text-left'>{if isset($vo.total_codes)}{$vo.total_codes}{else}0{/if}</td>
<td class='text-left'>{if isset($vo.total_days)}{$vo.total_days} {else}0 {/if}</td>
<td class='text-left'>{if isset($vo.first_activation)}{$vo.first_activation|format_datetime}{else}--{/if}</td>
<td class='text-left'>{if isset($vo.expire_time)}{$vo.expire_time}{else}--{/if}</td>
<td class='text-left'>
{if $vo.has_cache}
{if $vo.cache_match}
<span class="color-green">正常</span>
{else}
<span class="color-red">异常(缓存不匹配)</span>
{/if}
{if isset($vo.days_left) && $vo.days_left > 0}
<span class="color-green">{$vo.days_left} </span>
{else}
<span class="color-red">已过期</span>
{/if}
</td>
<td class='text-left'>
{if isset($vo.has_cache) && $vo.has_cache}
<span class="color-green">已缓存</span>
{else}
<span class="color-gray">无缓存</span>
{/if}
</td>
<td class='text-left'>
{if $vo.in_cooldown}
<span class="color-red">冷却中 ( {$vo.cooldown_time})</span>
{if isset($vo.in_cooldown) && $vo.in_cooldown}
<span class="color-red">冷却中 {if isset($vo.cooldown_time)}( {$vo.cooldown_time}){/if}</span>
{else}
<span class="color-green">可用</span>
{/if}

View File

@@ -6,52 +6,79 @@
<div class="layui-form-item">
<label class="layui-form-label">生成数量</label>
<div class="layui-input-block">
<input name="count" value="1" required placeholder="请输入生成的激活码数量" class="layui-input">
<tip class="help-block">一次最多可生成100个激活码</tip>
<input type="number" name="count" value="1" min="1" max="500" required placeholder="请输入生成数量(1-500)" class="layui-input">
<tip class="help-block">单次可生成1-500个激活码</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">有效天数</label>
<div class="layui-input-block">
<input name="days" value="30" required placeholder="请输入激活码有效天数" class="layui-input">
<div class="layui-btn-group-days" style="margin-bottom: 10px;">
{foreach :app\manager\model\AgentCode::getCardTypes() as $days => $info}
<button type="button" class="layui-btn layui-btn-sm" data-days="{$days}">{$info.name}</button>
{/foreach}
</div>
<input type="number" name="days" value="30" min="1" required placeholder="请输入有效天数" class="layui-input">
<tip class="help-block">激活码的有效使用天数</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择代理</label>
<div class="layui-input-block">
<select name='agent_id' class='layui-select' lay-filter='agent_id'>
<select name='agent_id' class='layui-select' lay-search>
<option value=''>不分配给代理商</option>
{foreach $agents as $agent}
<option value='{$agent.id}'>{$agent.username} ({if $agent.level==1}一级代理{else}二级代理{/if}) - 佣金比例{$agent.commission_rate}%</option>
<option value='{$agent.id}'>{$agent.username} ({if $agent.level==1}一级代理{else}二级代理{/if})</option>
{/foreach}
</select>
<tip class="help-block">如果选择代理商,需要设置销售价格</tip>
<tip class="help-block">可以选择直接分配给代理商</tip>
</div>
</div>
<div class="layui-form-item" id="price_item" style="display:none;">
<div class="layui-form-item">
<label class="layui-form-label">销售价格</label>
<div class="layui-input-block">
<input name="price" value="0" placeholder="请输入销售价格" class="layui-input">
<tip class="help-block">设置激活码的销售价格,用于计算佣金</tip>
<input type="number" name="price" value="0" min="0" step="0.01" placeholder="请输入销售价格" class="layui-input">
<tip class="help-block">设置激活码的销售价格,用于计算代理商佣金(选择代理商时必填)</tip>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" type="submit">确定生成</button>
<button class="layui-btn layui-btn-danger" type="button" data-close>取消</button>
<button class="layui-btn" type='submit'>确定生成</button>
<button class="layui-btn layui-btn-danger" type='button' data-close>取消</button>
</div>
</form>
<script>
layui.form.on('select(agent_id)', function(data){
if(data.value){
$('#price_item').show();
$('input[name=price]').attr('required', true);
}else{
$('#price_item').hide();
$('input[name=price]').removeAttr('required');
}
layui.use(['form'], function(){
// 快捷选择天数
$('.layui-btn-group-days .layui-btn').click(function(){
var days = $(this).data('days');
$('input[name="days"]').val(days);
});
// 监听代理商选择
layui.form.on('select(agent_id)', function(data){
var price = $('input[name="price"]');
if(data.value){
price.attr('required', true);
} else {
price.removeAttr('required');
}
});
});
</script>
<style>
.layui-btn-group .layui-btn {
margin-right: 5px;
}
.layui-btn-group .layui-btn:hover {
opacity: 0.9;
}
</style>
{/block}

View File

@@ -0,0 +1,70 @@
{include file='../../admin/view/search'}
<fieldset>
<legend>条件搜索</legend>
<form class="layui-form layui-form-pane form-search" action="{:sysuri()}" onsubmit="return false" method="get" autocomplete="off">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">邮箱账号</label>
<div class="layui-input-inline">
<input name="email" value="{$get.email|default=''}" placeholder="请输入邮箱账号" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">姓名</label>
<div class="layui-input-inline">
<input name="first_name" value="{$get.first_name|default=''}" placeholder="请输入名字" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">机器ID</label>
<div class="layui-input-inline">
<input name="machine_id" value="{$get.machine_id|default=''}" placeholder="请输入机器ID" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用状态</label>
<div class="layui-input-inline">
<select class="layui-select" name="is_used">
<option value=''>-- 全部 --</option>
<option value='0' {if $get.is_used eq '0'}selected{/if}>未使用</option>
<option value='1' {if $get.is_used eq '1'}selected{/if}>已使用</option>
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用者</label>
<div class="layui-input-inline">
<input name="used_by" value="{$get.used_by|default=''}" placeholder="请输入使用者" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">注册时间</label>
<div class="layui-input-inline">
<input data-date-range name="registration_time" value="{$get.registration_time|default=''}" placeholder="请选择注册时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-inline">
<input data-date-range name="created_at" value="{$get.created_at|default=''}" placeholder="请选择创建时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">使用时间</label>
<div class="layui-input-inline">
<input data-date-range name="used_at" value="{$get.used_at|default=''}" placeholder="请选择使用时间" class="layui-input">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>

View File

@@ -47,6 +47,19 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">绑定管理员</label>
<div class="layui-input-block">
<select name='admin_id' class='layui-select'>
<option value=''>请选择管理员账号</option>
{foreach $admins as $admin}
<option value='{$admin.id}'>{$admin.username} ({$admin.nickname})</option>
{/foreach}
</select>
<tip class="help-block">选择要绑定的管理员账号,绑定后该管理员可以作为二级代理</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">佣金比例</label>
<div class="layui-input-block">

View File

@@ -6,6 +6,7 @@
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-block">
<input name="id" type="hidden" value='{$vo.id|default=""}'>
<input name="username" value='{$vo.username|default=""}' required placeholder="请输入用户名" class="layui-input">
</div>
</div>
@@ -13,8 +14,7 @@
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input type="password" name="password" value='' {if empty($vo)} required {/if} placeholder="请输入密码" class="layui-input">
<tip class="help-block">编辑时不填写表示不修改密码</tip>
<input type="password" name="password" value='' placeholder="请输入密码,不修改请留空" class="layui-input">
</div>
</div>
@@ -28,25 +28,39 @@
<div class="layui-form-item">
<label class="layui-form-label">代理级别</label>
<div class="layui-input-block">
<select name='level' class='layui-select' lay-filter='level'>
<option value='1' {if isset($vo.level) and $vo.level==1}selected{/if}>一级代理</option>
<option value='2' {if isset($vo.level) and $vo.level==2}selected{/if}>级代理</option>
<select name='level' class='layui-select' lay-filter='level' required>
<option value=''>请选择代理级别</option>
<option value='1' {if isset($vo.level) && $vo.level==1}selected{/if}>级代理</option>
<option value='2' {if isset($vo.level) && $vo.level==2}selected{/if}>二级代理</option>
</select>
</div>
</div>
<div class="layui-form-item parent-agent" style="display:none;">
<div class="layui-form-item parent-agent" style="display:{if isset($vo.level) && $vo.level==2}block{else}none{/if};">
<label class="layui-form-label">上级代理</label>
<div class="layui-input-block">
<select name='parent_id' class='layui-select'>
<option value=''>请选择上级代理</option>
{foreach $agents as $agent}
<option value='{$agent.id}' {if isset($vo.parent_id) and $vo.parent_id==$agent.id}selected{/if}>{$agent.username}</option>
<option value='{$agent.id}' {if isset($vo.parent_id) && $vo.parent_id==$agent.id}selected{/if}>{$agent.username}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">绑定管理员</label>
<div class="layui-input-block">
<select name='admin_id' class='layui-select'>
<option value=''>请选择管理员账号</option>
{foreach $admins as $admin}
<option value='{$admin.id}' {if isset($vo.admin_id) && $vo.admin_id==$admin.id}selected{/if}>{$admin.username} ({$admin.nickname})</option>
{/foreach}
</select>
<tip class="help-block">选择要绑定的管理员账号,绑定后该管理员可以作为二级代理</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">佣金比例</label>
<div class="layui-input-block">
@@ -59,8 +73,8 @@
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name='status' class='layui-select'>
<option value='1' {if isset($vo.status) and $vo.status==1}selected{/if}>正常</option>
<option value='0' {if isset($vo.status) and $vo.status==0}selected{/if}>禁用</option>
<option value='1' {if isset($vo.status) && $vo.status==1}selected{/if}>正常</option>
<option value='0' {if isset($vo.status) && $vo.status==0}selected{/if}>禁用</option>
</select>
</div>
</div>
@@ -75,7 +89,6 @@
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
{notempty name='vo.id'}<input type='hidden' value='{$vo.id}' name='id'>{/notempty}
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-close>取消编辑</button>
</div>
@@ -90,13 +103,5 @@ layui.form.on('select(level)', function(data){
$('select[name=parent_id]').val('');
}
});
// 页面加载完成时检查
$(function(){
var level = $('select[name=level]').val();
if(level == '2'){
$('.parent-agent').show();
}
});
</script>
{/block}

View File

@@ -0,0 +1,122 @@
{extend name="../../admin/view/main"}
{block name="content"}
<div class="layui-card">
<div class="layui-card-header">
<b>版本信息</b>
</div>
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">版本号</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="{$version.version_no}" readonly>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">版本名称</label>
<div class="layui-input-block">
<input type="text" class="layui-input" value="{$version.version_name}" readonly>
</div>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">
<b>权限控制</b>
<button class="layui-btn layui-btn-sm layui-btn-primary pull-right" data-open="{:url('control')}?version_id={$version.id}&mode=add">添加规则</button>
</div>
<div class="layui-card-body">
<table class="layui-table margin-top-10" lay-skin="line">
{notempty name='rules'}
<thead>
<tr>
<th>控制类型</th>
<th>目标类型</th>
<th>目标ID</th>
<th>过期时间</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
{/notempty}
<tbody>
{foreach $rules as $rule}
<tr>
<td>{if $rule.control_type eq 'white_list'}<span class="color-green">白名单</span>{else}<span class="color-red">黑名单</span>{/if}</td>
<td>
{switch $rule.target_type}
{case user}用户{/case}
{case agent}代理{/case}
{case device}设备{/case}
{/switch}
</td>
<td>{$rule.target_id}</td>
<td>{$rule.expire_time}</td>
<td>{$rule.create_time}</td>
<td>
<a class="layui-btn layui-btn-danger layui-btn-sm" data-confirm="确定要删除该规则吗?" data-action='{:url("removeRule")}' data-value="id#{$rule.id}"> </a>
</td>
</tr>
{/foreach}
</tbody>
</table>
{empty name='rules'}<span class="notdata">没有记录哦</span>{/empty}
</div>
</div>
{if isset($mode) and $mode eq 'add'}
<form class="layui-form layui-card" action="{:url('control')}" data-auto="true" method="post">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">控制类型</label>
<div class="layui-input-block">
<input type="radio" name="control_type" value="white_list" title="白名单" checked>
<input type="radio" name="control_type" value="black_list" title="黑名单">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">目标类型</label>
<div class="layui-input-block">
<select name="target_type" class="layui-select">
<option value="user">用户</option>
<option value="agent">代理</option>
<option value="device">设备</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">目标ID</label>
<div class="layui-input-block">
<input name="target_id" required class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">过期时间</label>
<div class="layui-input-block">
<input name="expire_time" required class="layui-input" id="expire_time">
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<input type='hidden' value='{$version.id}' name='version_id'>
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-close>取消</button>
</div>
</form>
{block name='script'}
<script>
laydate.render({
elem: '#expire_time',
type: 'datetime'
});
</script>
{/block}
{/if}

View File

@@ -0,0 +1,210 @@
{extend name="../../admin/view/main"}
{block name="content"}
<form class="layui-form layui-card" action="{:request()->url()}" data-auto="true" method="post">
<div class="layui-card-body">
<div class="layui-form-item">
<label class="layui-form-label">版本号</label>
<div class="layui-input-block">
<input name="version_no" value='{$vo.version_no|default=""}' required placeholder="请输入版本号(x.x.x)" class="layui-input">
<tip>版本号格式为: 主版本号.次版本号.修订号,如: 1.0.0</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">版本名称</label>
<div class="layui-input-block">
<input name="version_name" value='{$vo.version_name|default=""}' required placeholder="请输入版本名称" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新包</label>
<div class="layui-input-block">
<input type="hidden" name="download_url" value="{$vo.download_url|default=''}" class="layui-input">
<div class="layui-upload-drag" id="uploadBtn">
<i class="layui-icon layui-icon-upload"></i>
<div class="upload-title">点击上传或拖拽文件到此处</div>
<div class="upload-tips">支持的文件类型: zip, exe, dmg, pkg, deb, rpm</div>
</div>
<div class="upload-info" style="margin-top: 10px;display:none;">
<div class="layui-progress layui-progress-big" lay-filter="uploadProgress" lay-showpercent="true">
<div class="layui-progress-bar" lay-percent="0%"></div>
</div>
<div class="file-info" style="margin-top: 10px;"></div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">平台</label>
<div class="layui-input-block">
<select name="platform" lay-verify="required">
<option value="all" {if isset($vo.platform) and $vo.platform eq 'all'}selected{/if}>全平台</option>
<option value="windows" {if isset($vo.platform) and $vo.platform eq 'windows'}selected{/if}>Windows</option>
<option value="mac" {if isset($vo.platform) and $vo.platform eq 'mac'}selected{/if}>Mac</option>
<option value="linux" {if isset($vo.platform) and $vo.platform eq 'linux'}selected{/if}>Linux</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">强制更新</label>
<div class="layui-input-block">
<input type="radio" name="is_force" value="1" title="" {if isset($vo.is_force) and $vo.is_force eq 1}checked{/if}>
<input type="radio" name="is_force" value="0" title="" {if empty($vo.is_force) or $vo.is_force eq 0}checked{/if}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">最低要求</label>
<div class="layui-input-block">
<input name="min_version" value='{$vo.min_version|default=""}' placeholder="请输入最低要求版本号" class="layui-input">
<tip>如果设置了最低要求版本,低于此版本的用户需要先更新到此版本</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">版本描述</label>
<div class="layui-input-block">
<textarea name="description" placeholder="请输入版本描述" class="layui-textarea">{$vo.description|default=""}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">更新日志</label>
<div class="layui-input-block">
<div class="layui-card">
<div class="layui-card-body">
<div id="logItems">
{notempty name="logs"}
{foreach $logs as $log}
<div class="log-item layui-form-item">
<div class="layui-inline" style="width:120px">
<select name="logs[type][]" lay-verify="required">
<option value="feature" {if $log.type eq 'feature'}selected{/if}>新功能</option>
<option value="fix" {if $log.type eq 'fix'}selected{/if}>修复</option>
<option value="optimize" {if $log.type eq 'optimize'}selected{/if}>优化</option>
</select>
</div>
<div class="layui-inline" style="width:calc(100% - 180px)">
<input type="text" name="logs[content][]" value="{$log.content}" required placeholder="请输入更新内容" class="layui-input">
</div>
<div class="layui-inline" style="width:50px">
<a class="layui-btn layui-btn-danger layui-btn-sm" onclick="removeLogItem(this)">删除</a>
</div>
</div>
{/foreach}
{/notempty}
</div>
<div class="text-center padding-bottom-15">
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" onclick="addLogItem()">添加更新内容</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
{notempty name='id'}<input type='hidden' value='{$id}' name='id'>{/notempty}
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" type='button' data-close>取消</button>
</div>
</form>
{block name="script"}
<script>
// 添加更新日志项
function addLogItem() {
var html = `
<div class="log-item layui-form-item">
<div class="layui-inline" style="width:120px">
<select name="logs[type][]" lay-verify="required">
<option value="feature">新功能</option>
<option value="fix">修复</option>
<option value="optimize">优化</option>
</select>
</div>
<div class="layui-inline" style="width:calc(100% - 180px)">
<input type="text" name="logs[content][]" required placeholder="请输入更新内容" class="layui-input">
</div>
<div class="layui-inline" style="width:50px">
<a class="layui-btn layui-btn-danger layui-btn-sm" onclick="removeLogItem(this)">删除</a>
</div>
</div>`;
$('#logItems').append(html);
layui.form.render();
}
// 删除更新日志项
function removeLogItem(obj) {
$(obj).closest('.log-item').remove();
}
// 上传更新包
layui.use(['upload', 'form', 'element'], function(){
var upload = layui.upload;
var form = layui.form;
var element = layui.element;
upload.render({
elem: '#uploadBtn',
url: '{:url("upload")}',
accept: 'file',
exts: 'zip|exe|dmg|pkg|deb|rpm',
drag: true,
before: function(){
console.log('开始上传...');
$('.upload-info').show();
element.progress('uploadProgress', '0%');
$('.file-info').html('');
},
progress: function(n){
var percent = n + '%';
console.log('上传进度:', percent);
element.progress('uploadProgress', percent);
},
done: function(res){
console.log('上传响应:', res);
if(res.code == 0){
// 更新表单值
$('input[name=download_url]').val(res.data.url);
// 更新顶部输入框的值
$('input[name="version_no"]').val(res.data.version_no);
$('input[name="version_name"]').val(res.data.version_name);
// 显示文件信息
var html = '<div class="layui-badge layui-bg-green">上传成功</div> ';
html += '<div class="layui-inline">版本号: ' + res.data.version_no + '</div> ';
html += '<div class="layui-inline">版本名称: ' + res.data.version_name + '</div>';
$('.file-info').html(html);
// 重新渲染表单
form.render();
} else {
console.error('上传失败:', res.msg);
$('.file-info').html('<div class="layui-badge layui-bg-red">'+res.msg+'</div>');
}
},
error: function(index, upload){
console.error('上传错误:', index, upload);
$('.file-info').html('<div class="layui-badge layui-bg-red">上传失败</div>');
}
});
});
</script>
<style>
.upload-title { font-size: 16px; margin-bottom: 5px; }
.upload-tips { color: #999; font-size: 12px; }
.layui-upload-drag { padding: 30px; }
.layui-upload-drag .layui-icon { font-size: 50px; color: #009688; }
.file-info .layui-inline { margin-right: 10px; }
</style>
{/block}
{/block}

View File

@@ -0,0 +1,79 @@
{extend name="../../admin/view/main"}
{block name="button"}
<button class='layui-btn layui-btn-sm layui-btn-primary' data-open='{:url("add")}'>添加版本</button>
{/block}
{block name="content"}
<div class="think-box-shadow">
<table class="layui-table margin-top-10" lay-skin="line">
{notempty name='list'}
<thead>
<tr>
<th class='text-left nowrap'>版本号</th>
<th class='text-left nowrap'>版本名称</th>
<th class='text-left nowrap'>平台</th>
<th class='text-left nowrap'>下载地址</th>
<th class='text-left nowrap'>强制更新</th>
<th class='text-left nowrap'>最低要求</th>
<th class='text-left nowrap'>状态</th>
<th class='text-left nowrap'>创建时间</th>
<th class='text-left'>操作</th>
</tr>
</thead>
{/notempty}
<tbody>
{foreach $list as $key=>$vo}
<tr>
<td class='text-left nowrap'>{$vo.version_no|default=''}</td>
<td class='text-left nowrap'>{$vo.version_name|default=''}</td>
<td class='text-left nowrap'>{$vo.platform|default='all'}</td>
<td class='text-left nowrap'>
<a class="layui-btn layui-btn-sm layui-btn-normal" href="{:sysconf('site_domain')}{$vo.download_url|default=''}" target="_blank">下载</a>
<a class="layui-btn layui-btn-sm" data-copy="{:sysconf('site_domain')}{$vo.download_url|default=''}" data-tips-text="复制下载链接">
<i class="layui-icon">&#xe64c;</i>
</a>
<input type="text" class="layui-input layui-input-inline" style="width:200px;margin-left:5px;" value="{:sysconf('site_domain')}{$vo.download_url|default=''}" readonly>
</td>
<td class='text-left nowrap'>{if $vo.is_force}{else}{/if}</td>
<td class='text-left nowrap'>{$vo.min_version|default=''}</td>
<td class='text-left nowrap'>
{if $vo.status eq 1}
<span class="color-green">已启用</span>
{else}
<span class="color-red">已禁用</span>
{/if}
</td>
<td class='text-left nowrap'>{$vo.create_time|format_datetime}</td>
<td class='text-left'>
<a class="layui-btn layui-btn-sm" data-open='{:url("edit")}?id={$vo.id}'>编辑</a>
{if $vo.status eq 1}
<a class="layui-btn layui-btn-sm layui-btn-warm" data-action='{:url("state")}' data-value="id#{$vo.id};status#0">禁用</a>
{else}
<a class="layui-btn layui-btn-sm layui-btn-warm" data-action='{:url("state")}' data-value="id#{$vo.id};status#1">启用</a>
{/if}
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除该版本吗?" data-action='{:url("remove")}' data-value="id#{$vo.id}">删除</a>
</td>
</tr>
{/foreach}
</tbody>
</table>
{empty name='list'}<span class="notdata">没有记录哦</span>{/empty}
</div>
{/block}
{block name="script"}
<script>
// 注册复制事件
$('a[data-copy]').on('click', function () {
var text = $(this).data('copy');
var input = document.createElement('input');
input.value = text;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
$.msg.success('复制成功');
});
</script>
{/block}

View File

@@ -37,9 +37,9 @@ return [
// 密码
'password' => 'root', */
// 用户名
'username' => 'root',
'username' => 'cursortools',
// 密码
'password' => 'root',
'password' => 'TQ3hwykBxmARw3ya',
// 端口
'hostport' => '3306',
// 数据库连接参数
@@ -82,5 +82,12 @@ return [
'username' => '',
'password' => '',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 10,
'prefix' => null,
'pwd' => null
]
],
];

View File

@@ -1,31 +0,0 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class Agents extends Migrator
{
public function change()
{
$table = $this->table('cursor_agents', ['engine' => 'InnoDB']);
$table->addColumn('username', 'string', ['limit' => 50, 'null' => false, 'comment' => '代理商用户名'])
->addColumn('password', 'string', ['limit' => 255, 'null' => false, 'comment' => '密码'])
->addColumn('nickname', 'string', ['limit' => 50, 'null' => true, 'comment' => '昵称'])
->addColumn('parent_id', 'integer', ['null' => true, 'default' => 0, 'comment' => '上级代理ID0表示一级代理'])
->addColumn('level', 'integer', ['limit' => 1, 'null' => false, 'default' => 1, 'comment' => '代理级别1=一级代理2=二级代理'])
->addColumn('balance', 'decimal', ['precision' => 10, 'scale' => 2, 'default' => '0.00', 'comment' => '余额'])
->addColumn('total_income', 'decimal', ['precision' => 10, 'scale' => 2, 'default' => '0.00', 'comment' => '总收入'])
->addColumn('commission_rate', 'decimal', ['precision' => 5, 'scale' => 2, 'default' => '0.00', 'comment' => '佣金比例'])
->addColumn('status', 'integer', ['limit' => 1, 'null' => false, 'default' => 1, 'comment' => '状态0=禁用1=启用'])
->addColumn('remark', 'string', ['limit' => 255, 'null' => true, 'comment' => '备注'])
->addColumn('last_login_time', 'datetime', ['null' => true, 'comment' => '最后登录时间'])
->addColumn('last_login_ip', 'string', ['limit' => 50, 'null' => true, 'comment' => '最后登录IP'])
->addColumn('created_at', 'datetime', ['null' => false, 'comment' => '创建时间'])
->addColumn('updated_at', 'datetime', ['null' => false, 'comment' => '更新时间'])
->addIndex(['username'], ['unique' => true])
->addIndex(['parent_id'])
->addIndex(['level'])
->addIndex(['status'])
->create();
}
}

View File

@@ -1,25 +0,0 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class AgentCodes extends Migrator
{
public function change()
{
$table = $this->table('cursor_agent_codes', ['engine' => 'InnoDB']);
$table->addColumn('agent_id', 'integer', ['null' => false, 'comment' => '代理商ID'])
->addColumn('code_id', 'integer', ['null' => false, 'comment' => '激活码ID'])
->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2, 'default' => '0.00', 'comment' => '销售价格'])
->addColumn('commission', 'decimal', ['precision' => 10, 'scale' => 2, 'default' => '0.00', 'comment' => '佣金'])
->addColumn('parent_commission', 'decimal', ['precision' => 10, 'scale' => 2, 'default' => '0.00', 'comment' => '上级佣金'])
->addColumn('status', 'integer', ['limit' => 1, 'null' => false, 'default' => 0, 'comment' => '状态0=未结算1=已结算'])
->addColumn('settle_time', 'datetime', ['null' => true, 'comment' => '结算时间'])
->addColumn('created_at', 'datetime', ['null' => false, 'comment' => '创建时间'])
->addColumn('updated_at', 'datetime', ['null' => false, 'comment' => '更新时间'])
->addIndex(['agent_id'])
->addIndex(['code_id'])
->addIndex(['status'])
->create();
}
}

View File

@@ -0,0 +1,79 @@
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class CreateVersionTables extends Migrator
{
public function change()
{
// 添加系统配置
$this->addSystemConfig();
// 软件版本表
if (!$this->hasTable('version')) {
$this->table('version', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci'])
->addColumn('version_no', 'string', ['limit' => 50, 'null' => false, 'comment' => '版本号'])
->addColumn('version_name', 'string', ['limit' => 100, 'null' => false, 'comment' => '版本名称'])
->addColumn('download_url', 'string', ['limit' => 500, 'null' => false, 'comment' => '下载地址'])
->addColumn('is_force', 'boolean', ['null' => false, 'default' => 0, 'comment' => '是否强制更新'])
->addColumn('min_version', 'string', ['limit' => 50, 'null' => true, 'comment' => '最低要求版本'])
->addColumn('platform', 'string', ['limit' => 20, 'null' => false, 'default' => 'all', 'comment' => '平台(windows,mac,linux)'])
->addColumn('status', 'boolean', ['null' => false, 'default' => 1, 'comment' => '状态(0禁用,1启用)'])
->addColumn('description', 'text', ['null' => true, 'comment' => '版本描述'])
->addColumn('create_time', 'datetime', ['null' => true, 'comment' => '创建时间'])
->addColumn('update_time', 'datetime', ['null' => true, 'comment' => '更新时间'])
->addIndex('version_no', ['name' => 'idx_version_no'])
->create();
}
// 版本更新日志表
if (!$this->hasTable('version_log')) {
$this->table('version_log', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci'])
->addColumn('version_id', 'integer', ['limit' => 20, 'null' => false, 'comment' => '版本ID'])
->addColumn('type', 'string', ['limit' => 20, 'null' => false, 'default' => 'feature', 'comment' => '更新类型(feature,fix,optimize)'])
->addColumn('content', 'text', ['null' => false, 'comment' => '更新内容'])
->addColumn('create_time', 'datetime', ['null' => true, 'comment' => '创建时间'])
->addIndex('version_id', ['name' => 'idx_version_id'])
->create();
}
// 版本控制表
if (!$this->hasTable('version_control')) {
$this->table('version_control', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci'])
->addColumn('version_id', 'integer', ['limit' => 20, 'null' => false, 'comment' => '版本ID'])
->addColumn('control_type', 'string', ['limit' => 20, 'null' => false, 'comment' => '控制类型(white_list,black_list)'])
->addColumn('target_id', 'integer', ['limit' => 20, 'null' => false, 'comment' => '目标ID'])
->addColumn('target_type', 'string', ['limit' => 20, 'null' => false, 'comment' => '目标类型(user,agent,device)'])
->addColumn('expire_time', 'datetime', ['null' => true, 'comment' => '过期时间'])
->addColumn('create_time', 'datetime', ['null' => true, 'comment' => '创建时间'])
->addColumn('update_time', 'datetime', ['null' => true, 'comment' => '更新时间'])
->addIndex(['version_id', 'target_id', 'target_type'], ['name' => 'idx_version_target'])
->create();
}
}
/**
* 添加系统配置
*/
protected function addSystemConfig()
{
// 检查是否存在配置
$exists = \think\facade\Db::name('system_config')
->where('type', 'base')
->where('name', 'site_domain')
->find();
if (!$exists) {
// 添加站点域名配置
\think\facade\Db::name('system_config')->insert([
'type' => 'base',
'name' => 'site_domain',
'value' => '',
'title' => '站点域名',
'description' => '请设置站点访问域名,例如: https://www.example.com',
'create_at' => date('Y-m-d H:i:s'),
]);
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
use think\migration\Migrator;
use think\facade\Db;
class AddVersionMenu extends Migrator
{
public function change()
{
// 检查是否已存在版本管理菜单
$exists = Db::name('system_menu')->where(['node' => 'version_manage'])->find();
if (!$exists) {
// 添加版本管理主菜单
$pid = Db::name('system_menu')->insertGetId([
'pid' => 0,
'title' => '版本管理',
'icon' => 'layui-icon layui-icon-template-1',
'node' => 'version_manage',
'url' => '#',
'sort' => 100,
'status' => 1,
'create_at' => date('Y-m-d H:i:s'),
]);
// 添加版本列表菜单
$menu_id = Db::name('system_menu')->insertGetId([
'pid' => $pid,
'title' => '版本列表',
'icon' => 'layui-icon layui-icon-list',
'node' => 'version_manage/version/index',
'url' => 'manager/version/index',
'sort' => 100,
'status' => 1,
'create_at' => date('Y-m-d H:i:s'),
]);
// 添加操作权限节点(不显示在菜单中)
Db::name('system_menu')->insertAll([
[
'pid' => $menu_id,
'title' => '添加版本',
'icon' => 'layui-icon layui-icon-add-1',
'node' => 'version_manage/version/add',
'url' => 'manager/version/add',
'sort' => 100,
'status' => 0, // 不显示在菜单中
'create_at' => date('Y-m-d H:i:s'),
],
[
'pid' => $menu_id,
'title' => '编辑版本',
'icon' => 'layui-icon layui-icon-edit',
'node' => 'version_manage/version/edit',
'url' => 'manager/version/edit',
'sort' => 100,
'status' => 0, // 不显示在菜单中
'create_at' => date('Y-m-d H:i:s'),
],
[
'pid' => $menu_id,
'title' => '删除版本',
'icon' => 'layui-icon layui-icon-delete',
'node' => 'version_manage/version/remove',
'url' => 'manager/version/remove',
'sort' => 100,
'status' => 0, // 不显示在菜单中
'create_at' => date('Y-m-d H:i:s'),
],
[
'pid' => $menu_id,
'title' => '版本状态',
'icon' => 'layui-icon layui-icon-circle',
'node' => 'version_manage/version/state',
'url' => 'manager/version/state',
'sort' => 100,
'status' => 0, // 不显示在菜单中
'create_at' => date('Y-m-d H:i:s'),
]
]);
}
}
}