143 lines
4.8 KiB
PHP
143 lines
4.8 KiB
PHP
<?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;
|
||
}
|
||
}
|