first commit

This commit is contained in:
root
2025-06-18 10:31:43 +08:00
commit d9f820b55d
981 changed files with 449311 additions and 0 deletions

45
app/Helpers/AppHelper.php Executable file
View File

@ -0,0 +1,45 @@
<?php
namespace App\Helpers;
use function Hyperf\Config\config;
class AppHelper
{
public static function getImageBaseUrl(): string
{
return config('upload.qiniu.base_url');
}
public static function setImageUrl(string $imageUrl)
{
$imageUrl = self::extractImagePath($imageUrl);
return self::getImageBaseUrl() . $imageUrl;
}
public static function extractImagePath(string $url): string
{
// 解析URL
$parsedUrl = parse_url($url);
// 获取路径部分
$path = $parsedUrl['path'];
// 去除路径前的 "/"
return ltrim($path, '/');
}
public static function generateAid(): string
{
return strtr(uniqid(more_entropy: true), [
'.' => ''
]);
}
public static function getYear(string $title)
{
preg_match('/([0-9]+)/', $title, $y);
return $y[1] ?? date('Y');
}
}

352
app/Helpers/ExcelHelper.php Normal file
View File

@ -0,0 +1,352 @@
<?php
namespace App\Helpers;
use Exception;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpMessage\Stream\SwooleStream;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Html;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use Hyperf\HttpServer\Contract\ResponseInterface;
class ExcelHelper
{
#[Inject]
protected ResponseInterface $response;
/**
* 导出Excel
*
* @param array $list
* @param array $header
* @param string $filename
* @param string $suffix
* @param string $path 输出绝对路径
* @return bool
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public static function exportData(ResponseInterface $response, $list = [], $header = [], $filename = '', $suffix = 'xlsx', $path = '')
{
if (!is_array($list) || !is_array($header)) {
return false;
}
!$filename && $filename = time();
// 初始化
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 写入头部
$hk = 1;
foreach ($header as $k => $v) {
$sheet->setCellValue(Coordinate::stringFromColumnIndex($hk) . '1', $v[0]);
$sheet->getStyle(Coordinate::stringFromColumnIndex($hk) . '1')->getFont()->setBold(true);
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($hk))->setAutoSize(true);
$hk += 1;
}
// 开始写入内容
$column = 2;
$size = ceil(count($list) / 500);
for ($i = 0; $i < $size; $i++) {
$buffer = array_slice($list, $i * 500, 500);
foreach ($buffer as $k => $row) {
$span = 1;
foreach ($header as $key => $value) {
// 解析字段
$realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
// 写入excel
$sheet->setCellValueExplicit(Coordinate::stringFromColumnIndex($span) . $column, $realData, DataType::TYPE_STRING);
// $sheet->setCellValue(Coordinate::stringFromColumnIndex($span) . $column, $realData);
$span++;
}
$column++;
unset($buffer[$k]);
}
}
// 清除之前的错误输出
// ob_end_clean();
ob_start();
// 直接输出下载
switch ($suffix) {
case 'xlsx' :
$writer = new Xlsx($spreadsheet);
if (!empty($path)) {
$writer->save($path);
} else {
// 2. 保存到内存(使用 php://memory
$tempFile = 'php://memory';
$stream = fopen($tempFile, 'w+');
$writer->save($stream);
rewind($stream);
$excelOutput = stream_get_contents($stream);
fclose($stream);
// 3. 返回给客户端下载
return $response->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
->withHeader('Content-Disposition', 'attachment; filename=' . $filename . '.xlsx')
->withHeader('Cache-Control', 'max-age=0')
->raw($excelOutput);
}
break;
case 'xls' :
$writer = new Xls($spreadsheet);
if (!empty($path)) {
$writer->save($path);
} else {
header("Content-Type:application/vnd.ms-excel;charset=utf-8;");
header("Content-Disposition:inline;filename=\"{$filename}.xls\"");
header('Cache-Control: max-age=0');
$writer->save('php://output');
}
break;
case 'csv' :
$writer = new Csv($spreadsheet);
if (!empty($path)) {
$writer->save($path);
} else {
header("Content-type:text/csv;charset=utf-8;");
header("Content-Disposition:attachment; filename={$filename}.csv");
header('Cache-Control: max-age=0');
$writer->save('php://output');
}
break;
case 'html' :
$writer = new Html($spreadsheet);
if (!empty($path)) {
$writer->save($path);
} else {
header("Content-Type:text/html;charset=utf-8;");
header("Content-Disposition:attachment;filename=\"{$filename}.{$suffix}\"");
header('Cache-Control: max-age=0');
$writer->save('php://output');
}
break;
}
/* 释放内存 */
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
$content = ob_end_flush();
// 创建一个临时流,并将内容写入其中
$tempStream = fopen('php://temp', 'r+');
if ($tempStream === false) {
throw new \RuntimeException('Unable to open temporary stream');
}
fwrite($tempStream, $content);
rewind($tempStream);
// $response = new Response();
$contentType = 'text/csv';
return $response->withHeader('content-description', 'File Transfer')
->withHeader('content-type', $contentType)
->withHeader('content-disposition', "attachment; filename=text.xlsx")
->withHeader('content-transfer-encoding', 'binary')
->withHeader('pragma', 'public')
->withBody(new SwooleStream($content));
return $response->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
->withHeader('Content-Disposition', 'attachment; filename="exported_file.xlsx"')
->withBody(new \GuzzleHttp\Psr7\Stream($tempStream));
// exit();
}
/**
* 导出的另外一种形式(不建议使用)
*
* @param array $list
* @param array $header
* @param string $filename
* @return bool
*/
public static function exportCsvData($list = [], $header = [], $filename = '')
{
if (!is_array($list) || !is_array($header)) {
return false;
}
// 清除之前的错误输出
ob_end_clean();
ob_start();
!$filename && $filename = time();
$html = "\xEF\xBB\xBF";
foreach ($header as $k => $v) {
$html .= $v[0] . "\t ,";
}
$html .= "\n";
if (!empty($list)) {
$info = [];
$size = ceil(count($list) / 500);
for ($i = 0; $i < $size; $i++) {
$buffer = array_slice($list, $i * 500, 500);
foreach ($buffer as $k => $row) {
$data = [];
foreach ($header as $key => $value) {
// 解析字段
$realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
$data[] = '"' . $realData . '"';
}
$info[] = implode("\t ,", $data) . "\t ,";
unset($data, $buffer[$k]);
}
}
$html .= implode("\n", $info);
}
header("Content-type:text/csv");
header("Content-Disposition:attachment; filename={$filename}.csv");
echo $html;
exit();
}
/**
* 导入
*
* @param $filePath
* @param int $startRow
* @return array|mixed
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
*/
public static function import($filePath, $startRow = 1)
{
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$reader->setReadDataOnly(true);
if (!$reader->canRead($filePath)) {
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
// setReadDataOnly Set read data only 只读单元格的数据,不格式化 e.g. 读时间会变成一个数据等
$reader->setReadDataOnly(true);
if (!$reader->canRead($filePath)) {
throw new Exception('不能读取Excel');
}
}
$spreadsheet = $reader->load($filePath);
$sheetCount = $spreadsheet->getSheetCount();// 获取sheet的数量
// 获取所有的sheet表格数据
$excleDatas = [];
$emptyRowNum = 0;
for ($i = 0; $i < $sheetCount; $i++) {
$currentSheet = $spreadsheet->getSheet($i); // 读取excel文件中的第一个工作表
$allColumn = $currentSheet->getHighestColumn(); // 取得最大的列号
$allColumn = Coordinate::columnIndexFromString($allColumn); // 由列名转为列数('AB'->28)
$allRow = $currentSheet->getHighestRow(); // 取得一共有多少行
$arr = [];
for ($currentRow = $startRow; $currentRow <= $allRow; $currentRow++) {
// 从第1列开始输出
for ($currentColumn = 1; $currentColumn <= $allColumn; $currentColumn++) {
$val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
$arr[$currentRow][] = trim($val);
}
// $arr[$currentRow] = array_filter($arr[$currentRow]);
// 统计连续空行
if (empty($arr[$currentRow]) && $emptyRowNum <= 50) {
$emptyRowNum++;
} else {
$emptyRowNum = 0;
}
// 防止坑队友的同事在excel里面弄出很多的空行陷入很漫长的循环中设置如果连续超过50个空行就退出循环返回结果
// 连续50行数据为空不再读取后面行的数据防止读满内存
if ($emptyRowNum > 50) {
break;
}
}
$excleDatas[$i] = $arr; // 多个sheet的数组的集合
}
// 这里我只需要用到第一个sheet的数据所以只返回了第一个sheet的数据
$returnData = $excleDatas ? array_shift($excleDatas) : [];
// 第一行数据就是空的为了保留其原始数据第一行数据就不做array_fiter操作
$returnData = $returnData && isset($returnData[$startRow]) && !empty($returnData[$startRow]) ? array_filter($returnData) : $returnData;
return $returnData;
}
/**
* 格式化内容
*
* @param array $array 头部规则
* @return false|mixed|null|string 内容值
*/
protected static function formatting(array $array, $value, $row)
{
!isset($array[2]) && $array[2] = 'text';
switch ($array[2]) {
// 文本
case 'text' :
return $value;
break;
// 日期
case 'date' :
return !empty($value) ? date($array[3], $value) : null;
break;
// 选择框
case 'selectd' :
return $array[3][$value] ?? null;
break;
// 匿名函数
case 'function' :
return isset($array[3]) ? call_user_func($array[3], $row) : null;
break;
// 默认
default :
break;
}
return null;
}
/**
* 解析字段
*
* @param $row
* @param $field
* @return mixed
*/
protected static function formattingField($row, $field)
{
$newField = explode('.', $field);
if (count($newField) == 1) {
return $row[$field] ?? '';
}
foreach ($newField as $item) {
if (isset($row[$item])) {
$row = $row[$item];
} else {
break;
}
}
return is_array($row) ? false : $row;
}
}

