first commit

This commit is contained in:
2026-05-07 16:37:25 +08:00
commit 822ed9a099
67 changed files with 1799 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
<?php
namespace app\common\Trait;
use think\Response;
/**
* API 响应 Trait
* 提供统一的 JSON 响应格式
*/
trait ApiResponse
{
/**
* 成功响应
*
* @param mixed $data 响应数据
* @param string $msg 响应消息
* @param int $code 响应码
* @param int $httpCode HTTP状态码
* @return Response
*/
protected function successResponse($data = [], $msg = '成功', $code = 200, $httpCode = 200)
{
return json([
'code' => $code,
'msg' => $msg,
'data' => $data
], $httpCode);
}
/**
* 错误响应
*
* @param string $msg 错误消息
* @param mixed $data 错误数据
* @param int $code 错误码
* @param int $httpCode HTTP状态码
* @return Response
*/
protected function errorResponse($msg = '失败', $data = null, $code = 400, $httpCode = 400)
{
return json([
'code' => $code,
'msg' => $msg,
'data' => $data
], $httpCode);
}
/**
* 自定义响应
*
* @param int $code 响应码
* @param string $msg 响应消息
* @param mixed $data 响应数据
* @param int $httpCode HTTP状态码
* @return Response
*/
protected function customResponse($code, $msg, $data = [], $httpCode = 200)
{
return json([
'code' => $code,
'msg' => $msg,
'data' => $data
], $httpCode);
}
}

View File

