diff --git a/app/admin/controller/api/Account.php b/app/admin/controller/api/Account.php
new file mode 100644
index 0000000..d2ac1c5
--- /dev/null
+++ b/app/admin/controller/api/Account.php
@@ -0,0 +1,272 @@
+_vali([
+ 'email.require' => '邮箱不能为空',
+ 'password.require' => '密码不能为空',
+ 'first_name.require' => '名字不能为空',
+ 'last_name.require' => '姓氏不能为空',
+ 'access_token.require' => 'access_token不能为空',
+ 'refresh_token.require' => 'refresh_token不能为空',
+ 'registration_time.require' => '注册时间不能为空'
+ ]);
+
+ // 检查邮箱是否已存在
+ if (Db::name('cursor_accounts')->where('email', $data['email'])->find()) {
+ return json([
+ 'code' => 400,
+ 'msg' => '该邮箱已存在'
+ ]);
+ }
+
+ // 准备插入数据
+ $insertData = [
+ 'email' => $data['email'],
+ 'password' => $data['password'],
+ 'first_name' => $data['first_name'],
+ 'last_name' => $data['last_name'],
+ 'access_token' => $data['access_token'],
+ 'refresh_token' => $data['refresh_token'],
+ 'machine_id' => $data['machine_id'] ?? '',
+ 'user_agent' => $data['user_agent'] ?? '',
+ 'registration_time' => $data['registration_time'],
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'is_used' => 0
+ ];
+
+ // 插入数据
+ $id = Db::name('cursor_accounts')->insertGetId($insertData);
+
+ return json([
+ 'code' => 200,
+ 'msg' => '添加成功',
+ 'data' => ['id' => $id]
+ ]);
+
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => $e->getMessage()
+ ]);
+ }
+ }
+
+ /**
+ * 获取未使用的账号
+ */
+ public function getUnused()
+ {
+ try {
+ // 获取machine_id
+ $machineId = input('machine_id', '');
+ if (empty($machineId)) {
+ return json([
+ 'code' => 401,
+ 'msg' => '设备ID不能为空'
+ ]);
+ }
+
+ // 检查设备是否在冷却期
+ $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', $isInCooldown)
+ ]
+ ]);
+ }
+
+ // 查询设备激活状态
+ $activations = Db::name('cursor_activation_codes')
+ ->where('used_by', '=', $machineId)
+ ->where('is_used', '=', 1)
+ ->order('used_at desc')
+ ->select()
+ ->toArray();
+
+ if (empty($activations)) {
+ return json([
+ 'code' => 401,
+ 'msg' => '设备未激活'
+ ]);
+ }
+
+ // 计算总天数和到期时间
+ $totalDays = array_sum(array_column($activations, 'days'));
+ $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']);
+ $expireTime = $firstActivationTime + ($totalDays * 24 * 3600);
+
+ // 检查是否过期
+ if (time() > $expireTime) {
+ return json([
+ 'code' => 401,
+ 'msg' => '设备授权已过期'
+ ]);
+ }
+
+ // 检查缓存中是否有该设备最近使用的账号
+ $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)
+ ]
+ ]);
+ }
+ }
+
+ // 记录冷却期
+ $cooldownExpires = time() + 1800; // 30分钟冷却期
+ \think\facade\Cache::set($cooldownKey, $cooldownExpires, 1800);
+
+ // 开启事务
+ Db::startTrans();
+ try {
+ // 获取一个未使用的账号
+ $account = Db::name('cursor_accounts')
+ ->where('is_used', 0)
+ ->lock(true)
+ ->find();
+
+ if (empty($account)) {
+ Db::rollback();
+ return json([
+ 'code' => 404,
+ 'msg' => '没有可用的账号'
+ ]);
+ }
+
+ // 如果有旧账号,将其标记为未使用
+ if ($cachedAccount) {
+ Db::name('cursor_accounts')
+ ->where('id', $cachedAccount['id'])
+ ->update([
+ 'is_used' => 0,
+ 'used_at' => null,
+ 'used_by' => ''
+ ]);
+ }
+
+ // 更新新账号状态
+ Db::name('cursor_accounts')
+ ->where('id', $account['id'])
+ ->update([
+ 'is_used' => 1,
+ 'used_at' => date('Y-m-d H:i:s'),
+ 'used_by' => $machineId
+ ]);
+
+ Db::commit();
+
+ // 缓存新账号信息
+ \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)
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ Db::rollback();
+ throw $e;
+ }
+
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => $e->getMessage()
+ ]);
+ }
+ }
+
+ /**
+ * 后台强制重置设备账号
+ * @auth true
+ */
+ public function resetDeviceAccount()
+ {
+ try {
+ $data = $this->_vali([
+ 'machine_id.require' => '设备ID不能为空'
+ ]);
+
+ $machineId = $data['machine_id'];
+
+ // 清除设备的账号缓存
+ $cacheKey = "device_account_{$machineId}";
+ \think\facade\Cache::delete($cacheKey);
+
+ // 清除冷却期
+ $cooldownKey = "device_cooldown_{$machineId}";
+ \think\facade\Cache::delete($cooldownKey);
+
+ // 将该设备使用的账号标记为未使用
+ Db::name('cursor_accounts')
+ ->where('used_by', $machineId)
+ ->update([
+ 'is_used' => 0,
+ 'used_at' => null,
+ 'used_by' => ''
+ ]);
+
+ return json([
+ 'code' => 200,
+ 'msg' => '重置成功'
+ ]);
+
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => $e->getMessage()
+ ]);
+ }
+ }
+}
diff --git a/app/admin/controller/api/Agent.php b/app/admin/controller/api/Agent.php
new file mode 100644
index 0000000..caa1698
--- /dev/null
+++ b/app/admin/controller/api/Agent.php
@@ -0,0 +1,225 @@
+_vali([
+ 'username.require' => '用户名不能为空',
+ 'password.require' => '密码不能为空'
+ ]);
+
+ // 查询代理商
+ $agent = Db::name('cursor_agents')
+ ->where('username', $data['username'])
+ ->find();
+
+ if (empty($agent)) {
+ return json(['code' => 400, 'msg' => '用户名或密码错误']);
+ }
+
+ // 验证密码
+ if (!password_verify($data['password'], $agent['password'])) {
+ return json(['code' => 400, 'msg' => '用户名或密码错误']);
+ }
+
+ // 检查状态
+ if ($agent['status'] != 1) {
+ return json(['code' => 400, 'msg' => '账号已被禁用']);
+ }
+
+ // 生成token
+ $token = md5($agent['id'] . time() . rand(1000, 9999));
+
+ // 存储token,有效期7天
+ Cache::set('agent_token_' . $token, $agent['id'], 7 * 24 * 3600);
+
+ // 更新登录信息
+ Db::name('cursor_agents')
+ ->where('id', $agent['id'])
+ ->update([
+ 'last_login_time' => date('Y-m-d H:i:s'),
+ 'last_login_ip' => $this->request->ip(),
+ 'updated_at' => date('Y-m-d H:i:s')
+ ]);
+
+ // 返回登录信息
+ return json([
+ 'code' => 200,
+ 'msg' => '登录成功',
+ 'data' => [
+ 'token' => $token,
+ 'agent' => [
+ 'id' => $agent['id'],
+ 'username' => $agent['username'],
+ 'nickname' => $agent['nickname'],
+ 'level' => $agent['level'],
+ 'balance' => $agent['balance'],
+ 'commission_rate' => $agent['commission_rate']
+ ]
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ return json(['code' => 500, 'msg' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 验证token
+ */
+ protected function checkToken()
+ {
+ $token = $this->request->header('token');
+ if (empty($token)) {
+ return json(['code' => 401, 'msg' => '请先登录']);
+ }
+
+ $agentId = Cache::get('agent_token_' . $token);
+ if (empty($agentId)) {
+ return json(['code' => 401, 'msg' => '登录已过期,请重新登录']);
+ }
+
+ return $agentId;
+ }
+
+ /**
+ * 获取代理商信息
+ */
+ public function info()
+ {
+ try {
+ $agentId = $this->checkToken();
+ if (!is_numeric($agentId)) {
+ return $agentId; // 返回错误信息
+ }
+
+ $agent = Db::name('cursor_agents')
+ ->where('id', $agentId)
+ ->find();
+
+ if (empty($agent)) {
+ return json(['code' => 400, 'msg' => '代理商不存在']);
+ }
+
+ // 获取激活码统计
+ $codeStats = Db::name('cursor_agent_codes')
+ ->where('agent_id', $agentId)
+ ->field([
+ 'COUNT(*) as total_codes',
+ 'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as settled_codes',
+ 'SUM(commission) as total_commission',
+ 'SUM(CASE WHEN status = 1 THEN commission ELSE 0 END) as settled_commission'
+ ])
+ ->find();
+
+ // 获取未使用的激活码
+ $unusedCodes = Db::name('cursor_agent_codes')
+ ->alias('ac')
+ ->join('cursor_activation_codes c', 'ac.code_id = c.id')
+ ->where('ac.agent_id', $agentId)
+ ->where('c.is_used', 0)
+ ->field('c.code, c.days, ac.price')
+ ->select()
+ ->toArray();
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => [
+ 'agent' => [
+ 'id' => $agent['id'],
+ 'username' => $agent['username'],
+ 'nickname' => $agent['nickname'],
+ 'level' => $agent['level'],
+ 'balance' => $agent['balance'],
+ 'total_income' => $agent['total_income'],
+ 'commission_rate' => $agent['commission_rate'],
+ 'last_login_time' => $agent['last_login_time']
+ ],
+ 'stats' => [
+ 'total_codes' => $codeStats['total_codes'] ?? 0,
+ 'settled_codes' => $codeStats['settled_codes'] ?? 0,
+ 'total_commission' => $codeStats['total_commission'] ?? 0,
+ 'settled_commission' => $codeStats['settled_commission'] ?? 0
+ ],
+ 'unused_codes' => $unusedCodes
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ return json(['code' => 500, 'msg' => $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 获取代理商的激活码列表
+ */
+ public function codes()
+ {
+ try {
+ $agentId = $this->checkToken();
+ if (!is_numeric($agentId)) {
+ return $agentId;
+ }
+
+ $page = input('page/d', 1);
+ $limit = input('limit/d', 20);
+ $status = input('status');
+
+ $query = Db::name('cursor_agent_codes')
+ ->alias('ac')
+ ->join('cursor_activation_codes c', 'ac.code_id = c.id')
+ ->where('ac.agent_id', $agentId);
+
+ if (isset($status)) {
+ $query = $query->where('c.is_used', intval($status));
+ }
+
+ $total = $query->count();
+ $list = $query->field([
+ 'c.code',
+ 'c.days',
+ 'c.is_used',
+ 'c.used_at',
+ 'c.used_by',
+ 'ac.price',
+ 'ac.commission',
+ 'ac.status as settle_status',
+ 'ac.created_at'
+ ])
+ ->page($page, $limit)
+ ->order('ac.created_at desc')
+ ->select()
+ ->toArray();
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => [
+ 'list' => $list,
+ 'total' => $total,
+ 'page' => $page,
+ 'limit' => $limit
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ return json(['code' => 500, 'msg' => $e->getMessage()]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/admin/controller/api/Mail.php b/app/admin/controller/api/Mail.php
index 3ac156c..a6e75db 100644
--- a/app/admin/controller/api/Mail.php
+++ b/app/admin/controller/api/Mail.php
@@ -35,6 +35,16 @@ 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' => '备用配置2',
+ 'config' => [
+ 'DOMAIN' => 'wuen.site',
+ 'TEMP_MAIL' => 'actoke',
+ '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'
+ ]
]
];
diff --git a/app/admin/controller/api/Member.php b/app/admin/controller/api/Member.php
index 268171b..3489061 100644
--- a/app/admin/controller/api/Member.php
+++ b/app/admin/controller/api/Member.php
@@ -5,6 +5,7 @@ namespace app\admin\controller\api;
use app\manager\model\Member as MemberModel;
use think\admin\Controller;
+use think\facade\Db;
/**
@@ -82,4 +83,208 @@ class Member extends Controller
]
]);
}
+
+ /**
+ * 激活码验证并激活
+ * @return void
+ */
+ public function activate()
+ {
+ try {
+ // 验证激活码
+ $code = input('code');
+ if (empty($code)) {
+ return json(['code' => 400, 'msg' => '激活码不能为空']);
+ }
+
+ // 获取设备信息
+ $machineId = input('machine_id', '');
+ if (empty($machineId)) {
+ return json(['code' => 400, 'msg' => '设备ID不能为空']);
+ }
+
+ // 收集设备详细信息
+ $deviceInfo = [
+ 'machine_id' => $machineId,
+ 'os' => input('os', ''), // 操作系统信息
+ 'device_name' => input('device_name', ''), // 设备名称
+ 'ip' => $this->request->ip(), // IP地址
+ 'user_agent' => $this->request->header('user-agent', ''), // UA信息
+ 'location' => input('location', '') // 地理位置(如果有)
+ ];
+
+ // 获取激活码信息
+ $activation = Db::name('cursor_activation_codes')
+ ->where('code', $code)
+ ->where('is_used', 0)
+ ->find();
+
+ if (empty($activation)) {
+ return json(['code' => 400, 'msg' => '激活码无效或已被使用']);
+ }
+
+ // 开启事务
+ Db::startTrans();
+ try {
+ // 标记新激活码为已使用
+ Db::name('cursor_activation_codes')
+ ->where('id', $activation['id'])
+ ->update([
+ 'is_used' => 1,
+ 'used_at' => date('Y-m-d H:i:s'),
+ 'used_by' => $machineId,
+ 'device_info' => json_encode($deviceInfo)
+ ]);
+
+ Db::commit();
+
+ // 获取该设备所有的激活记录
+ $activations = Db::name('cursor_activation_codes')
+ ->where('used_by', '=', $machineId)
+ ->where('is_used', '=', 1)
+ ->order('used_at desc')
+ ->select()
+ ->toArray();
+
+ // 计算总天数
+ $totalDays = array_sum(array_column($activations, 'days'));
+
+ // 计算最终到期时间
+ $now = time();
+ $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); // 第一次激活时间
+ $expireTime = $firstActivationTime + ($totalDays * 24 * 3600); // 总天数转换为秒数
+ $daysLeft = ceil(($expireTime - $now) / 86400);
+
+ // 返回成功信息
+ $result = [
+ 'code' => 200,
+ 'msg' => '激活成功',
+ 'data' => [
+ 'expire_time' => date('Y-m-d H:i:s', $expireTime),
+ 'total_days' => $totalDays,
+ 'added_days' => $activation['days'],
+ 'days_left' => $daysLeft,
+ 'machine_id' => $machineId,
+ 'device_info' => $deviceInfo,
+ 'activation_time' => date('Y-m-d H:i:s'),
+ 'activation_records' => array_map(function($record) {
+ return [
+ 'code' => $record['code'],
+ 'days' => $record['days'],
+ 'activation_time' => $record['used_at'],
+ 'device_info' => json_decode($record['device_info'], true)
+ ];
+ }, $activations)
+ ]
+ ];
+
+ return json($result);
+
+ } catch (\Exception $e) {
+ Db::rollback();
+ throw $e;
+ }
+
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => $e->getMessage()
+ ]);
+ }
+ }
+
+ /**
+ * 获取会员状态
+ */
+ public function status()
+ {
+ try {
+ // 获取设备ID
+ $machineId = input('machine_id', '');
+ if (empty($machineId)) {
+ return json(['code' => 400, 'msg' => '设备ID不能为空']);
+ }
+
+ // 获取该设备所有的激活记录
+ $activations = Db::name('cursor_activation_codes')
+ ->where('used_by', '=', $machineId)
+ ->where('is_used', '=', 1)
+ ->order('used_at desc')
+ ->select()
+ ->toArray();
+
+ if (empty($activations)) {
+ return json([
+ 'code' => 401,
+ 'msg' => '未激活',
+ 'data' => [
+ 'status' => 'inactive',
+ 'expire_time' => '',
+ 'total_days' => 0,
+ 'days_left' => 0,
+ 'activation_records' => []
+ ]
+ ]);
+ }
+
+ // 计算总天数
+ $totalDays = array_sum(array_column($activations, 'days'));
+
+ // 计算最终到期时间
+ $now = time();
+ $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); // 第一次激活时间
+ $expireTime = $firstActivationTime + ($totalDays * 24 * 3600); // 总天数转换为秒数
+ $daysLeft = ceil(($expireTime - $now) / 86400);
+
+ // 判断是否过期
+ if ($daysLeft <= 0) {
+ return json([
+ 'code' => 401,
+ 'msg' => '已过期',
+ 'data' => [
+ 'status' => 'expired',
+ 'expire_time' => date('Y-m-d H:i:s', $expireTime),
+ 'total_days' => $totalDays,
+ 'days_left' => 0,
+ 'machine_id' => $machineId,
+ 'activation_records' => array_map(function($record) {
+ return [
+ 'code' => $record['code'],
+ 'days' => $record['days'],
+ 'activation_time' => $record['used_at'],
+ 'device_info' => json_decode($record['device_info'], true)
+ ];
+ }, $activations)
+ ]
+ ]);
+ }
+
+ // 返回正常状态
+ return json([
+ 'code' => 200,
+ 'msg' => '正常',
+ 'data' => [
+ 'status' => 'active',
+ 'expire_time' => date('Y-m-d H:i:s', $expireTime),
+ 'total_days' => $totalDays,
+ 'days_left' => $daysLeft,
+ 'machine_id' => $machineId,
+ 'activation_records' => array_map(function($record) {
+ return [
+ 'code' => $record['code'],
+ 'days' => $record['days'],
+ 'activation_time' => $record['used_at'],
+ 'device_info' => json_decode($record['device_info'], true)
+ ];
+ }, $activations)
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ return json([
+ 'code' => 500,
+ 'msg' => $e->getMessage()
+ ]);
+ }
+ }
}
\ No newline at end of file
diff --git a/app/agent/controller/Index.php b/app/agent/controller/Index.php
new file mode 100644
index 0000000..8091e65
--- /dev/null
+++ b/app/agent/controller/Index.php
@@ -0,0 +1,108 @@
+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();
+ }
+}
\ No newline at end of file
diff --git a/app/agent/view/index/codes.html b/app/agent/view/index/codes.html
new file mode 100644
index 0000000..d8ce2b3
--- /dev/null
+++ b/app/agent/view/index/codes.html
@@ -0,0 +1,83 @@
+{extend name="../../admin/view/main"}
+
+{block name="button"}
+
+{/block}
+
+{block name="content"}
+
+
+
+
+ {notempty name='list'}
+
+
+ | 激活码 |
+ 有效天数 |
+ 销售价格 |
+ 佣金 |
+ 使用状态 |
+ 使用时间 |
+ 使用者 |
+ 结算状态 |
+ 生成时间 |
+
+
+ {/notempty}
+
+ {foreach $list as $key=>$vo}
+
+ | {$vo.code} |
+ {$vo.days} 天 |
+ {$vo.price} |
+ {$vo.commission} |
+
+ {if $vo.is_used eq 1}
+ 已使用
+ {else}
+ 未使用
+ {/if}
+ |
+ {$vo.used_at|format_datetime|default='--'} |
+ {$vo.used_by|default='--'} |
+
+ {if $vo.status eq 1}
+ 已结算
+ {else}
+ 未结算
+ {/if}
+ |
+ {$vo.created_at|format_datetime} |
+
+ {/foreach}
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/app/agent/view/index/generate.html b/app/agent/view/index/generate.html
new file mode 100644
index 0000000..c38eeec
--- /dev/null
+++ b/app/agent/view/index/generate.html
@@ -0,0 +1,20 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/controller/Account.php b/app/manager/controller/Account.php
new file mode 100644
index 0000000..3ae0c3b
--- /dev/null
+++ b/app/manager/controller/Account.php
@@ -0,0 +1,304 @@
+title = 'Cursor账号管理';
+ // 创建查询对象
+ $query = $this->_query('cursor_accounts');
+ // 数据列表处理
+ $query->equal('is_used')->like('email');
+ // 列表排序并显示
+ $query->order('id desc')->page();
+ }
+
+ /**
+ * 生成激活码
+ * @auth true
+ */
+ public function generate()
+ {
+ if ($this->request->isPost()) {
+ // 获取参数
+ $count = intval(input('count', 1));
+ $days = intval(input('days', 30));
+ $agentId = input('agent_id', '');
+ $price = floatval(input('price', 0));
+
+ // 参数验证
+ if ($count < 1 || $count > 100) {
+ $this->error('生成数量必须在1-100之间');
+ }
+ if ($days < 1) {
+ $this->error('有效天数必须大于0');
+ }
+
+ // 开启事务
+ \think\facade\Db::startTrans();
+
+ $codes = [];
+ $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' => $days,
+ 'created_at' => $now,
+ 'is_used' => 0
+ ];
+ }
+
+ // 批量插入激活码
+ $result = \think\facade\Db::name('cursor_activation_codes')->insertAll($codes);
+ if (!$result) {
+ \think\facade\Db::rollback();
+ $this->error('激活码生成失败');
+ }
+
+ // 获取插入的ID范围
+ $firstId = \think\facade\Db::name('cursor_activation_codes')->getLastInsID();
+ $lastId = $firstId + count($codes) - 1;
+
+ // 如果指定了代理商,则自动分配
+ if (!empty($agentId) && $price > 0) {
+ $agent = \think\facade\Db::name('cursor_agents')
+ ->where('id', $agentId)
+ ->find();
+
+ if ($agent) {
+ $agentCodes = [];
+ $commission = $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 = $price * ($parent['commission_rate'] / 100);
+ }
+ }
+
+ // 准备代理商激活码数据
+ for ($id = $firstId; $id <= $lastId; $id++) {
+ $agentCodes[] = [
+ 'agent_id' => $agent['id'],
+ 'code_id' => $id,
+ 'price' => $price,
+ 'commission' => $commission,
+ 'parent_commission' => $parentCommission,
+ 'status' => 0,
+ 'created_at' => $now,
+ 'updated_at' => $now
+ ];
+ }
+
+ // 批量插入代理商激活码关联
+ if (!empty($agentCodes)) {
+ $result = \think\facade\Db::name('cursor_agent_codes')->insertAll($agentCodes);
+ if (!$result) {
+ \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 codes()
+ {
+ $this->title = '激活码管理';
+
+ // 获取代理商列表用于筛选
+ $this->agents = \think\facade\Db::name('cursor_agents')
+ ->where('status', 1)
+ ->select()
+ ->toArray();
+
+ // 创建查询对象
+ $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.*',
+ 'a.id as agent_id',
+ 'a.username as agent_name',
+ 'a.level as agent_level',
+ 'ac.price',
+ 'ac.commission',
+ 'ac.parent_commission',
+ 'ac.status as settle_status'
+ ]);
+
+ // 数据列表处理
+ $query->where(function($query) {
+ $agentId = input('agent_id', '');
+ if ($agentId !== '') {
+ $query->where('a.id', '=', $agentId);
+ }
+
+ $isUsed = input('is_used', '');
+ if ($isUsed !== '') {
+ $query->where('c.is_used', '=', $isUsed);
+ }
+
+ $code = input('code', '');
+ if ($code) {
+ $query->whereLike('c.code', "%{$code}%");
+ }
+
+ $usedBy = input('used_by', '');
+ if ($usedBy) {
+ $query->whereLike('c.used_by', "%{$usedBy}%");
+ }
+ });
+
+ // 列表排序并显示
+ $query->order('c.id desc')->page();
+ }
+
+ /**
+ * 修改账号状态
+ * @auth true
+ */
+ public function state()
+ {
+ $this->_applyFormToken();
+ $this->_save('cursor_accounts', ['is_used' => input('state')]);
+ }
+
+ /**
+ * 删除账号
+ * @auth true
+ */
+ public function remove()
+ {
+ $this->_applyFormToken();
+ $this->_delete('cursor_accounts');
+ }
+
+ /**
+ * 删除激活码
+ * @auth true
+ */
+ public function removeCode()
+ {
+ $this->_applyFormToken();
+ $this->_delete('cursor_activation_codes');
+ }
+
+ /**
+ * 设备账号管理
+ * @auth true
+ * @menu true
+ */
+ public function devices()
+ {
+ $this->title = '设备账号管理';
+ // 创建查询对象
+ $query = $this->_query('cursor_accounts')
+ ->where('used_by', '<>', '')
+ ->where('is_used', 1);
+
+ // 数据列表处理
+ $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']);
+ }
+ }
+ }
+
+ $this->list = $list;
+ }
+
+ /**
+ * 重置设备账号
+ * @auth true
+ */
+ public function resetDevice()
+ {
+ $this->_applyFormToken();
+ try {
+ $machineId = input('machine_id', '');
+ if (empty($machineId)) {
+ $this->error('设备ID不能为空');
+ }
+
+ // 清除设备的账号缓存
+ $cacheKey = "device_account_{$machineId}";
+ \think\facade\Cache::delete($cacheKey);
+
+ // 清除冷却期
+ $cooldownKey = "device_cooldown_{$machineId}";
+ \think\facade\Cache::delete($cooldownKey);
+
+ // 将该设备使用的账号标记为未使用
+ \think\facade\Db::name('cursor_accounts')
+ ->where('used_by', $machineId)
+ ->update([
+ 'is_used' => 0,
+ 'used_at' => null,
+ 'used_by' => ''
+ ]);
+
+ $this->success('重置成功!');
+ } catch (\Exception $e) {
+ $this->error("重置失败:{$e->getMessage()}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/manager/controller/Agent.php b/app/manager/controller/Agent.php
new file mode 100644
index 0000000..574fd32
--- /dev/null
+++ b/app/manager/controller/Agent.php
@@ -0,0 +1,275 @@
+title = '代理商管理';
+ // 创建查询对象
+ $query = $this->_query('cursor_agents');
+ // 数据列表处理
+ $query->equal('level,status')->like('username,nickname');
+ // 列表排序并显示
+ $query->order('id desc')->page();
+ }
+
+ /**
+ * 添加代理商
+ * @auth true
+ */
+ public function add()
+ {
+ if ($this->request->isPost()) {
+ $data = $this->_vali([
+ 'username.require' => '用户名不能为空',
+ 'password.require' => '密码不能为空',
+ 'nickname.default' => '',
+ 'level.require' => '代理级别不能为空',
+ 'parent_id.default' => 0,
+ 'commission_rate.require' => '佣金比例不能为空',
+ 'status.default' => 1,
+ 'remark.default' => ''
+ ]);
+
+ // 检查用户名是否存在
+ if (Db::name('cursor_agents')->where('username', $data['username'])->find()) {
+ $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('上级代理不存在或不是一级代理!');
+ }
+ }
+
+ // 处理密码
+ $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
+ $data['created_at'] = $data['updated_at'] = date('Y-m-d H:i:s');
+
+ if (Db::name('cursor_agents')->insert($data) !== false) {
+ $this->success('添加成功!');
+ } else {
+ $this->error('添加失败!');
+ }
+ } else {
+ // 获取一级代理列表(用于选择上级)
+ $this->agents = Db::name('cursor_agents')
+ ->where('level', 1)
+ ->where('status', 1)
+ ->select();
+ $this->fetch();
+ }
+ }
+
+ /**
+ * 编辑代理商
+ * @auth true
+ */
+ public function edit()
+ {
+ $this->_applyFormToken();
+ $this->_form('cursor_agents', 'form');
+ }
+
+ /**
+ * 修改代理商状态
+ * @auth true
+ */
+ public function state()
+ {
+ $this->_applyFormToken();
+ $this->_save('cursor_agents', ['status' => input('status')]);
+ }
+
+ /**
+ * 删除代理商
+ * @auth true
+ */
+ public function remove()
+ {
+ $this->_applyFormToken();
+ $this->_delete('cursor_agents');
+ }
+
+ /**
+ * 分配激活码
+ * @auth true
+ */
+ public function assignCodes()
+ {
+ if ($this->request->isPost()) {
+ $data = $this->_vali([
+ 'agent_id.require' => '代理商不能为空',
+ 'code_ids.require' => '请选择要分配的激活码',
+ 'price.require' => '请设置销售价格'
+ ]);
+
+ // 开启事务
+ Db::startTrans();
+ try {
+ $agent = Db::name('cursor_agents')->where('id', $data['agent_id'])->find();
+ if (empty($agent)) {
+ $this->error('代理商不存在!');
+ }
+
+ $codeIds = explode(',', $data['code_ids']);
+ $insertData = [];
+ $now = date('Y-m-d H:i:s');
+
+ foreach ($codeIds as $codeId) {
+ // 检查激活码是否已分配
+ $exists = Db::name('cursor_agent_codes')->where('code_id', $codeId)->find();
+ if ($exists) {
+ continue;
+ }
+
+ // 计算佣金
+ $commission = $data['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 = $data['price'] * ($parent['commission_rate'] / 100);
+ }
+ }
+
+ $insertData[] = [
+ 'agent_id' => $data['agent_id'],
+ 'code_id' => $codeId,
+ 'price' => $data['price'],
+ 'commission' => $commission,
+ 'parent_commission' => $parentCommission,
+ 'status' => 0,
+ 'created_at' => $now,
+ 'updated_at' => $now
+ ];
+ }
+
+ // 批量插入数据
+ if (!empty($insertData)) {
+ Db::name('cursor_agent_codes')->insertAll($insertData);
+ }
+
+ Db::commit();
+ $this->success('分配成功!');
+ } catch (\Exception $e) {
+ Db::rollback();
+ $this->error('分配失败:' . $e->getMessage());
+ }
+ } else {
+ // 获取未分配的激活码
+ $this->codes = Db::name('cursor_activation_codes')
+ ->where('is_used', 0)
+ ->whereNotExists(function($query) {
+ $query->table('cursor_agent_codes')->whereRaw('cursor_agent_codes.code_id = cursor_activation_codes.id');
+ })
+ ->select();
+
+ // 获取代理商列表
+ $this->agents = Db::name('cursor_agents')
+ ->where('status', 1)
+ ->select();
+
+ $this->fetch();
+ }
+ }
+
+ /**
+ * 查看代理商激活码
+ * @auth true
+ */
+ public function codes()
+ {
+ $this->title = '代理商激活码';
+ $query = $this->_query('cursor_agent_codes')->alias('ac')
+ ->join('cursor_agents a', 'a.id = ac.agent_id')
+ ->join('cursor_activation_codes c', 'c.id = ac.code_id')
+ ->field('ac.*, a.username as agent_name, c.code, c.days, c.is_used, c.used_at, c.used_by');
+
+ // 数据列表处理
+ $query->equal('ac.agent_id')->equal('c.is_used')->equal('ac.status');
+ // 列表排序并显示
+ $query->order('ac.id desc')->page();
+ }
+
+ /**
+ * 结算佣金
+ * @auth true
+ */
+ public function settle()
+ {
+ $id = input('id');
+ if (empty($id)) {
+ $this->error('参数错误!');
+ }
+
+ $agentCode = Db::name('cursor_agent_codes')->where('id', $id)->find();
+ if (empty($agentCode)) {
+ $this->error('记录不存在!');
+ }
+
+ if ($agentCode['status'] == 1) {
+ $this->error('该记录已结算!');
+ }
+
+ // 开启事务
+ Db::startTrans();
+ try {
+ // 更新代理商余额
+ Db::name('cursor_agents')
+ ->where('id', $agentCode['agent_id'])
+ ->inc('balance', $agentCode['commission'])
+ ->inc('total_income', $agentCode['commission'])
+ ->update();
+
+ // 如果有上级佣金,更新上级代理商余额
+ if ($agentCode['parent_commission'] > 0) {
+ $agent = Db::name('cursor_agents')->where('id', $agentCode['agent_id'])->find();
+ if ($agent && $agent['parent_id'] > 0) {
+ Db::name('cursor_agents')
+ ->where('id', $agent['parent_id'])
+ ->inc('balance', $agentCode['parent_commission'])
+ ->inc('total_income', $agentCode['parent_commission'])
+ ->update();
+ }
+ }
+
+ // 更新结算状态
+ Db::name('cursor_agent_codes')
+ ->where('id', $id)
+ ->update([
+ 'status' => 1,
+ 'settle_time' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s')
+ ]);
+
+ Db::commit();
+ $this->success('结算成功!');
+ } catch (\Exception $e) {
+ Db::rollback();
+ $this->error('结算失败:' . $e->getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/manager/model/ActivationCode.php b/app/manager/model/ActivationCode.php
new file mode 100644
index 0000000..d71b3c3
--- /dev/null
+++ b/app/manager/model/ActivationCode.php
@@ -0,0 +1,39 @@
+ 'datetime',
+ 'created_at' => 'datetime',
+ ];
+
+ // 获取代理商分配信息
+ public function agentCode()
+ {
+ return $this->hasOne('app\manager\model\AgentCode', 'code_id');
+ }
+
+ // 检查是否已分配给代理商
+ public function isAssigned()
+ {
+ return (bool)$this->agentCode;
+ }
+
+ // 检查是否可用
+ public function isAvailable()
+ {
+ return !$this->is_used && !$this->isAssigned();
+ }
+}
\ No newline at end of file
diff --git a/app/manager/model/Agent.php b/app/manager/model/Agent.php
new file mode 100644
index 0000000..bd68a22
--- /dev/null
+++ b/app/manager/model/Agent.php
@@ -0,0 +1,51 @@
+ 'float',
+ 'total_income' => 'float',
+ 'commission_rate' => 'float',
+ 'last_login_time' => 'datetime',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ // 获取父级代理
+ public function parent()
+ {
+ return $this->belongsTo(Agent::class, 'parent_id');
+ }
+
+ // 获取子级代理
+ public function children()
+ {
+ return $this->hasMany(Agent::class, 'parent_id');
+ }
+
+ // 获取代理的激活码
+ public function codes()
+ {
+ return $this->hasMany('app\manager\model\AgentCode', 'agent_id');
+ }
+
+ // 更新余额
+ public function updateBalance($amount)
+ {
+ $this->balance += $amount;
+ $this->total_income += max(0, $amount);
+ return $this->save();
+ }
+}
\ No newline at end of file
diff --git a/app/manager/model/AgentCode.php b/app/manager/model/AgentCode.php
new file mode 100644
index 0000000..cc27bce
--- /dev/null
+++ b/app/manager/model/AgentCode.php
@@ -0,0 +1,68 @@
+ 'float',
+ 'commission' => 'float',
+ 'parent_commission' => 'float',
+ 'settle_time' => 'datetime',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ // 获取所属代理商
+ public function agent()
+ {
+ return $this->belongsTo('app\manager\model\Agent', 'agent_id');
+ }
+
+ // 获取激活码信息
+ public function code()
+ {
+ return $this->belongsTo('app\manager\model\ActivationCode', 'code_id');
+ }
+
+ // 结算佣金
+ public function settle()
+ {
+ if ($this->status == 1) {
+ return false;
+ }
+
+ // 开启事务
+ $this->startTrans();
+ try {
+ // 更新代理商余额
+ $this->agent->updateBalance($this->commission);
+
+ // 如果有上级佣金,更新上级代理商余额
+ if ($this->parent_commission > 0 && $this->agent->parent) {
+ $this->agent->parent->updateBalance($this->parent_commission);
+ }
+
+ // 更新结算状态
+ $this->status = 1;
+ $this->settle_time = date('Y-m-d H:i:s');
+ $this->save();
+
+ $this->commit();
+ return true;
+ } catch (\Exception $e) {
+ $this->rollback();
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/manager/view/account/codes.html b/app/manager/view/account/codes.html
new file mode 100644
index 0000000..0f1f5b9
--- /dev/null
+++ b/app/manager/view/account/codes.html
@@ -0,0 +1,123 @@
+{extend name="../../admin/view/main"}
+
+{block name="button"}
+
+{/block}
+
+{block name="content"}
+
+
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/account/devices.html b/app/manager/view/account/devices.html
new file mode 100644
index 0000000..425dba4
--- /dev/null
+++ b/app/manager/view/account/devices.html
@@ -0,0 +1,75 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+
+
+
+ {notempty name='list'}
+
+
+ | 账号邮箱 |
+ 设备ID |
+ 使用时间 |
+ 注册时间 |
+ 缓存状态 |
+ 冷却状态 |
+ 操作 |
+
+
+ {/notempty}
+
+ {foreach $list as $key=>$vo}
+
+ | {$vo.email} |
+ {$vo.used_by} |
+ {$vo.used_at|format_datetime} |
+ {$vo.registration_time|format_datetime} |
+
+ {if $vo.has_cache}
+ {if $vo.cache_match}
+ 正常
+ {else}
+ 异常(缓存不匹配)
+ {/if}
+ {else}
+ 无缓存
+ {/if}
+ |
+
+ {if $vo.in_cooldown}
+ 冷却中 (至 {$vo.cooldown_time})
+ {else}
+ 可用
+ {/if}
+ |
+
+ {if auth("resetDevice")}
+ 重置账号
+ {/if}
+ |
+
+ {/foreach}
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/account/generate.html b/app/manager/view/account/generate.html
new file mode 100644
index 0000000..c5e18f0
--- /dev/null
+++ b/app/manager/view/account/generate.html
@@ -0,0 +1,57 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/account/index.html b/app/manager/view/account/index.html
new file mode 100644
index 0000000..29cdaff
--- /dev/null
+++ b/app/manager/view/account/index.html
@@ -0,0 +1,86 @@
+{extend name="../../admin/view/main"}
+
+{block name="button"}
+
+
+
+{/block}
+
+{block name="content"}
+
+
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/agent/add.html b/app/manager/view/agent/add.html
new file mode 100644
index 0000000..470d744
--- /dev/null
+++ b/app/manager/view/agent/add.html
@@ -0,0 +1,93 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/agent/assign_codes.html b/app/manager/view/agent/assign_codes.html
new file mode 100644
index 0000000..e45e74d
--- /dev/null
+++ b/app/manager/view/agent/assign_codes.html
@@ -0,0 +1,63 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/agent/codes.html b/app/manager/view/agent/codes.html
new file mode 100644
index 0000000..7551c0b
--- /dev/null
+++ b/app/manager/view/agent/codes.html
@@ -0,0 +1,103 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+
+
+
+ {notempty name='list'}
+
+
+ | 激活码 |
+ 代理商 |
+ 有效天数 |
+ 销售价格 |
+ 佣金 |
+ 上级佣金 |
+ 使用状态 |
+ 使用时间 |
+ 使用者 |
+ 结算状态 |
+ 分配时间 |
+ |
+
+
+ {/notempty}
+
+ {foreach $list as $key=>$vo}
+
+ | {$vo.code} |
+ {$vo.agent_name} |
+ {$vo.days} 天 |
+ {$vo.price} |
+ {$vo.commission} |
+ {$vo.parent_commission|default='--'} |
+
+ {if $vo.is_used eq 1}
+ 已使用
+ {else}
+ 未使用
+ {/if}
+ |
+ {$vo.used_at|format_datetime|default='--'} |
+ {$vo.used_by|default='--'} |
+
+ {if $vo.status eq 1}
+ 已结算
+ {else}
+ 未结算
+ {/if}
+ |
+ {$vo.created_at|format_datetime} |
+
+ {if auth("settle") and $vo.status eq 0 and $vo.is_used eq 1}
+ 结 算
+ {/if}
+ |
+
+ {/foreach}
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/agent/form.html b/app/manager/view/agent/form.html
new file mode 100644
index 0000000..ee94aec
--- /dev/null
+++ b/app/manager/view/agent/form.html
@@ -0,0 +1,102 @@
+{extend name="../../admin/view/main"}
+
+{block name="content"}
+
+
+
+{/block}
\ No newline at end of file
diff --git a/app/manager/view/agent/index.html b/app/manager/view/agent/index.html
new file mode 100644
index 0000000..fe8088d
--- /dev/null
+++ b/app/manager/view/agent/index.html
@@ -0,0 +1,123 @@
+{extend name="../../admin/view/main"}
+
+{block name="button"}
+
+
+
+
+
+
+{/block}
+
+{block name="content"}
+
+
+
+
+ {empty name='list'}
没有记录哦{else}{$pagehtml|raw|default=''}{/empty}
+
+{/block}
\ No newline at end of file
diff --git a/database/migrations/20240212001_agents.php b/database/migrations/20240212001_agents.php
new file mode 100644
index 0000000..4820f34
--- /dev/null
+++ b/database/migrations/20240212001_agents.php
@@ -0,0 +1,31 @@
+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' => '上级代理ID,0表示一级代理'])
+ ->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();
+ }
+}
\ No newline at end of file
diff --git a/database/migrations/20240212002_agent_codes.php b/database/migrations/20240212002_agent_codes.php
new file mode 100644
index 0000000..1217f99
--- /dev/null
+++ b/database/migrations/20240212002_agent_codes.php
@@ -0,0 +1,25 @@
+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();
+ }
+}
\ No newline at end of file