This commit is contained in:
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.170"
|
||||
SYSTEM_VERSION = "V10.171"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
<div class="dashboard-filter-card">
|
||||
<form class="dashboard-filter-form" method="GET" action="/">
|
||||
<input class="dashboard-search" type="text" name="q" value="{{ search_query }}" placeholder="搜尋商品名稱或品號...">
|
||||
<select class="dashboard-select" name="category" onchange="this.form.submit()">
|
||||
<select class="dashboard-select" name="category" data-dashboard-auto-submit>
|
||||
<option value="all">所有分類</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat }}" {% if current_category == cat %}selected{% endif %}>{{ cat }}</option>
|
||||
@@ -209,10 +209,10 @@
|
||||
<a class="{% if current_filter == 'delisted' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='delisted', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">下架</a>
|
||||
</div>
|
||||
|
||||
<button class="dashboard-action-button" type="button" onclick="triggerTask()">
|
||||
<button class="dashboard-action-button" type="button" data-dashboard-task="crawler">
|
||||
<i class="fas fa-rotate"></i> 更新
|
||||
</button>
|
||||
<button class="dashboard-action-button is-primary" type="button" onclick="triggerNotification()">
|
||||
<button class="dashboard-action-button is-primary" type="button" data-dashboard-task="notification">
|
||||
<i class="fas fa-bell"></i> 發送通知
|
||||
</button>
|
||||
</form>
|
||||
@@ -222,7 +222,7 @@
|
||||
<section>
|
||||
<div class="dashboard-table-card">
|
||||
<div class="dashboard-table-head">
|
||||
<span class="momo-mono" style="font-size:11px;font-weight:800;color:var(--momo-text-tertiary);letter-spacing:.08em;">04</span>
|
||||
<span class="dashboard-section-index momo-mono">04</span>
|
||||
<span class="dashboard-table-title">{{ 'AI 挑品清單' if current_filter == 'ai_picks' else '商品列表' }}</span>
|
||||
<span class="dashboard-table-meta momo-mono">
|
||||
{% if current_filter == 'ai_picks' %}
|
||||
@@ -389,7 +389,7 @@
|
||||
<div class="dashboard-price-sub">match {{ (competitor.match_score * 100) | round(0) | int }}%</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span style="color:var(--momo-text-tertiary);">待比對</span>
|
||||
<span class="dashboard-muted">待比對</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
@@ -430,7 +430,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span style="color:var(--momo-text-tertiary);">尚無建議理由</span>
|
||||
<span class="dashboard-muted">尚無建議理由</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
@@ -440,7 +440,7 @@
|
||||
{% elif item.yesterday_diff < 0 %}
|
||||
<span class="dashboard-change-down">▼ -{{ item.yesterday_diff | abs | int | number_format }}</span>
|
||||
{% else %}
|
||||
<span style="color:var(--momo-text-tertiary);">--</span>
|
||||
<span class="dashboard-muted">--</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end momo-mono">
|
||||
@@ -450,13 +450,13 @@
|
||||
{% elif week_diff < 0 %}
|
||||
<span class="dashboard-change-down">-{{ week_diff | abs | int | number_format }}</span>
|
||||
{% else %}
|
||||
<span style="color:var(--momo-text-tertiary);">--</span>
|
||||
<span class="dashboard-muted">--</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end momo-mono" style="color:var(--momo-text-secondary);">
|
||||
<td class="dashboard-table-time text-end momo-mono">
|
||||
{{ item.record.timestamp.strftime('%m-%d %H:%M') if item.record.timestamp else '--' }}
|
||||
</td>
|
||||
<td class="text-end momo-mono" style="color:var(--momo-text-secondary);">
|
||||
<td class="dashboard-table-time text-end momo-mono">
|
||||
{{ item.safe_created_at.strftime('%m-%d %H:%M') if item.safe_created_at else '--' }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -520,5 +520,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/page-dashboard-v2.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -389,6 +389,13 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dashboard-section-index {
|
||||
color: var(--momo-text-tertiary);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.dashboard-table-title {
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 14px;
|
||||
@@ -596,6 +603,14 @@
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.dashboard-muted {
|
||||
color: var(--momo-text-tertiary);
|
||||
}
|
||||
|
||||
.dashboard-table-time {
|
||||
color: var(--momo-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-pchome-price {
|
||||
color: var(--momo-accent-strong);
|
||||
font-size: 16px;
|
||||
|
||||
@@ -30,17 +30,27 @@ let priceChartInstance = null;
|
||||
}
|
||||
|
||||
function ensureDashboardChart() {
|
||||
if (window.EwoooCChartTheme && window.EwoooCChartTheme.loadChartJs) {
|
||||
return window.EwoooCChartTheme.loadChartJs();
|
||||
}
|
||||
if (typeof Chart !== 'undefined') {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve(window.Chart);
|
||||
}
|
||||
if (dashboardChartLoader) {
|
||||
return dashboardChartLoader;
|
||||
}
|
||||
dashboardChartLoader = new Promise((resolve, reject) => {
|
||||
const existing = document.querySelector('script[data-chartjs-loader="dashboard"]');
|
||||
if (existing) {
|
||||
existing.addEventListener('load', () => resolve(window.Chart), { once: true });
|
||||
existing.addEventListener('error', () => reject(new Error('Chart.js 載入失敗')), { once: true });
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js';
|
||||
script.async = true;
|
||||
script.onload = resolve;
|
||||
script.dataset.chartjsLoader = 'dashboard';
|
||||
script.onload = () => resolve(window.Chart);
|
||||
script.onerror = () => reject(new Error('Chart.js 載入失敗'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
@@ -235,29 +245,40 @@ let priceChartInstance = null;
|
||||
});
|
||||
});
|
||||
|
||||
function triggerTask() {
|
||||
if (confirm('確定要手動執行全站爬蟲嗎?可能需要一段時間。')) {
|
||||
fetch('/api/run_task', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCSRFToken() }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => alert('錯誤: ' + error));
|
||||
document.querySelectorAll('[data-dashboard-auto-submit]').forEach(select => {
|
||||
select.addEventListener('change', () => {
|
||||
if (select.form) {
|
||||
select.form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardTaskMap = {
|
||||
crawler: {
|
||||
confirmText: '確定要手動執行全站爬蟲嗎?可能需要一段時間。',
|
||||
url: '/api/run_task'
|
||||
},
|
||||
notification: {
|
||||
confirmText: '確定要發送今日商品異動通知嗎?',
|
||||
url: '/api/trigger_momo_notification'
|
||||
}
|
||||
};
|
||||
|
||||
function runDashboardTask(taskName) {
|
||||
const task = dashboardTaskMap[taskName];
|
||||
if (!task || !confirm(task.confirmText)) return;
|
||||
fetch(task.url, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCSRFToken() }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => alert('錯誤: ' + error));
|
||||
}
|
||||
|
||||
function triggerNotification() {
|
||||
if (confirm('確定要發送今日商品異動通知嗎?')) {
|
||||
fetch('/api/trigger_momo_notification', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCSRFToken() }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => alert('錯誤: ' + error));
|
||||
}
|
||||
}
|
||||
document.querySelectorAll('[data-dashboard-task]').forEach(button => {
|
||||
button.addEventListener('click', () => runDashboardTask(button.dataset.dashboardTask));
|
||||
});
|
||||
|
||||
function trackMomoLinkClick(event) {
|
||||
const link = event.target.closest('.momo-tracked-link');
|
||||
|
||||
Reference in New Issue
Block a user