@@ -0,0 +1,26 @@
<?php
// app/middleware/Cors.php
declare(strict_types=1);
namespace app\admin\middleware;
class Cors
{
public function handle($request, \Closure $next)
{
// 设置CORS响应头
header('Access-Control-Allow-Origin: *'); // 允许的源
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, Content-Type, X-Requested-With, X-CSRF-Token');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 1728000');
// 处理CORS预检请求
if ($request->isOptions()) {
return response()->code(200); // 预检请求,允许通过
}
return $next($request); // 继续处理请求
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace app\common\model;
use think\Model;
class CompanyOperators extends Model
{
protected $table = 'company_operators';
public function company()
{
return $this->belongsTo(Companys::class, 'company_id', 'id');
}
public function getCompanyNameAttr($value, $data)
{
return $this->company->company_name ?? null;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace app\common\model;
use think\Model;
class Companys extends Model
{
protected $table = 'companys';
}

View File

@@ -0,0 +1,32 @@
<?php
namespace app\common\model;
use think\Model;
use app\common\model\CustomerEmployees;
class Customers extends Model
{
protected $table = 'customers';
public function employees()
{
return $this->hasMany(CustomerEmployees::class, 'customer_id', 'id');
}
/* 模型事件:在删除客户记录之后执行 */
public static function onAfterDelete($customer)
{
// 删除与该客户相关的员工记录
$customer->employees()->delete();
}
public function getCompanyOfAccount(){
return $this->hasMany(User::class,'company_id','id');
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace app\common\model;
use think\Model;
class Tickets extends Model
{
protected $table = 'tickets';
# 自动写入时间戳
protected $autoWriteTimestamp = true;
# 定义时间戳字段名
protected $createTime = 'create_time';
protected $updateTime = 'update_time';
# 定义字段类型
protected $type = [
'id' => 'integer',
'company_id' => 'integer',
'operator_id' => 'integer',
'user_id' => 'integer',
'website' => 'string',
'datanum' => 'string',
'progress' => 'integer',
'status' => 'integer',
'remark' => 'string',
'lock_time' => 'integer',
'create_time' => 'integer',
'update_time' => 'integer',
];
/*
时间戳获取器
*/
public function getCreateTimeAttr($value)
{
return date('Y-m-d H:i', $value);
}
public function getUpdateTimeAttr($value)
{
return date('Y-m-d H:i', $value);
}
public function getLockTimeAttr($value)
{
if (empty($value)) {
return '';
}
return date('Y-m-d H:i', $value);
}
/**
* 关联:工单关联负责人
*/
public function ticketStaff()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function ticketCompany()
{
return $this->belongsTo(Companys::class, 'company_id', 'id');
}
public function ticketOperator()
{
return $this->belongsTo(CompanyOperators::class, 'operator_id', 'id');
}
// --- 定义虚拟属性 (获取关联表的值) ---
public function getUsernameAttr($value, $data)
{
return $this->ticketStaff->username ?? '';
}
public function getCompanyNameAttr($value, $data)
{
return $this->ticketCompany->company_name ?? '';
}
public function getOperatorNameAttr($value, $data)
{
return $this->ticketOperator->operator_name ?? '';
}
}

16
app/common/model/User.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
namespace app\common\model;
use think\Model;
class User extends Model
{
protected $table = 'users';
public function company(){
return $this->belongsTo(Companys::class,'company_id','id');
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace app\common\service;
use app\common\model\User;
use think\facade\Session;
class AuthService
{
/**
* 验证用户登录状态
* @return array|false 返回用户信息数组或false
*/
public function login(string $username, string $password)
{
$user = User::with(['company'])
->where('username', $username)
->find();
if (!$user || !password_verify($password, $user['password'])) {
throw new \Exception('用户名或密码错误');
}
if ($user['status'] != 1) {
throw new \Exception('账户已禁用');
}
$userInfo = [
'company_name' => $user['company']['company_name'], // 所属公司名称
'username' => $user['username'],
'role' => $user['role'],
];
Session::set('user_info', $user);
return $userInfo;
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace app\common\service;
use app\common\model\Tickets;
use app\common\model\CompanyOperators;
use app\common\model\User;
use think\facade\Cache;
class TicketService
{
/**
* 获取工单列表业务逻辑
* @param array $params 外部传入的过滤参数
*/
public function getTicketList(array $params)
{
$query = Tickets::with(['ticketStaff', 'ticketCompany', 'ticketOperator']); // 查询构造器
// 状态筛选
if ($params['status'] !== null) {
$query->where('status', $params['status']);
}
// 运营人员筛选
if (!empty($params['operator_name'])) {
$query->where('operator_name', $params['operator_name']);
}
// 负责人筛选
if (!empty($params['username'])) {
$query->where('username', $params['username']);
}
// 关键词搜索
if (!empty($params['searchKeyword'])) {
$query->where(function ($q) use ($params) {
$q->where('website', 'like', '%' . $params['searchKeyword'] . '%')
->whereOr('operator_name', 'like', '%' . $params['searchKeyword'] . '%');
});
}
$order = strtolower($params['sortOrder']) === 'asc' ? 'asc' : 'desc';
// order -- 排序 paginate -- 分页(查询当前页的数据,还会自动计算总记录数)
$list = $query->order('create_time', $order)->paginate(
$params['limit'],
false,
[
'page' => $params['page'],
'query' => $params // 保持分页链接的查询参数
]
);
$list->each(function ($item) {
$item->hidden(['ticketStaff', 'ticketCompany', 'ticketOperator', 'company_id', 'operator_id', 'user_id']);
$item->append(['username', 'company_name', 'operator_name']);
return $item;
});
return $list;
}
/**
* 新增工单
*
*/
public function createTicket($data)
{
if (empty($data['website'])) {
throw new \Exception('网站不能为空');
}
$website = trim($data['website']);
// 并发锁
$lockKey = 'create_ticket_lock_' . md5($website);
if (Cache::has($lockKey)) {
throw new \Exception('当前该网址正在被创建工单中,请稍后重试');
}
Cache::set($lockKey, 1, 5); // 锁定5秒
try {
// 查找是否有未完成的同网站工单
$unfinishedTicket = Tickets::where('website', $website)
->whereIn('status', [0, 1])
->find();
if ($unfinishedTicket) {
Cache::delete($lockKey);
throw new \Exception('已存在未完成的同网站工单,无法创建');
}
// 当前无相同网站
$ticketResult = Tickets::create($data);
return $ticketResult;
} catch (\Exception $e) {
Cache::delete($lockKey);
throw $e;
}
}
/**
* 更新工单
* @param $id 工单id
* @param $data 外部传入的更新数据
* @param $uid 当前登录用户id
* @param $role 当前登录用户角色
* @return array
*/
public function updateTicket($id, $data, $allowFields, $uid, $isSuper = false)
{
$ticket = Tickets::where('id', $id)->find();
if (!$ticket) {
throw new \Exception('工单不存在');
}
// 当不是管理员,进入判断
if (!$isSuper) {
if (!is_null($ticket->user_id) && $ticket->user_id != $uid) {
throw new \Exception('没有权限修改该工单');
}
}
// 提取 $allowFields 允许修改的字段
$updateData = [];
foreach ($allowFields as $field) {
if (array_key_exists($field, $data)) {
$updateData[$field] = $data[$field];
}
}
// 如果没有有效数据需要更新
if (empty($updateData)) {
throw new \Exception('没有合法的更新字段');
}
return $ticket->save($updateData);
}
/**
* 获取所有运营人员名称 (下拉筛选用)-仅限后台使用
*
*/
public function getAllOperators()
{
$operators = CompanyOperators::with('company')->select();
$operators->each(function ($item) {
$item->hidden(['company_id', 'company', 'create_time', 'status']);
$item->append(['company_name']);
return $item;
});
return $operators;
}
/**
* 获取员工列表(下拉筛选用)
*/
public function getStaffList()
{
$map = [
'role' => 'user',
'status' => 1
];
$staffs = User::where($map)->field('id, username', 'real_name')->select();
return $staffs;
}
}