355
app/Helpers/TitleHelper.php Executable file
View File

@ -0,0 +1,355 @@
<?php
namespace App\Helpers;
use App\Enums\ArticleStyleEnum;
use App\Enums\LocationEnum;
class TitleHelper
{
public static function translate(string $englishTitle)
{
$map = [
'Madrid' => [
[
'preg' => '/Madrid Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::MADRID->value,
],[
'preg' => '/Madrid Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::MADRID->value,
],
],
'Spain' => [
[
'preg' => '/Spain Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::SPAIN->value,
],[
'preg' => '/Spain Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::SPAIN->value,
],
],
'Istanbul' => [
[
'preg' => '/Istanbul Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::ISTANBUL->value,
],[
'preg' => '/Istanbul Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::ISTANBUL->value,
],
],
'Lagos' => [
[
'preg' => '/Lagos Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::LAGOS->value,
],[
'preg' => '/Lagos Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::LAGOS->value,
],
],
'Russia' => [
[
'preg' => '/Russia Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::RUSSIA->value,
],[
'preg' => '/Russia Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::RUSSIA->value,
],
],
'Shanghai' => [
[
'preg' => '/Shanghai Spring ([0-9]*?)/',
'trans' => '春季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::SHANGHAI->value,
],[
'preg' => '/Shanghai Fall ([0-9]*?)/',
'trans' => '秋季',
'style' => ArticleStyleEnum::NULL->value,
'location' => LocationEnum::SHANGHAI->value,
],
],
'Bridal' => [
[
'preg' => '/Bridal Spring ([0-9]*?)/',
'trans' => '春季婚纱礼服',
'style' => ArticleStyleEnum::BRIDAL->value,
'location' => LocationEnum::NULL->value,
],[
'preg' => '/Bridal Fall ([0-9]*?)/',
'trans' => '秋季婚纱礼服',
'style' => ArticleStyleEnum::BRIDAL->value,
'location' => LocationEnum::NULL->value,
],
],
'Australia' => [
[
'preg' => '/Australia Resort ([0-9]*)/',
'trans' => '度假系列',
'style' => 1,
'location' => LocationEnum::AUSTRALIA->value,
],[
'preg' => '/Australia Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::AUSTRALIA->value,
],
],
'Menswear' => [
[
'preg' => '/Spring ([0-9]*?) Menswear/',
'trans' => '春季男装',
'style' => 0,
'location' => 0,
],[
'preg' => '/Fall ([0-9]*?) Menswear/',
'trans' => '秋季男装',
'style' => 0,
'location' => 0,
]
],
'Ukraine' => [
[
'preg' => '/Ukraine Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::UKRAINE->value, // 乌克兰
],
[
'preg' => '/Ukraine Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::UKRAINE->value, // 乌克兰
],
],
'Kiev' => [
[
'preg' => '/Kiev Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::KIEV->value, // 基辅
],[
'preg' => '/Kiev Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::KIEV->value, // 基辅
]
],
'Stockholm' => [
[
'preg' => '/Stockholm Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::STOCKHOLM->value, // 斯德哥尔摩
],
[
'preg' => '/Stockholm Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::STOCKHOLM->value, // 斯德哥尔摩
],
],
'Tbilisi' => [
[
'preg' => '/Tbilisi Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::TBILISI->value, // 斯德哥尔摩
],
[
'preg' => '/Tbilisi Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::TBILISI->value, // 斯德哥尔摩
],
],
'Mexico' => [
[
'preg' => '/Mexico Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
],[
'preg' => '/Mexico Spring ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
],[
'preg' => '/Mexico City Fall ([0-9]*)/',
'trans' => '城市秋季',
'style' => 0,
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
],[
'preg' => '/Mexico City Spring ([0-9]*)/',
'trans' => '城市春季',
'style' => 0,
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
],
],
'Tokyo' => [
[
'preg' => '/Tokyo Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::TOKYO->value, // 东京
],[
'preg' => '/Tokyo Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::TOKYO->value, // 东京
],
],
'Berlin' => [
[
'preg' => '/Berlin Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::BERLIN->value, // 柏林
],
[
'preg' => '/Berlin Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::BERLIN->value, // 柏林
],
],
'Copenhagen' => [
[
'preg' => '/Copenhagen Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::COPENHAGEN->value, // 哥本哈根
],[
'preg' => '/Copenhagen Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::COPENHAGEN->value, // 哥本哈根
],
],
'São Paulo' => [
[
'preg' => '/São Paulo Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::SAO_PAULO->value, // 哥本哈根
],[
'preg' => '/São Paulo Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::SAO_PAULO->value, // 哥本哈根
],
],
'SEOUL' => [
[
'preg' => '/Seoul Spring ([0-9]*)/',
'trans' => '春季',
'style' => 0,
'location' => LocationEnum::SEOUL->value,
],[
'preg' => '/Seoul Fall ([0-9]*)/',
'trans' => '秋季',
'style' => 0,
'location' => LocationEnum::SEOUL->value,
],
],
'Spring' => [
[
'preg' => '/([0-9]*?) Spring Summer/',
'trans' => '春夏',
'style' => 0,
'location' => 0,
],
],
'Autumn Winter' => [
[
'preg' => '/([0-9]*?) Autumn Winter/',
'trans' => '秋冬',
'style' => 0,
'location' => 0,
],
],
'Ready-to-Wear' => [
[
'preg' => '/Fall ([0-9]*?) Ready-to-Wear/',
'trans' => '秋季成衣',
'style' => 0,
'location' => 0,
],[
'preg' => '/Spring ([0-9]*?) Ready-to-Wear/',
'trans' => '春季成衣',
'style' => 0,
'location' => 0,
],
],
'Resort' => [
[
'preg' => '/Resort ([0-9]*?)/',
'trans' => '度假系列',
'style' => ArticleStyleEnum::RESORT->value,
'location' => LocationEnum::NULL->value,
],
],
'Couture' => [
[
'preg' => '/Spring ([0-9]*?) Couture/',
'trans' => '春季高订系列',
'style' => ArticleStyleEnum::RESORT->value,
'location' => LocationEnum::NULL->value,
],
[
'preg' => '/Fall ([0-9]*?) Couture/',
'trans' => '秋季高订系列',
'style' => ArticleStyleEnum::RESORT->value,
'location' => LocationEnum::NULL->value,
],
],
'Pre-Fall' => [
[
'preg' => '/Pre-Fall ([0-9]*)/',
'trans' => '早秋',
'style' => 0,
'location' => 0,
],
],
];
$res = $englishTitle;
foreach ($map as $keyword => $pregMap) {
if (stripos($englishTitle, $keyword) !== false) {
foreach ($pregMap as $pregItem) {
preg_match_all($pregItem['preg'], $englishTitle, $matches);
if (count($matches) > 1 && $matches[1]) {
$res = trim(current($matches[1]) . " {$pregItem['trans']}");
break;
}
}
if ($res) {
break;
}
}
}
return $res;
}
}

