-
-
- 利润表
- 收入、成本、净利润
-
-
- 季度
- 年度
-
-
-
-
-
- 资产负债表
- 资产、负债、权益
-
-
- 结构
- 趋势
-
-
-
-
-
- 现金流量表
- 经营、投资、筹资
-
-
- 自由现金流
- 质量
-
-
+
+
+
+ {items.length === 0 ? (
+
暂无报告
+ ) : (
+
+
+
+
+ | 股票代码 |
+ 公司名称 |
+ 创建时间 |
+ 操作 |
+
+
+
+ {items.map((r) => {
+ const name = (r as any)?.content?.financials?.name || (r as any)?.content?.company_name || ''
+ return (
+
+ | {r.symbol} |
+ {name || -} |
+ {new Date(r.createdAt).toLocaleString()} |
+
+ 查看
+ |
+
+ )
+ })}
+
+
+
+ )}
- );
+ )
}
\ No newline at end of file
diff --git a/frontend/src/lib/prisma.ts b/frontend/src/lib/prisma.ts
new file mode 100644
index 0000000..290ba6d
--- /dev/null
+++ b/frontend/src/lib/prisma.ts
@@ -0,0 +1,13 @@
+import { PrismaClient } from '@prisma/client'
+
+const globalForPrisma = global as unknown as { prisma?: PrismaClient }
+
+export const prisma =
+ globalForPrisma.prisma ||
+ new PrismaClient({
+ log: ['error', 'warn']
+ })
+
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
+
+
diff --git a/scripts/dev.sh b/scripts/dev.sh
index 399d4c4..9996284 100755
--- a/scripts/dev.sh
+++ b/scripts/dev.sh
@@ -13,9 +13,12 @@ BACKEND_DIR="$REPO_ROOT/backend"
FRONTEND_DIR="$REPO_ROOT/frontend"
CONFIG_FILE="$REPO_ROOT/config/config.json"
+# Guard to ensure cleanup runs only once
+__CLEANED_UP=0
+
# Port configuration
BACKEND_PORT=8000
-FRONTEND_PORT=3000
+FRONTEND_PORT=3001
# Kill process using specified port
kill_port() {
@@ -70,8 +73,10 @@ run_backend() {
ensure_backend
cd "$BACKEND_DIR"
# Run and colorize output (avoid stdbuf on macOS)
- UVICORN_CMD=(uvicorn app.main:app --reload --port "$BACKEND_PORT")
- "${UVICORN_CMD[@]}" 2>&1 | awk -v p="[BACKEND]" -v color="$GREEN" -v reset="$RESET" '{print color p " " $0 reset}'
+ UVICORN_CMD=(uvicorn app.main:app --reload --port "$BACKEND_PORT" --log-level info)
+ "${UVICORN_CMD[@]}" 2>&1 | while IFS= read -r line; do
+ printf "%b[%s] [BACKEND] %s%b\n" "$GREEN" "$(date '+%Y-%m-%d %H:%M:%S')" "$line" "$RESET"
+ done
}
ensure_frontend() {
@@ -85,27 +90,70 @@ ensure_frontend() {
run_frontend() {
ensure_frontend
cd "$FRONTEND_DIR"
- npm run dev 2>&1 | awk -v p="[FRONTEND]" -v color="$CYAN" -v reset="$RESET" '{print color p " " $0 reset}'
+ npm run dev 2>&1 | while IFS= read -r line; do
+ printf "%b[%s] [FRONTEND] %s%b\n" "$CYAN" "$(date '+%Y-%m-%d %H:%M:%S')" "$line" "$RESET"
+ done
+}
+
+# Recursively kill a process tree (children first), with optional signal (default TERM)
+kill_tree() {
+ local pid="$1"
+ local signal="${2:-TERM}"
+ if [[ -z "${pid:-}" ]]; then
+ return
+ fi
+ # Kill children first
+ local children
+ children=$(pgrep -P "$pid" 2>/dev/null || true)
+ if [[ -n "${children:-}" ]]; then
+ for child in $children; do
+ kill_tree "$child" "$signal"
+ done
+ fi
+ # Then the parent
+ kill -"$signal" "$pid" 2>/dev/null || true
}
cleanup() {
+ # Ensure this runs only once even if multiple signals (INT/TERM/EXIT) arrive
+ if [[ $__CLEANED_UP -eq 1 ]]; then
+ return
+ fi
+ __CLEANED_UP=1
+
echo -e "\n${YELLOW}[CLEANUP]${RESET} Stopping services..."
-
- # Kill process groups to ensure all child processes are terminated
+
+ # Gracefully stop trees for backend and frontend, then escalate if needed
if [[ -n "${BACKEND_PID:-}" ]]; then
- kill -TERM -"$BACKEND_PID" 2>/dev/null || kill "$BACKEND_PID" 2>/dev/null || true
+ kill_tree "$BACKEND_PID" TERM
fi
if [[ -n "${FRONTEND_PID:-}" ]]; then
- kill -TERM -"$FRONTEND_PID" 2>/dev/null || kill "$FRONTEND_PID" 2>/dev/null || true
+ kill_tree "$FRONTEND_PID" TERM
fi
-
- sleep 1
-
- # Force kill any remaining processes on these ports
+
+ # Wait up to ~3s for graceful shutdown
+ for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
+ local backend_alive=0 frontend_alive=0
+ if [[ -n "${BACKEND_PID:-}" ]] && kill -0 "$BACKEND_PID" 2>/dev/null; then backend_alive=1; fi
+ if [[ -n "${FRONTEND_PID:-}" ]] && kill -0 "$FRONTEND_PID" 2>/dev/null; then frontend_alive=1; fi
+ if [[ $backend_alive -eq 0 && $frontend_alive -eq 0 ]]; then
+ break
+ fi
+ sleep 0.2
+ done
+
+ # Escalate to KILL if still alive
+ if [[ -n "${BACKEND_PID:-}" ]] && kill -0 "$BACKEND_PID" 2>/dev/null; then
+ kill_tree "$BACKEND_PID" KILL
+ fi
+ if [[ -n "${FRONTEND_PID:-}" ]] && kill -0 "$FRONTEND_PID" 2>/dev/null; then
+ kill_tree "$FRONTEND_PID" KILL
+ fi
+
+ # As a final safeguard, free the ports
kill_port "$BACKEND_PORT"
kill_port "$FRONTEND_PORT"
-
- wait 2>/dev/null || true
+
echo -e "${GREEN}[CLEANUP]${RESET} All services stopped."
}
@@ -116,8 +164,8 @@ main() {
kill_port "$BACKEND_PORT"
kill_port "$FRONTEND_PORT"
- echo -e "${GREEN}[BACKEND]${RESET} API: http://127.0.0.1:$BACKEND_PORT"
- echo -e "${CYAN}[FRONTEND]${RESET} APP: http://127.0.0.1:$FRONTEND_PORT\n"
+ echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [BACKEND]${RESET} API: http://127.0.0.1:$BACKEND_PORT"
+ echo -e "${CYAN}[$(date '+%Y-%m-%d %H:%M:%S')] [FRONTEND]${RESET} APP: http://127.0.0.1:$FRONTEND_PORT\n"
run_backend & BACKEND_PID=$!
run_frontend & FRONTEND_PID=$!
diff --git a/scripts/test-employees.py b/scripts/test-employees.py
index 5caaa6d..51c29d7 100755
--- a/scripts/test-employees.py
+++ b/scripts/test-employees.py
@@ -10,7 +10,7 @@ import json
# 添加项目根目录到Python路径
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'backend'))
-from app.services.tushare_client import TushareClient
+from tushare_legacy_client import TushareLegacyClient as TushareClient
async def test_employees_data():
diff --git a/scripts/test-holder-number.py b/scripts/test-holder-number.py
index 7011824..569c6c4 100755
--- a/scripts/test-holder-number.py
+++ b/scripts/test-holder-number.py
@@ -11,7 +11,7 @@ from datetime import datetime, timedelta
# 添加项目根目录到Python路径
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'backend'))
-from app.services.tushare_client import TushareClient
+from tushare_legacy_client import TushareLegacyClient as TushareClient
async def test_holder_number_data():
diff --git a/scripts/test-holder-processing.py b/scripts/test-holder-processing.py
index c4865ec..80abb3e 100755
--- a/scripts/test-holder-processing.py
+++ b/scripts/test-holder-processing.py
@@ -11,7 +11,7 @@ from datetime import datetime, timedelta
# 添加项目根目录到Python路径
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'backend'))
-from app.services.tushare_client import TushareClient
+from tushare_legacy_client import TushareLegacyClient as TushareClient
async def test_holder_num_processing():
diff --git a/scripts/test-tax-to-ebt.py b/scripts/test-tax-to-ebt.py
index ab504b1..946b39d 100644
--- a/scripts/test-tax-to-ebt.py
+++ b/scripts/test-tax-to-ebt.py
@@ -9,7 +9,7 @@ import json
# 添加 backend 目录到 Python 路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
-from app.services.tushare_client import TushareClient
+from tushare_legacy_client import TushareLegacyClient as TushareClient
async def test_tax_to_ebt():
# 读取配置获取 token
diff --git a/scripts/tushare_legacy_client.py b/scripts/tushare_legacy_client.py
new file mode 100644
index 0000000..0e5fe8b
--- /dev/null
+++ b/scripts/tushare_legacy_client.py
@@ -0,0 +1,41 @@
+import sys
+import os
+import asyncio
+from typing import Any, Dict, List, Optional
+
+# Add backend to path to import TushareProvider
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
+from app.data_providers.tushare import TushareProvider
+
+class TushareLegacyClient:
+ """
+ An adapter to mimic the old TushareClient for legacy scripts,
+ but uses the new TushareProvider under the hood.
+ """
+ def __init__(self, token: str):
+ if not token:
+ raise ValueError("Token must be provided.")
+ self.provider = TushareProvider(token=token)
+
+ async def query(
+ self,
+ api_name: str,
+ params: Optional[Dict[str, Any]] = None,
+ fields: Optional[str] = None, # Note: fields are not used in the new provider's _query
+ ) -> List[Dict[str, Any]]:
+ """
+ Mimics the .query() method by calling the provider's internal _query method.
+ """
+ # The new _query method is protected, but we call it here for the script's sake.
+ return await self.provider._query(api_name=api_name, params=params, fields=fields)
+
+ async def aclose(self):
+ """Mimic aclose to allow 'async with' syntax."""
+ if hasattr(self.provider, '_client') and self.provider._client:
+ await self.provider._client.aclose()
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc, tb):
+ await self.aclose()