commit 822ed9a099bd705d8ee123089b8d6d10babc2ccd Author: GiveMePast <1248298477@qq.com> Date: Thu May 7 16:37:25 2026 +0800 first commit diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..c457fe5 --- /dev/null +++ b/.example.env @@ -0,0 +1,11 @@ +APP_DEBUG = true + +DB_TYPE = mysql +DB_HOST = 127.0.0.1 +DB_NAME = test +DB_USER = username +DB_PASS = password +DB_PORT = 3306 +DB_CHARSET = utf8 + +DEFAULT_LANG = zh-cn diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5054a67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.log +.env +composer.phar +composer.lock +.DS_Store +Thumbs.db +/.idea +/.vscode +/vendor +/.settings +/.buildpath +/.project \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8d94897 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2025 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..05c7b83 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +![](https://www.thinkphp.cn/uploads/images/20230630/300c856765af4d8ae758c503185f8739.png) + +ThinkPHP 8 +=============== + +## 特性 + +* 基于PHP`8.0+`重构 +* 升级`PSR`依赖 +* 依赖`think-orm`3.0+版本 +* 全新的`think-dumper`服务,支持远程调试 +* 支持`6.0`/`6.1`无缝升级 + +> ThinkPHP8的运行环境要求PHP8.0+ + +现在开始,你可以使用官方提供的[ThinkChat](https://chat.topthink.com/),让你在学习ThinkPHP的旅途中享受私人AI助理服务! + +![](https://www.topthink.com/uploads/assistant/20230630/4d1a3f0ad2958b49bb8189b7ef824cb0.png) + +ThinkPHP生态服务由[顶想云](https://www.topthink.com)(TOPThink Cloud)提供,为生态提供专业的开发者服务和价值之选。 + +## 文档 + +[完全开发手册](https://doc.thinkphp.cn) + + +## 赞助 + +全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及GIT仓库获得巨大曝光,同时提升企业的品牌声誉,也更好保障ThinkPHP的可持续发展。 + +[![](https://www.thinkphp.cn/sponsor/special.svg)](https://www.thinkphp.cn/sponsor/special) + +[![](https://www.thinkphp.cn/sponsor.svg)](https://www.thinkphp.cn/sponsor) + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 命名规范 + +`ThinkPHP`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +直接提交PR或者Issue即可 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2024 by ThinkPHP (http://thinkphp.cn) All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..96556e8 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,22 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + {} + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + +} diff --git a/app/ExceptionHandle.php b/app/ExceptionHandle.php new file mode 100644 index 0000000..453d126 --- /dev/null +++ b/app/ExceptionHandle.php @@ -0,0 +1,58 @@ +request->post(); + + # 参数校验 + if (empty($params['real_name'])) { + return json(['code' => 400, 'message' => '缺少必要参数']); + } + + # 业务逻辑 + try { + $result = $accountService->createAccount($params); + return json(['code' => 200, 'message' => '账户创建成功', 'data' => $result]); + } catch (\Exception $e) { + + return json([ + 'code' => 400, + 'message' => $e->getMessage(), + 'data' => null + ]); + } + } +} diff --git a/app/admin/controller/Ticket.php b/app/admin/controller/Ticket.php new file mode 100644 index 0000000..cf4c21f --- /dev/null +++ b/app/admin/controller/Ticket.php @@ -0,0 +1,84 @@ + $this->request->param('page', 1, 'intval'), + "limit" => $this->request->param('limit', 20, 'intval'), + "status" => $this->request->param('status', null), + "operator_name" => $this->request->param('operator_name', null), + "searchKeyword" => $this->request->param('search', null), + "sortOrder" => $this->request->param('sort_order', 'desc'), + ]; + + $result = $ticketService->getTicketList($params); + + + return json(['code' => 200, 'msg' => '查询成功', 'data' => $result]); + } + + + /** + * 新增工单 + */ + public function createTicket(TicketService $ticketService) + { + $data = $this->request->post(); + + try { + $result = $ticketService->createTicket($data); + return json(['code' => 200, 'msg' => '创建成功', 'data' => $result]); + } catch (\Exception $e) { + return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => null]); + } + } + + + /** + * 更新工单(后台调用) + */ + public function updateTicket(TicketService $ticketService, $id) + { + + $adminId = Session::get('id'); // 获取当前登录管理员ID + $data = $this->request->post(); + + $allowFields = ['company_id', 'operator_id', 'user_id', 'website', 'datanum', 'progress', 'status', 'remark']; + + try { + $result = $ticketService->updateTicket($id, $data, $allowFields, $adminId, true); + return json(['code' => 200, 'msg' => '更新成功', 'data' => $result]); + } catch (\Exception $e) { + return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => []]); + } + } + + /** + * 删除一个工单(仅后台可调用) + */ + public function deleteTicket($id) + { + $ticket = Tickets::find($id); + if (!$ticket) return json(['code' => false, 'msg' => '工单不存在', 'data' => []]); + + return $ticket->delete() + ? json(['code' => true, 'msg' => '删除成功', 'data' => []]) + : json(['code' => false, 'msg' => '删除失败', 'data' => []]); + } +} diff --git a/app/admin/controller/TicketFilter.php b/app/admin/controller/TicketFilter.php new file mode 100644 index 0000000..4f80144 --- /dev/null +++ b/app/admin/controller/TicketFilter.php @@ -0,0 +1,30 @@ +getAllOperators(); + + return json(['code' => 200, 'msg' => '查询成功', 'data' => $result]); + } + + + /** + * 获取我方员工列表 (下拉选择用) + */ + public function getStaffList(TicketService $ticketService) + { + $result = $ticketService->getStaffList(); + return json(['code' => 200, 'msg' => '查询成功', 'data' => $result]); + } +} diff --git a/app/admin/event.php b/app/admin/event.php new file mode 100644 index 0000000..4eff890 --- /dev/null +++ b/app/admin/event.php @@ -0,0 +1,5 @@ +login_user_id = (int)$payload['user_id'] ?? null; + $request->login_role = $payload['role'] ?? null; + + return $next($request); + }catch (TokenExpiredException $e){ + // 捕获token过期异常 + return json(['code' => 401, 'msg' => '登录已过期,请重新登录'],401); + + } catch (\Exception $e) { + // 如果报错,返回具体的错误信息,方便我们排查 + return json(['code' => 400, 'msg' => '权限校验失败:' . $e->getMessage()], 400); + } + } +} diff --git a/app/admin/route/app.php b/app/admin/route/app.php new file mode 100644 index 0000000..4607f27 --- /dev/null +++ b/app/admin/route/app.php @@ -0,0 +1,19 @@ +middleware(Auth::class); // 只有这里需要 Token \ No newline at end of file diff --git a/app/admin/service/AccountService.php b/app/admin/service/AccountService.php new file mode 100644 index 0000000..8f02408 --- /dev/null +++ b/app/admin/service/AccountService.php @@ -0,0 +1,34 @@ +find(); + if ($exist) { + throw new \Exception('用户名已存在'); + } + + $insertData = [ + 'username' => $data['username'] ?? $data['real_name'], + 'real_name' => $data['real_name'], + 'password' => password_hash('123456', PASSWORD_DEFAULT), + 'role' => $data['role'] ?? 'user', + 'status' => 1, + ]; + + $user = UserModel::create($insertData); + + /** @var \think\Model $user */ + return $user->visible(['id', 'username', 'real_name', 'role', 'status'])->toArray(); + } +} diff --git a/app/admin/service/CustomersService.php b/app/admin/service/CustomersService.php new file mode 100644 index 0000000..9113fa8 --- /dev/null +++ b/app/admin/service/CustomersService.php @@ -0,0 +1,48 @@ +select(); + return $list; + } + + public function addCustomer($companyName) + { + $id = CustomersModel::create([ + 'company_name' => $companyName + ]); + return $id; + } + + public function addEmployee($empName, $Id) + { + $id = CustomersModel::where('id', $Id)->find()->employees()->save([ + 'employee_name' => $empName + ]); + return $id; + } + + + public function deleteEmployee($empId) + { + $result = CustomerEmployees::destroy($empId); + return $result; // 返回受影响的行数,删除一行就1,没有删除就是0 + } + + public function deleteCustomer($customerId) + { + // 删除客户单位会自动删除关联的员工(假设你在模型中设置了级联删除) + $result = CustomersModel::destroy($customerId); + return $result; // 返回受影响的行数,删除一行就1,没有删除就是0 + } + + +} diff --git a/app/common.php b/app/common.php new file mode 100644 index 0000000..57cb6d8 --- /dev/null +++ b/app/common.php @@ -0,0 +1,2 @@ + $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); + } +} diff --git a/app/common/middleware/Cors.php b/app/common/middleware/Cors.php new file mode 100644 index 0000000..606b5d6 --- /dev/null +++ b/app/common/middleware/Cors.php @@ -0,0 +1,26 @@ +isOptions()) { + return response()->code(200); // 预检请求,允许通过 + } + + return $next($request); // 继续处理请求 + } +} diff --git a/app/common/model/CompanyOperators.php b/app/common/model/CompanyOperators.php new file mode 100644 index 0000000..cfcd722 --- /dev/null +++ b/app/common/model/CompanyOperators.php @@ -0,0 +1,22 @@ +belongsTo(Companys::class, 'company_id', 'id'); + } + + + public function getCompanyNameAttr($value, $data) + { + return $this->company->company_name ?? null; + } +} diff --git a/app/common/model/Companys.php b/app/common/model/Companys.php new file mode 100644 index 0000000..0518343 --- /dev/null +++ b/app/common/model/Companys.php @@ -0,0 +1,12 @@ +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'); + } + + + + +} diff --git a/app/common/model/Tickets.php b/app/common/model/Tickets.php new file mode 100644 index 0000000..4d7e8ca --- /dev/null +++ b/app/common/model/Tickets.php @@ -0,0 +1,94 @@ + '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 ?? ''; + } +} diff --git a/app/common/model/User.php b/app/common/model/User.php new file mode 100644 index 0000000..713125f --- /dev/null +++ b/app/common/model/User.php @@ -0,0 +1,16 @@ +belongsTo(Companys::class,'company_id','id'); + } + +} diff --git a/app/common/service/AuthService.php b/app/common/service/AuthService.php new file mode 100644 index 0000000..18bd665 --- /dev/null +++ b/app/common/service/AuthService.php @@ -0,0 +1,42 @@ +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; + + } +} diff --git a/app/common/service/TicketService.php b/app/common/service/TicketService.php new file mode 100644 index 0000000..258707a --- /dev/null +++ b/app/common/service/TicketService.php @@ -0,0 +1,186 @@ +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; + } +} diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..e9851bb --- /dev/null +++ b/app/event.php @@ -0,0 +1,17 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [ + ], +]; diff --git a/app/index/common.php b/app/index/common.php new file mode 100644 index 0000000..1243615 --- /dev/null +++ b/app/index/common.php @@ -0,0 +1,2 @@ +request->only(['username', 'password'], 'post'); + + // 验证数据 + if (empty($params['username']) || empty($params['password'])) { + return json(['code' => 400, 'message' => '用户名和密码不能为空']); + } + + try { + $result = $authService->login($params['username'], $params['password']); + } catch (\Exception $e) { + return json(['code' => 400, 'message' => $e->getMessage(),'data' => []]); + } + + return json(['code' => 200, 'message' => '登录成功', 'data' => $result]); + } + + /* + 退出登录(已完成) + */ + protected function logout() + { + Session::clear(); + return json(['code' => 200, 'message' => '退出成功']); + } +} diff --git a/app/index/controller/Ticket.php b/app/index/controller/Ticket.php new file mode 100644 index 0000000..8f69e2d --- /dev/null +++ b/app/index/controller/Ticket.php @@ -0,0 +1,29 @@ +request->post(); + + $allowFields = ['datanum', 'progress', 'status', 'remark', 'lock_time']; + + try { + $result = $ticketService->updateTicket($id, $data, $allowFields, $adminId, false); + return json(['code' => 200, 'msg' => '更新成功', 'data' => $result]); + } catch (\Exception $e) { + return json(['code' => 400, 'msg' => $e->getMessage()]); + } + } +} diff --git a/app/index/event.php b/app/index/event.php new file mode 100644 index 0000000..4eff890 --- /dev/null +++ b/app/index/event.php @@ -0,0 +1,5 @@ + 401, 'message' => '未登录']); + } + + return $next($request); + } +} diff --git a/app/provider.php b/app/provider.php new file mode 100644 index 0000000..73d99fa --- /dev/null +++ b/app/provider.php @@ -0,0 +1,9 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..db1ee6a --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ +=8.0.0", + "topthink/framework": "^8.0", + "topthink/think-orm": "^3.0|^4.0", + "topthink/think-filesystem": "^2.0|^3.0", + "topthink/think-multi-app": "^1.1" + }, + "require-dev": { + "topthink/think-dumper": "^1.0", + "topthink/think-trace": "^2.0" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..63285d2 --- /dev/null +++ b/config/app.php @@ -0,0 +1,30 @@ + '', + // 是否启用路由 + 'with_route' => true, + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => [], + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..6b72dc8 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,29 @@ + 'file', + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // 更多的缓存连接 + ], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..a818a98 --- /dev/null +++ b/config/console.php @@ -0,0 +1,9 @@ + [ + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..d3b3aab --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,20 @@ + 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..9d3f0d0 --- /dev/null +++ b/config/database.php @@ -0,0 +1,63 @@ + env('DB_DRIVER', 'mysql'), + + // 自定义时间查询规则 + 'time_query_rule' => [], + + // 自动写入时间戳字段 + // true为自动识别类型 false关闭 + // 字符串则明确指定时间字段类型 支持 int timestamp datetime date + 'auto_timestamp' => true, + + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + + // 时间字段配置 配置格式:create_time,update_time + 'datetime_field' => '', + + // 数据库连接配置信息 + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => env('DB_TYPE', 'mysql'), + // 服务器地址 + 'hostname' => env('DB_HOST', '127.0.0.1'), + // 数据库名 + 'database' => env('DB_NAME', ''), + // 用户名 + 'username' => env('DB_USER', 'root'), + // 密码 + 'password' => env('DB_PASS', ''), + // 端口 + 'hostport' => env('DB_PORT', '3306'), + // 数据库连接参数 + 'params' => [], + // 数据库编码 + 'charset' => env('DB_CHARSET', 'utf8mb4'), + // 数据库表前缀 + 'prefix' => env('DB_PREFIX', ''), + + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 是否需要断线重连 + 'break_reconnect' => false, + // 监听SQL + 'trigger_sql' => env('APP_DEBUG', true), + // 开启字段缓存 + 'fields_cache' => false, + ], + + // 更多的数据库配置信息 + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..582a8f8 --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,24 @@ + 'local', + // 磁盘列表 + 'disks' => [ + 'local' => [ + 'type' => 'local', + 'root' => app()->getRuntimePath() . 'storage', + ], + 'public' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..ccad14a --- /dev/null +++ b/config/lang.php @@ -0,0 +1,29 @@ + env('DEFAULT_LANG', 'zh-cn'), + // 自动侦测浏览器语言 + 'auto_detect_browser' => true, + // 允许的语言列表 + 'allow_lang_list' => [], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..0d406f8 --- /dev/null +++ b/config/log.php @@ -0,0 +1,45 @@ + 'file', + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..7e1972f --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,8 @@ + [], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..729d7be --- /dev/null +++ b/config/route.php @@ -0,0 +1,55 @@ + '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 是否区分大小写 + 'url_case_sensitive' => false, + // 自动扫描子目录分组 + 'route_auto_group' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认模块名(开启自动多模块有效) + 'default_module' => 'index', + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + // 操作方法的参数绑定方式 route get param + 'action_bind_param' => 'get', + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + // API版本header变量 + 'api_version' => 'Api-Version', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..120cb90 --- /dev/null +++ b/config/session.php @@ -0,0 +1,19 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 86400, + // 前缀 + 'prefix' => '', +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..01259a0 --- /dev/null +++ b/config/view.php @@ -0,0 +1,25 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', +]; diff --git a/extend/.gitignore b/extend/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/extend/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3f2817f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..c3b7842 --- /dev/null +++ b/public/index.php @@ -0,0 +1,25 @@ + +// +---------------------------------------------------------------------- + +use think\App; + +// [ 应用入口文件 ] + +require __DIR__ . '/../vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; + +$response = $http->run(); + +$response->send(); + +$http->end($response); diff --git a/public/nginx.htaccess b/public/nginx.htaccess new file mode 100644 index 0000000..5441c50 --- /dev/null +++ b/public/nginx.htaccess @@ -0,0 +1,9 @@ +location ~* (runtime|application)/{ + return 403; +} + +location / { + if (!-e $request_filename){ + rewrite ^(.*)$ /index.php?s=$1 last; break; + } +} \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..6f27bb6 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: \ No newline at end of file diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..9b39a62 --- /dev/null +++ b/public/router.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + + require __DIR__ . "/index.php"; +} diff --git a/public/static/.gitignore b/public/static/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/static/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/route/app.php b/route/app.php new file mode 100644 index 0000000..dfd6479 --- /dev/null +++ b/route/app.php @@ -0,0 +1,5 @@ +console->run(); diff --git a/view/README.md b/view/README.md new file mode 100644 index 0000000..360eb24 --- /dev/null +++ b/view/README.md @@ -0,0 +1 @@ +如果不使用模板,可以删除该目录 \ No newline at end of file