Когда пользователь вводит тикер, приложение:
Отправляет запрос GET на мой бэкэнд Flask (/search)
Отображает данные об акциях, возвращенные из API Tiingo
После успешного отображения отправьте запрос POST в /add_to_history для хранения тикер в базе данных SQLite
Запрос GET работает правильно — данные отображаются в моей таблице так, как ожидалось.
Проблема:
Как только запрос POST (/add_to_history) возвращает ответ 200, таблица, в которой отображались полученные биржевые данные, немедленно исчезает со страницы. В консоли нет ошибок, и оба сетевых запроса возвращают статус 200. Пользовательский интерфейс просто скрывает содержимое сразу после завершения вызова POST.
Вот мой код index.html
Stock App
window.stockData = {
"companyOutLook": null,
"stockSummary": null
}
function onBtnClick(event) {
event.preventDefault();
const inputField = document.getElementById('stockTicker');
const tickerSymbol = inputField.value;
fetch(`http://127.0.0.1:5000/search?ticker=${e ... kerSymbol)}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Data received:', data);
// Check if response contains an error
if (data.error) {
hideTabsAndTable();
showErrorMessage();
return;
}
// Store data for later use
if (Array.isArray(data) && data.length > 0) {
window.stockData.companyOutLook = data[0];
if (data.length > 1) {
window.stockData.stockSummary = data[1];
}
else {
window.stockData.stockSummary = null;
}
// Show tabs and table when data is available
hideErrorMessage();
showTabsAndTable();
showCompanyOutlook();
// add to sqlite history
fetch(`http://127.0.0.1:5000/add_to_history`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ticker: tickerSymbol}),
})
.then(response => {
console.log('Response:', response);
})
.catch(error => {
console.error('Error:', error);
});
}
else {
window.stockData.companyOutLook = null;
window.stockData.stockSummary = null;
hideTabsAndTable();
showErrorMessage();
}
})
.catch(error => {
hideTabsAndTable();
showErrorMessage();
});
}
function showTabsAndTable() {
const tabs = document.querySelector('.tabs');
const table = document.querySelector('.content');
if (tabs) tabs.style.display = 'flex';
if (table) table.style.display = 'table';
}
function hideTabsAndTable() {
const tabs = document.querySelector('.tabs');
const table = document.querySelector('.content');
if (tabs) tabs.style.display = 'none';
if (table) table.style.display = 'none';
}
function showCompanyOutlook() {
const table = document.querySelector('.content');
table.innerHTML = '';
const rows = [
'Company Name',
'Ticker Symbol',
'Exchange Code',
'Start Date',
'Description'
];
const dataNames = [
"name",
"ticker",
"exchangeCode",
"startDate",
"description"
]
if (window.stockData.companyOutLook) {
rows.forEach((header, i) => {
const row = document.createElement('tr');
const headerCell = document.createElement('td');
headerCell.className = 'content-header';
headerCell.textContent = header;
const dataCell = document.createElement('td');
dataCell.className = 'content-data';
const value = window.stockData.companyOutLook[dataNames] ?? '';
dataCell.textContent = value;
row.appendChild(headerCell);
row.appendChild(dataCell);
table.appendChild(row);
});
}
else {
table.innerHTML = "
Error: No record has been found, please enter a valid symbol.
";
}
// Update active tab styling
updateActiveTab(0);
}
function showStockSummary() {
const table = document.querySelector('.content');
table.innerHTML = '';
const rows = [
'Ticker Symbol',
'Trading Day',
'Previous Closing Price',
'Opening Price',
'High Price',
'Low Price',
'Last Price',
'Change',
'Change Percent',
'Number of Shares Traded'
];
if (window.stockData.stockSummary) {
const summary = window.stockData.stockSummary;
const last = Number(summary.last ?? summary.tngoLast ?? 0);
const prevClose = Number(summary.prevClose ?? 0);
const change = prevClose ? last - prevClose : 0;
const changePercent = prevClose ? (change / prevClose) * 100 : 0;
const tradingDay = summary.timestamp
? String(summary.timestamp).split('T')[0]
: '';
const data = [
summary.ticker ?? '',
tradingDay,
prevClose || '',
summary.open ?? '',
summary.high ?? '',
summary.low ?? '',
last || '',
(change > 0 ? "+" + change.toFixed(2) : change < 0 ? change.toFixed(2) : "0") || '',
(changePercent > 0 ? "+" + changePercent.toFixed(2) + "%" : changePercent < 0 ? changePercent.toFixed(2) + "%" : "0%") || '',
summary.volume ?? '',
];
rows.forEach((header, i) => {
const row = document.createElement('tr');
const headerCell = document.createElement('td');
headerCell.className = 'content-header';
headerCell.textContent = header;
const dataCell = document.createElement('td');
dataCell.className = 'content-data';
dataCell.id = `data-${i}`;
dataCell.textContent = data;
row.appendChild(headerCell);
row.appendChild(dataCell);
table.appendChild(row);
});
// add image to data 7 and 8
const data7 = document.getElementById('data-7');
const data8 = document.getElementById('data-8');
if (data7 && data7.textContent.includes("+")) {
data7.innerHTML += "

"
} else if (data7 && data7.textContent.includes("-")) {
data7.innerHTML += "

"
}
if (data8 && data8.textContent.includes("+")) {
data8.innerHTML += "

"
} else if (data8 && data8.textContent.includes("-")) {
data8.innerHTML += "

"
}
}
else {
table.innerHTML = "
Error: No record has been found, please enter a valid symbol.
";
}
// Update active tab styling
updateActiveTab(1);
}
function updateActiveTab(activeIndex) {
const tabs = document.querySelectorAll('.tabs > div');
tabs.forEach((tab, index) => {
if (index === activeIndex) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
}
function showErrorMessage() {
const errorMessage = document.querySelector('.error-message');
errorMessage.style.display = 'block';
}
function hideErrorMessage() {
const errorMessage = document.querySelector('.error-message');
errorMessage.style.display = 'none';
}
// Initialize tabs with click handlers
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('.tabs > div');
tabs[0].addEventListener('click', showCompanyOutlook);
tabs[1].addEventListener('click', showStockSummary);
// Search History tab - no action for now
});
function clearInput() {
const inputField = document.getElementById('stockTicker');
inputField.value = '';
window.stockData.companyOutLook = null;
window.stockData.stockSummary = null;
hideTabsAndTable();
hideErrorMessage();
}
Stock Search
Enter Stock Ticker Symbol*
Search
Clear
Error: No record has been found, please enter a valid symbol.
Company Outlook
Stock Summary
Search History
Вот файл app.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import requests
import sqlite3
app = Flask(__name__)
CORS(app, resources={r"/search*": {"origins": "*"}, r"/add_to_history*": {"origins": "*"}})
API_KEY = "SECRET_API_KEY"
DB_NAME = "search_history.db"
@app.route('/')
def index():
return "Hello, World!"
def init_db():
with sqlite3.connect(DB_NAME) as conn:
conn.execute('''CREATE TABLE IF NOT EXISTS SearchHistory (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticker TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)''')
# initialize the database
# run init_db() when the app starts
init_db()
@app.route('/history', methods=['GET'])
def get_search_history():
with sqlite3.connect(DB_NAME) as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute('SELECT ticker, timestamp FROM SearchHistory LIMIT 10').fetchall()
results = [dict(row) for row in rows]
return jsonify(results)
@app.route('/add_to_history', methods=['POST'])
def add_to_history():
try:
data = request.get_json()
if not data:
return jsonify({'error': 'No JSON data provided'}), 400
ticker = data.get('ticker')
if not ticker:
return jsonify({'error': 'Ticker is required'}), 400
with sqlite3.connect(DB_NAME) as conn:
conn.execute('INSERT INTO SearchHistory(ticker) VALUES (?)', (ticker,))
conn.commit()
return jsonify({'message': 'Record added to history'}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
def get_history():
with sqlite3.connect(DB_NAME) as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute('SELECT ticker, timestamp FROM SearchHistory LIMIT 10').fetchall()
results = [dict(row) for row in rows]
return results
@app.route('/search', methods=['GET'])
def search():
try:
ticker = request.args.get('ticker', "")
# if not ticker is passed, return error message
if not ticker:
return jsonify({'error': "No record has been found, please enter a valid symbol" })
# define a return object
results = []
# fetch company outlook tab
url = f"https://api.tiingo.com/tiingo/daily/{ti ... n={API_KEY}"
response = requests.get(url)
data = response.json()
if data and data.get('detail', "") != "Not found.":
results.append(data)
# fetch stock summary tab
url = f"https://api.tiingo.com/iex/{ticker}?token={API_KEY}"
response = requests.get(url)
data = response.json()
if data and len(data) > 0:
results.append(data[0])
# add to history if fetched successfully
# if results:
# add_to_history(ticker)
return jsonify(results)
except Exception as e:
return jsonify({'error': str(e) })
if __name__ == '__main__':
app.run(debug=True)
Подробнее здесь: https://stackoverflow.com/questions/798 ... e-database
Мобильная версия