diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 2fd8f78..6f43f04 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -196,7 +196,7 @@ class CustomerController extends BaseController } /** - * 客户恢复 + * 无用线索举报 * @param Request $request * @return \support\Response */ diff --git a/app/crmAdmin/controller/CustomerController.php b/app/crmAdmin/controller/CustomerController.php index cab0d24..bc4b69d 100644 --- a/app/crmAdmin/controller/CustomerController.php +++ b/app/crmAdmin/controller/CustomerController.php @@ -45,14 +45,32 @@ class CustomerController extends BaseController { try { $param = $request->all(); - $corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first(); - $param['corp_abbr'] = $corp->id; -// $param['corp_abbr'] = $request->basic_data['corp_abbr']; +// $corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first(); +// $param['corp_abbr'] = $corp->id; + $result = (new CustomerService())->export($param); + + // 检查文件是否存在 + if (!file_exists($result['filepath'])) { + return $this->error('导出文件生成失败'); + } + + // 获取文件信息 + $fileSize = filesize($result['filepath']); + $filename = $result['filename']; - return $this->success($this->page($result)); + // 设置响应头 + $response = response()->file($result['filepath']); + $response->header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $response->header('Content-Disposition', 'attachment; filename="' . $filename . '"'); + $response->header('Content-Length', $fileSize); + $response->header('Cache-Control', 'no-cache, must-revalidate'); + $response->header('Pragma', 'no-cache'); + + return $response; + } catch (\Throwable $e) { - return $this->error($e->getMessage().$e->getFile().$e->getLine().$e->getTraceAsString()); + return $this->error($e->getMessage()); } } @@ -68,7 +86,8 @@ class CustomerController extends BaseController if (empty($param['id'])) { return $this->error('ID'.trans('not_null')); } - if (in_array($param['scene'],['delete','report'])) { + + if (!in_array($param['scene'],['delete','report'])) { return $this->error('scene 不合法'); } $result = (new CustomerService())->audit($param); diff --git a/app/crmAdmin/logic/CustomerLogic.php b/app/crmAdmin/logic/CustomerLogic.php index 1ff4651..99af464 100644 --- a/app/crmAdmin/logic/CustomerLogic.php +++ b/app/crmAdmin/logic/CustomerLogic.php @@ -15,9 +15,10 @@ class CustomerLogic return $res; } - public function delete(int $id) + public function forceDelete(int $id) { $customer = Customer::find($id); + //强制删除 $res = $customer->forceDelete(); return $res; } diff --git a/app/crmAdmin/service/CustomerService.php b/app/crmAdmin/service/CustomerService.php index f7f57f1..e5f1cf2 100644 --- a/app/crmAdmin/service/CustomerService.php +++ b/app/crmAdmin/service/CustomerService.php @@ -3,6 +3,7 @@ namespace app\crmAdmin\service; use app\common\model\User; +use app\crm\model\CustomerHis; use app\crmAdmin\logic\CustomerLogic; use app\crm\model\CarType; use app\crm\model\Channel; @@ -11,11 +12,28 @@ use app\crm\model\Customer; use app\crm\model\Follow; use app\crm\utils\Util; use config\constants\Constants; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Style\Alignment; +use PhpOffice\PhpSpreadsheet\Style\Border; +use PhpOffice\PhpSpreadsheet\Style\Fill; class CustomerService { public function list($params) { + $customer = $this->search($params); + return $customer; + } + + /** + * 查询列表数据 + * @param $params + * @param $export + * @return Customer[]|\Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Collection[]|\Illuminate\Pagination\LengthAwarePaginator + */ + public function search($params, $export = false){ + // $page = $request->input('page', 1); // $perPage = $request->input('per_page', 10); $perPage = $params['size'] ?? 10; @@ -31,9 +49,9 @@ class CustomerService $where = []; if ($scene == 'audit') { - $where = ['status', 2]; + $where[] = ['status', '=', 2]; }else { - $where = ['status', 1]; + $where[] = ['status', '=', 1]; } if($seas == 'seas' || $scene == 'seas'){ $where[] = ['user_id', 0]; @@ -92,11 +110,22 @@ class CustomerService $query->onlyTrashed(); } - $customer = $query->paginate($perPage, ['*'], 'page', $page); - foreach ($customer as &$item) { - $item = $this->getCustomer($item); - if($seas == 'seas' || $scene == 'seas'){ - $item['seas_time'] = time()-strtotime($item['deleted_at']); + if(!$export){ + $customer = $query->orderBy('id','desc')->paginate($perPage, ['*'], 'page', $page); + }else{ + $customer = $query->orderBy('id','desc')->get(); + return $customer; + } + if(!$export){ + foreach ($customer as &$item) { + $item = $this->getCustomer($item); + if($seas == 'seas' || $scene == 'seas'){ + $item['seas_time'] = time()-strtotime($item['deleted_at']); + } + if ($scene == 'audit') { //审核时查询举报人姓名 + $customerHis = CustomerHis::where('customer_id',$item->id)->orderBy('id','desc')->first(['user_his']); + $customer['user_name'] = empty($customerHis['user_his']) ? '' : User::find($customer['user_his'])->value('name'); + } } } unset($item); @@ -104,6 +133,130 @@ class CustomerService return $customer; } + public function export($params) + { + $customers = $this->search($params, true); + + // 创建新的Spreadsheet对象 + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // 设置表头 + $headers = [ + 'A1' => '客户ID', + 'B1' => '联系人', + 'C1' => '电话', + 'D1' => '邮箱', + 'E1' => 'WhatsApp', + 'F1' => '性别', + 'G1' => '国家/地区', + 'H1' => '渠道来源', + 'I1' => '描述', + 'J1' => '所属人', + 'K1' => '录入人', + ]; + + // 设置表头样式 + foreach ($headers as $cell => $value) { + $sheet->setCellValue($cell, $value); + } + + // 设置表头样式 + $headerStyle = [ + 'font' => [ + 'bold' => true, + 'color' => ['rgb' => 'FFFFFF'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => '4472C4'], + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + $sheet->getStyle('A1:K1')->applyFromArray($headerStyle); + + // 填充数据 + $row = 2; + foreach ($customers as $customer) { + // 获取完整的客户信息 + $customerData = $this->getCustomer($customer); + + // 填充数据行 + $sheet->setCellValue('A' . $row, $customerData['id']); + $sheet->setCellValue('B' . $row, $customerData['contacts']); + $sheet->setCellValue('C' . $row, $customerData['phone']); + $sheet->setCellValue('D' . $row, $customerData['email']); + $sheet->setCellValue('E' . $row, $customerData['WhatsApp']); + $sheet->setCellValue('F' . $row, $customerData['sex_txt']); + $sheet->setCellValue('G' . $row, $customerData['country_name']); + $sheet->setCellValue('H' . $row, $customerData['channel_name']); + $sheet->setCellValue('I' . $row, $customerData['describe']); + $sheet->setCellValue('J' . $row, $customerData['user_name']); // 所属人姓名 + $sheet->setCellValue('K' . $row, $customerData['enter_user_name']); + + $row++; + } + + // 设置数据行样式 + $dataStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_CENTER, + ], + ]; + + $lastRow = $row - 1; + if ($lastRow >= 2) { + $sheet->getStyle('A2:K' . $lastRow)->applyFromArray($dataStyle); + } + + // 自动调整列宽 + foreach (range('A', 'K') as $column) { + $sheet->getColumnDimension($column)->setAutoSize(true); + } + + // 设置行高 + $sheet->getRowDimension(1)->setRowHeight(25); + + // 生成文件名 + $filename = '客户数据_' . date('Y-m-d_H-i-s') . '.xlsx'; + + // 创建输出目录 + $exportDir = runtime_path() . '/export/'; + if (!is_dir($exportDir)) { + mkdir($exportDir, 0755, true); + } + + $filepath = $exportDir . $filename; + + // 创建Excel写入器 + $writer = new Xlsx($spreadsheet); + $writer->save($filepath); + + // 返回文件信息 + return [ + 'filename' => $filename, + 'filepath' => $filepath, + 'size' => filesize($filepath), + 'count' => count($customers) + ]; + } + /** * @param $params * @return bool @@ -118,13 +271,10 @@ class CustomerService $logic = new CustomerLogic(); if($params['scene'] == 'delete'){ - return $logic->delete($params['id']); + return $logic->forceDelete($params['id']); }else{ - return $logic->status($params['id'], 2); + return $logic->status($params['id'], 1); } - - - } /** diff --git a/composer.json b/composer.json index 64b3e58..ec04c9f 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,8 @@ "webman/redis-queue": "^2.1", "symfony/translation": "^7.3", "symfony/filesystem": "^7.3", - "workerman/crontab": "^1.0" + "workerman/crontab": "^1.0", + "phpoffice/phpspreadsheet": "^4.5" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 7f84b75..d88df56 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8ef9d9a07b2be477345002a7623ea951", + "content-hash": "471a31011b58e1e514bdb9c8963055ad", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -186,6 +186,85 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "defuse/php-encryption", "version": "v2.4.0", @@ -2524,6 +2603,191 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.2" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-01-27T12:07:53+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "monolog/monolog", "version": "2.10.0", @@ -2831,6 +3095,112 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "4.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2ea9786632e6fac1aee601b6e426bcc723d8ce13", + "reference": "2ea9786632e6fac1aee601b6e426bcc723d8ce13", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.5.0" + }, + "time": "2025-07-24T05:15:59+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", diff --git a/config/route.php b/config/route.php index 88720e1..a23bdfe 100644 --- a/config/route.php +++ b/config/route.php @@ -148,6 +148,7 @@ Route::group('/crm', function () { Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); Route::any('/customer/followNext', [app\crm\controller\CustomerController::class, 'followNext']); + Route::any('/customer/report', [app\crm\controller\CustomerController::class, 'report']); Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']); Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']);