196
app/Helpers/TreeHelper.php Executable file
View File

@ -0,0 +1,196 @@
<?php
namespace App\Helpers;
class TreeHelper
{
/**
* 获取完整的树结构,包含祖先节点
*/
const INCLUDE_ANCESTORS = 1;
/**
* 获取部分树,不包含祖先节点
*/
const EXCLUDE_ANCESTORS = 0;
/**
* 数据
* @var array
*/
protected array $data = [];
/**
* 哈希树
* @var array
*/
protected array $hashTree = [];
/**
* 父级字段名
* @var string
*/
protected string $pidName = 'pid';
/**
* @param $data
* @param string $pid_name
*/
public function __construct($data, string $pid_name = 'pid')
{
$this->pidName = $pid_name;
if (is_object($data) && method_exists($data, 'toArray')) {
$this->data = $data->toArray();
} else {
$this->data = (array)$data;
$this->data = array_map(function ($item) {
if (is_object($item) && method_exists($item, 'toArray')) {
return $item->toArray();
}
return $item;
}, $this->data);
}
$this->hashTree = $this->getHashTree();
}
/**
* 获取子孙节点
* @param array $include
* @param bool $with_self
* @return array
*/
protected function getDescendant(array $include, bool $with_self = false): array
{
$items = [];
foreach ($include as $id) {
if (!isset($this->hashTree[$id])) {
return [];
}
if ($with_self) {
$item = $this->hashTree[$id];
unset($item['children']);
$items[$item['id']] = $item;
}
foreach ($this->hashTree[$id]['children'] ?? [] as $item) {
unset($item['children']);
$items[$item['id']] = $item;
foreach ($this->getDescendant([$item['id']]) as $it) {
$items[$it['id']] = $it;
}
}
}
return array_values($items);
}
/**
* 获取哈希树
* @param array $data
* @return array
*/
protected function getHashTree(array $data = []): array
{
$data = $data ?: $this->data;
$hash_tree = [];
foreach ($data as $item) {
$hash_tree[$item['id']] = $item;
}
foreach ($hash_tree as $index => $item) {
if ($item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
$hash_tree[$item[$this->pidName]]['children'][$hash_tree[$index]['id']] = &$hash_tree[$index];
}
}
return $hash_tree;
}
/**
* 获取树
* @param array $include
* @param int $type
* @return array|null
*/
protected function _getTree(array $include = [], int $type = 1): ?array
{
// $type === static::EXCLUDE_ANCESTORS
if ($type === static::EXCLUDE_ANCESTORS) {
$items = [];
$include = array_unique($include);
foreach ($include as $id) {
if (!isset($this->hashTree[$id])) {
return [];
}
$items[] = $this->hashTree[$id];
}
return static::arrayValues($items);
}
// $type === static::INCLUDE_ANCESTORS
$hash_tree = $this->hashTree;
$items = [];
if ($include) {
$map = [];
foreach ($include as $id) {
if (!isset($hash_tree[$id])) {
continue;
}
$item = $hash_tree[$id];
$max_depth = 100;
while ($max_depth-- > 0 && $item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
$last_item = $item;
$pid = $item[$this->pidName];
$item = $hash_tree[$pid];
$item_id = $item['id'];
if (empty($map[$item_id])) {
$map[$item_id] = 1;
$hash_tree[$pid]['children'] = [];
}
$hash_tree[$pid]['children'][$last_item['id']] = $last_item;
$item = $hash_tree[$pid];
}
$items[$item['id']] = $item;
}
} else {
$items = $hash_tree;
}
$formatted_items = [];
foreach ($items as $item) {
if (!$item[$this->pidName] || !isset($hash_tree[$item[$this->pidName]])) {
$formatted_items[] = $item;
}
}
return static::arrayValues($formatted_items);
}
/**
* 递归重建数组下标
* @param $array
* @return array
*/
public static function arrayValues($array): array
{
if (!$array) {
return [];
}
if (!isset($array['children'])) {
$current = current($array);
if (!is_array($current)) {
return $array;
}
$tree = array_values($array);
foreach ($tree as $index => $item) {
$tree[$index] = static::arrayValues($item);
}
return $tree;
}
$array['children'] = array_values($array['children']);
foreach ($array['children'] as $index => $child) {
$array['children'][$index] = static::arrayValues($child);
}
return $array;
}
public static function getTree($data)
{
return (new self($data))->_getTree();
}
}