更新若干功能
This commit is contained in:
142
app/Service/common/ExcelCsvService.php
Normal file
142
app/Service/common/ExcelCsvService.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user