Files
lgp-admin-plus-api/app/Service/common/ExcelCsvService.php
LQ 4ddf5e3c5f
Some checks failed
Tests / PHP 8.2 (push) Has been cancelled
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
更新若干功能
2026-08-20 19:16:49 +08:00

143 lines
4.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service\common;
/**
* 表格导出辅助:报价给结构化列/行(前端 ExcelJS 画 xlsx小程序继续用 text。
* 仍保留 toCsv / parseUpload避免旧调用立刻断掉。
*/
class ExcelCsvService
{
private static mixed $_instance;
public static function getInstance(): null|static
{
$name = get_called_class();
if (!isset(self::$_instance[$name])) {
self::$_instance[$name] = new static();
}
return self::$_instance[$name];
}
/**
* 行数组转带 BOM 的 CSV 文本
*/
public function toCsv(array $header, array $rows): string
{
$fp = fopen('php://temp', 'r+');
fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($fp, $header);
foreach ($rows as $row) {
$line = [];
foreach ($header as $key) {
$line[] = $row[$key] ?? '';
}
fputcsv($fp, $line);
}
rewind($fp);
$csv = stream_get_contents($fp);
fclose($fp);
return $csv === false ? '' : $csv;
}
/**
* 解析上传文件csv / 文本)
*/
public function parseUpload($file): array
{
if (empty($file)) {
UtilsService::getInstance()->errorThrow('请上传表格文件');
}
$path = is_string($file) ? $file : $file->getRealPath();
$raw = file_get_contents($path);
if ($raw === false) {
UtilsService::getInstance()->errorThrow('文件读取失败');
}
if (str_starts_with($raw, "\xEF\xBB\xBF")) {
$raw = substr($raw, 3);
}
$fp = fopen('php://temp', 'r+');
fwrite($fp, $raw);
rewind($fp);
$header = fgetcsv($fp);
if (empty($header)) {
fclose($fp);
UtilsService::getInstance()->errorThrow('表格没有表头');
}
$header = array_map(static fn ($v) => trim((string) $v), $header);
$rows = [];
while (($cols = fgetcsv($fp)) !== false) {
if ($this->rowEmpty($cols)) {
continue;
}
$item = [];
foreach ($header as $i => $key) {
$item[$key] = isset($cols[$i]) ? trim((string) $cols[$i]) : '';
}
$rows[] = $item;
}
fclose($fp);
return $rows;
}
/**
* 清单报价:后台用 ExcelJS 画 xlsx小程序继续用 text 分享
*/
public function quoteFromList(array $info): array
{
$rows = [];
$lines = [];
$name = (string) ($info['name'] ?? $info['list_no'] ?? '报价');
$lines[] = $name;
foreach ($info['items'] ?? [] as $item) {
$title = (string) ($item['title'] ?? $item['catalogue']['title'] ?? $item['product']['title'] ?? '');
$spec = (string) ($item['specification'] ?? $item['price_sheet']['specification'] ?? '');
$qty = (int) ($item['quantity'] ?? 1);
$unit = (string) ($item['unit_price_text'] ?? '');
$total = (string) ($item['total_price_text'] ?? '');
$rows[] = [
'title' => $title,
'specification' => $spec,
'quantity' => $qty,
'unit_price' => $unit,
'total_price' => $total,
];
$lines[] = trim($title . ' ' . $spec . ' x' . $qty . ($unit !== '' ? ' ¥' . $unit : ''));
}
$payable = (string) ($info['payable_amount_text'] ?? $info['total_amount_text'] ?? '');
if ($payable !== '') {
$lines[] = '合计 ¥' . $payable;
}
$no = (string) ($info['list_no'] ?? 'quote');
return [
'filename' => 'quote-' . $no . '.xlsx',
'title' => $name,
'sheet' => '报价',
'subtitle' => '清单号 ' . $no,
'columns' => [
['key' => 'title', 'header' => '商品', 'width' => 28],
['key' => 'specification', 'header' => '规格', 'width' => 22],
['key' => 'quantity', 'header' => '数量', 'width' => 10, 'align' => 'center'],
['key' => 'unit_price', 'header' => '单价', 'width' => 14, 'align' => 'right'],
['key' => 'total_price', 'header' => '小计', 'width' => 14, 'align' => 'right'],
],
'rows' => $rows,
'summary' => [
'title' => '合计',
'total_price' => $payable === '' ? '' : ('¥' . $payable),
],
'text' => implode("\n", $lines),
];
}
private function rowEmpty(array $cols): bool
{
foreach ($cols as $col) {
if (trim((string) $col) !== '') {
return false;
}
}
return true;
}
}