本文基於一個典型的 ECShop 二次開發項目,深度剖析「eBay 訂單統計報表」模塊的核心代碼結構:主控制器邏輯、多語言包、模板渲染,涵蓋單周期/多周期對比、Flash 圖表數據生成、Excel 導出等企業級報表功能。
erp_ebay_order_stats.php (控制器)
│
├─ 語言包: languages/zh_cn/admin/erp_ebay_statistic.php
├─ 模板: admin/templates/erp_ebay_order_stats.htm
└─ 依賴: init.php, Smarty, ADODB, Flash 圖表組件
<?php
define('IN_ECS', true);
require(dirname(__FILE__) . '/includes/init.php'); // 載入核心配置、DB、Smarty、Session
require_once(ROOT_PATH . 'languages/' . $_CFG['lang'] . '/admin/erp_ebay_statistic.php');
$smarty->assign('lang', $_LANG);
// act 參數路由
$act = $_REQUEST['act'] ?? 'view';
// 權限檢查
admin_priv('order_stats');
?>
<?php
// 單區間模式
$start_date = local_strtotime($_POST['start_date'] ?? (local_date('Y-m-d', time() - 86400*14)));
$end_date = local_strtotime($_POST['end_date'] ?? local_date('Y-m-d'));
// 多月份對比模式
$start_date_arr = [];
$end_date_arr = [];
if (!empty($_POST['year_month'])) {
foreach ($_POST['year_month'] as $ym) {
if ($ym) {
$t = local_strtotime($ym . '-1');
$start_date_arr[] = $t;
$end_date_arr[] = local_strtotime($ym . '-' . date('t', $t));
}
}
} else {
$t = local_strtotime(local_date('Y-m'));
$start_date_arr[] = local_strtotime(local_date('Y-m-1'));
$end_date_arr[] = local_strtotime(local_date('Y-m-d'));
}
?>
| 報表 | SQL 核心指標 | 圖表類型 |
|---|---|---|
| 綜合訂單量 | COUNT(*) GROUP BY DAY(created_time) | 單區間:Line Chart / 多月:Multi-Series Line |
| 銷售額 | SUM(subtotal) GROUP BY DAY(created_time) | 同上 |
| 訂單狀態分布 | COUNT(*), ebay_status GROUP BY ebay_status | 單區間:3D Pie / 多月:Stacked Column |
關鍵代碼片段:多月份對比數據集構建
<?php
// 以銷售額為例
$amount_xml = "<graph caption='{$_LANG['amount_stats']}' ... >";
foreach ($start_date_arr as $k => $val) {
$seriesName = local_date('Y-m', $val);
$amount_xml .= "<dataset seriesName='$seriesName' color='" . chart_color($k) . "'>";
$sql = "SELECT FLOOR((created_time - $val) / 86400) AS sn, created_time, SUM(subtotal) AS order_amount
FROM " . $ecs->table('erp_ebay_order') . "
WHERE ebay_status = 'Complete'
AND created_time >= '$val'
AND created_time <= " . ($end_date_arr[$k] + 86400) . "
GROUP BY sn";
$res = $db->query($sql);
$lastDay = 0;
while ($row = $db->fetchRow($res)) {
$day = gmdate('d', $row['created_time'] + $timezone * 3600);
// 補齊缺失日期為 0
for (; $lastDay < $day - 1; $lastDay++) {
$amount_xml .= "<set value='0' />";
}
$amount_xml .= "<set value='{$row['order_amount']}' />";
$lastDay = $day;
}
$amount_xml .= "</dataset>";
}
// 類別軸 1-31
$amount_xml .= "<categories>" . implode('', array_map(fn($i)=>"<category label='$i'/>", range(1,31))) . "</categories></graph>";
?>
<?php
elseif ($act == 'download') {
admin_priv('download_order_stats');
$filename = $_REQUEST['filename'] ?? 'report';
header("Content-type: application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename=$filename.xls");
// 合並查詢訂單量+銷售額
$sql = "SELECT FLOOR((created_time - $start_date)/86400) sn, created_time, COUNT(*) cnt, SUM(subtotal) amt
FROM " . $ecs->table('erp_ebay_order') . "
WHERE created_time >= $start_date AND created_time <= " . ($end_date+86400) . " AND ebay_status='Complete'
GROUP BY sn";
// 輸出 Tab 分隔值
$data = "日期\t訂單量\t銷售額\n";
while ($row = $db->fetchRow($res)) {
$data .= gmdate($_CFG['date_format'], $row['created_time']+$timezone*3600+86400) . "\t"
. $row['cnt'] . "\t" . price_format($row['amt']) . "\n";
}
// 狀態統計
// ...
echo $data;
}
?>
<!-- 簡化版模板邏輯 -->
<div class="tab-div">
<div id="tabbar-div">
<span class="tab-front" id="general-tab">{$lang.tab_general}</span>
<span class="tab-back" id="amount-tab">{$lang.tab_amount}</span>
<span class="tab-back" id="status-tab">{$lang.tab_status}</span>
</div>
<div id="tabbody-div">
<table id="general-table"><tr><td>
<!--{if $is_multi eq "0"}--> <object ... line.swf ...> <!--{else}--> <object ... MSLine.swf ...> <!--{/if}-->
</td></tr></table>
<!-- amount-table, status-table 同理 -->
</div>
</div>
<script> // tab.js 控制顯示/隱藏 </script>
| 現狀 | 問題 | 重構方向 |
|---|---|---|
| Flash 圖表 | 瀏覽器已全面停用 Flash,圖表無法顯示 | 遷移至 ECharts、Chart.js、ApexCharts 等 HTML5/Canvas 庫 |
| SQL 字符串拼接 | 注入風險、難維護 | 使用 PDO 預處理 或 Query Builder (Laravel DB, ThinkPHP Db, Doctrine DBAL) |
全局變量 $db/$ecs | 強耦合、難測試 | 依賴注入容器、Repository 模式 |
| Smarty 3 老版本 | 語法受限、性能一般 | 升級 Smarty 4 或遷移至 Twig、Blade、原生 PHP 模板 |
| Excel 輸出 Tab 分隔 | 非標準 .xls,兼容性差 | 使用 PhpSpreadsheet 生成真實 .xlsx,支持樣式、公式、多工作表 |
硬編碼表名 erp_ebay_order | 無前綴配置、難多租戶 | Model 層封裝、表前綴配置化 |
<?php
// app/Http/Controllers/Admin/EbayOrderStatsController.php
class EbayOrderStatsController
{
public function __construct(
private EbayOrderRepository $repo,
private ChartDataBuilder $chartBuilder,
private ExcelExporter $excelExporter
) {}
public function index(Request $request): Response
{
$params = $request->validate([
'mode' => 'in:single,multi',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'year_month' => 'nullable|array',
'year_month.*' => 'date_format:Y-m',
]);
$charts = $this->chartBuilder->build($params);
return view('admin.ebay.stats', ['charts' => $charts, 'params' => $params]);
}
public function export(Request $request): BinaryFileResponse
{
$params = $request->validate([...]);
$filePath = $this->excelExporter->export($params);
return response()->download($filePath)->deleteFileAfterSend();
}
}
?>
這段代碼是典型的「jQuery + Flash + Smarty + 過程式 PHP」時代產物(約 2010-2015 年)。理解其業務邏輯(多維度時間切片、數據補零、Flash XML 協議)對重構遷移至關重要,但切勿在新項目中複製此架構。