Compare commits

..

13 Commits

Author SHA1 Message Date
fish 06f7fc930a 更新数据库文件
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:27:05 +08:00
fish 1e6589a39f 修复页面标题被confirm弹窗代码污染
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:26:41 +08:00
fish 7bc85b2528 按钮样式:编辑绿底、删除红框无底
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:22:47 +08:00
fish 9bab06c1e7 删除按钮改为红框红字样式
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:14:45 +08:00
fish fb5cc801ee 删除确认改用自定义居中弹窗
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:13:01 +08:00
fish d87bb00208 已平仓列表新增持仓天数
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:10:48 +08:00
fish 0684f15859 日期字段添加周几显示
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:09:20 +08:00
fish c2a6b99fbd 按钮样式互换并统一间距
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 14:06:00 +08:00
fish c05a1b6c7f 编辑按钮加边框,操作按钮间距统一
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 13:58:52 +08:00
fish 8b5fa347cb 交易记录和期权新增编辑功能
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 13:56:40 +08:00
fish b22123bdfc 涨跌配色调整为红涨绿跌
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 13:51:47 +08:00
fish 8076cbbd51 期权新增买卖方向和盈亏算法展示
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 13:49:54 +08:00
fish c8ed098bfc 新增期权交易记录功能,支持C/P类型和行权价
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-26 13:32:21 +08:00
9 changed files with 818 additions and 16 deletions
+2 -1
View File
@@ -7,7 +7,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from app.database import engine, Base, SessionLocal from app.database import engine, Base, SessionLocal
from app.models import User from app.models import User
from app.seed import seed from app.seed import seed
from app.routers import contracts, admin, auth, positions, trades from app.routers import contracts, admin, auth, positions, trades, option_trades
TEMPLATES_DIR = Path(__file__).parent / "templates" TEMPLATES_DIR = Path(__file__).parent / "templates"
@@ -58,6 +58,7 @@ app.include_router(contracts.router)
app.include_router(admin.router) app.include_router(admin.router)
app.include_router(positions.router) app.include_router(positions.router)
app.include_router(trades.router) app.include_router(trades.router)
app.include_router(option_trades.router)
@app.get("/") @app.get("/")
+29
View File
@@ -147,3 +147,32 @@ class Trade(Base):
else: else:
result = (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0) result = (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
return round(result, 2) return round(result, 2)
class OptionTrade(Base):
__tablename__ = "option_trades"
id: Mapped[int] = mapped_column(primary_key=True)
product_code: Mapped[str] = mapped_column(String(10))
contract_code: Mapped[str] = mapped_column(String(10), index=True)
option_type: Mapped[str] = mapped_column(String(4))
direction: Mapped[str] = mapped_column(String(4))
strike_price: Mapped[float] = mapped_column(Float)
open_date: Mapped[date] = mapped_column(Date)
open_price: Mapped[float] = mapped_column(Float)
open_fee: Mapped[float | None] = mapped_column(Float, nullable=True, default=0)
close_date: Mapped[date | None] = mapped_column(Date, nullable=True)
close_price: Mapped[float | None] = mapped_column(Float, nullable=True)
close_fee: Mapped[float | None] = mapped_column(Float, nullable=True, default=0)
status: Mapped[str] = mapped_column(String(10), default="open")
@property
def pnl(self) -> float | None:
if self.close_price is None:
return None
mul = 20 # glass futures point value
if self.direction == "sell":
result = (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
else:
result = (self.close_price - self.open_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
return round(result, 2)
+149
View File
@@ -0,0 +1,149 @@
from datetime import date
from fastapi import APIRouter, Depends, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import OptionTrade, Contract, Product
router = APIRouter(prefix="/options", tags=["options"])
OPTION_TYPES = [("C", "C 看涨"), ("P", "P 看跌")]
OPTION_DIRECTIONS = [("buy", ""), ("sell", "")]
def get_product_contracts(db: Session) -> dict:
result: dict[str, list[str]] = {}
products = db.query(Product).order_by(Product.code).all()
for p in products:
codes = [c.code for c in p.contracts if c.is_active]
if codes:
result[p.code] = codes
return result
def today_str() -> str:
return date.today().isoformat()
@router.get("/", response_class=HTMLResponse)
def option_page(request: Request, db: Session = Depends(get_db)):
view = request.query_params.get("view", "")
open_trades = (
db.query(OptionTrade).filter(OptionTrade.status == "open")
.order_by(OptionTrade.open_date.desc()).all()
)
closed_trades = (
db.query(OptionTrade).filter(OptionTrade.status == "closed")
.order_by(OptionTrade.close_date.desc()).limit(50).all()
)
template = request.app.state.templates.get_template("options.html")
return HTMLResponse(
template.render(
request=request,
active_nav="options",
product_contracts=get_product_contracts(db),
option_types=OPTION_TYPES,
option_directions=OPTION_DIRECTIONS,
open_trades=open_trades,
closed_trades=closed_trades,
today=today_str(),
weekdays=["周一","周二","周三","周四","周五","周六","周日"],
)
)
@router.post("/open")
def open_trade(
request: Request,
contract_code: str = Form(...),
option_type: str = Form(...),
direction: str = Form(...),
strike_price: float = Form(...),
open_date: str = Form(...),
open_price: float = Form(...),
open_fee: float = Form(0.0),
db: Session = Depends(get_db),
):
code = contract_code.upper()
contract = db.query(Contract).filter(Contract.code == code).first()
product_code = contract.product.code if contract else code[:2]
t = OptionTrade(
product_code=product_code,
contract_code=code,
option_type=option_type,
direction=direction,
strike_price=strike_price,
open_date=date.fromisoformat(open_date),
open_price=open_price,
open_fee=open_fee,
status="open",
)
db.add(t)
db.commit()
return RedirectResponse("/options/", status_code=303)
@router.post("/{trade_id}/close")
def close_trade(
request: Request,
trade_id: int,
close_date: str = Form(...),
close_price: float = Form(...),
close_fee: float = Form(0.0),
db: Session = Depends(get_db),
):
t = db.query(OptionTrade).filter(OptionTrade.id == trade_id).first()
if t and t.status == "open":
t.close_date = date.fromisoformat(close_date)
t.close_price = close_price
t.close_fee = close_fee
t.status = "closed"
db.commit()
return RedirectResponse("/options/", status_code=303)
@router.post("/{trade_id}/edit")
def edit_trade(
request: Request,
trade_id: int,
contract_code: str = Form(...),
option_type: str = Form(...),
direction: str = Form(...),
strike_price: float = Form(...),
open_date: str = Form(...),
open_price: float = Form(...),
open_fee: float = Form(0.0),
close_date: str = Form(""),
close_price: float = Form(None),
close_fee: float = Form(None),
db: Session = Depends(get_db),
):
t = db.query(OptionTrade).filter(OptionTrade.id == trade_id).first()
if t:
code = contract_code.upper()
contract = db.query(Contract).filter(Contract.code == code).first()
t.product_code = contract.product.code if contract else code[:2]
t.contract_code = code
t.option_type = option_type
t.direction = direction
t.strike_price = strike_price
t.open_date = date.fromisoformat(open_date)
t.open_price = open_price
t.open_fee = open_fee
if close_date and close_price is not None:
t.close_date = date.fromisoformat(close_date)
t.close_price = close_price
t.close_fee = close_fee or 0
db.commit()
return RedirectResponse("/options/", status_code=303)
@router.post("/{trade_id}/delete")
def delete_trade(trade_id: int, db: Session = Depends(get_db)):
t = db.query(OptionTrade).filter(OptionTrade.id == trade_id).first()
if t:
db.delete(t)
db.commit()
return RedirectResponse("/options/", status_code=303)
+33
View File
@@ -57,6 +57,7 @@ def trades_page(request: Request, db: Session = Depends(get_db)):
open_trades=open_trades, open_trades=open_trades,
closed_trades=closed_trades, closed_trades=closed_trades,
today=today_str(), today=today_str(),
weekdays=["周一","周二","周三","周四","周五","周六","周日"],
) )
) )
@@ -108,6 +109,38 @@ def close_trade(
return RedirectResponse("/trades/", status_code=303) return RedirectResponse("/trades/", status_code=303)
@router.post("/{trade_id}/edit")
def edit_trade(
request: Request,
trade_id: int,
contract_code: str = Form(...),
direction: str = Form(...),
open_date: str = Form(...),
open_price: float = Form(...),
open_fee: float = Form(0.0),
close_date: str = Form(""),
close_price: float = Form(None),
close_fee: float = Form(None),
db: Session = Depends(get_db),
):
t = db.query(Trade).filter(Trade.id == trade_id).first()
if t:
code = contract_code.upper()
contract = db.query(Contract).filter(Contract.code == code).first()
t.product_code = contract.product.code if contract else code[:2]
t.contract_code = code
t.direction = direction
t.open_date = date.fromisoformat(open_date)
t.open_price = open_price
t.open_fee = open_fee
if close_date and close_price is not None:
t.close_date = date.fromisoformat(close_date)
t.close_price = close_price
t.close_fee = close_fee or 0
db.commit()
return RedirectResponse("/trades/", status_code=303)
@router.post("/{trade_id}/delete") @router.post("/{trade_id}/delete")
def delete_trade(trade_id: int, db: Session = Depends(get_db)): def delete_trade(trade_id: int, db: Session = Depends(get_db)):
t = db.query(Trade).filter(Trade.id == trade_id).first() t = db.query(Trade).filter(Trade.id == trade_id).first()
+5 -2
View File
@@ -133,8 +133,8 @@
/* ── Badges ── */ /* ── Badges ── */
.badge { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; } .badge { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; }
.badge-up { background: var(--success-bg); color: var(--success-fg); } .badge-up { background: var(--danger-bg); color: var(--danger-fg); }
.badge-down { background: var(--danger-bg); color: var(--danger-fg); } .badge-down { background: var(--success-bg); color: var(--success-fg); }
.badge-warn { background: var(--warn-bg); color: var(--warn-fg); } .badge-warn { background: var(--warn-bg); color: var(--warn-fg); }
/* ── Forms ── */ /* ── Forms ── */
@@ -209,6 +209,9 @@
<a href="/trades/" class="{% if active_nav == 'trades' %}active{% endif %}"> <a href="/trades/" class="{% if active_nav == 'trades' %}active{% endif %}">
<span class="icon">📝</span> 交易记录 <span class="icon">📝</span> 交易记录
</a> </a>
<a href="/options/" class="{% if active_nav == 'options' %}active{% endif %}">
<span class="icon">📊</span> 期权交易
</a>
<a href="/admin/" class="{% if active_nav == 'admin' %}active{% endif %}"> <a href="/admin/" class="{% if active_nav == 'admin' %}active{% endif %}">
<span class="icon">⚙️</span> 系统管理 <span class="icon">⚙️</span> 系统管理
</a> </a>
+415
View File
@@ -0,0 +1,415 @@
{% extends "base.html" %}
{% block title %}期权交易{% endblock %}
{% block heading %}期权交易{% endblock %}
{% block breadcrumb %}期权开平仓记录{% endblock %}
{% block content %}
{% set view = request.query_params.get('view', '') %}
<style>
.tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 2px solid var(--border); }
.tab-btn {
padding: 10px 20px; border: none; background: none;
font-size: 0.88rem; font-weight: 500; color: var(--sub);
cursor: pointer; text-decoration: none; border-bottom: 2px solid transparent;
margin-bottom: -2px; transition: color .12s, border-color .12s;
}
.tab-btn:hover { color: var(--fg); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
</style>
<div class="tabs">
<a href="/options/" class="tab-btn{% if view != 'closed' %} active{% endif %}">📋 持仓</a>
<a href="/options/?view=closed" class="tab-btn{% if view == 'closed' %} active{% endif %}">📊 已平仓</a>
</div>
{% if view != 'closed' %}
<div class="tab-panel active">
{# ── 新建开仓 ── #}
<div class="section-title">新建开仓</div>
<div class="form-card" style="margin-bottom:28px;max-width:100%;">
<form method="post" action="/options/open">
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:100px;">
<label>品种</label>
<select id="productSelect" required onchange="filterContracts()">
<option value="">--</option>
{% for pcode in product_contracts %}
<option value="{{ pcode }}">{{ pcode }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:120px;">
<label>合约</label>
<select name="contract_code" id="contractSelect" required disabled>
<option value="">-- 先选品种 --</option>
</select>
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:70px;">
<label>类型</label>
<select name="option_type" required>
{% for v, label in option_types %}
<option value="{{ v }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:60px;">
<label>买卖</label>
<select name="direction" required>
{% for v, label in option_directions %}
<option value="{{ v }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:90px;">
<label>行权价</label>
<input type="number" step="any" name="strike_price" required placeholder="1300" style="font-size:0.9rem;">
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
<label>权利金</label>
<input type="number" step="any" name="open_price" required placeholder="25" style="font-size:0.9rem;">
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:130px;">
<label>开仓日期</label>
<input type="date" name="open_date" value="{{ today }}" required style="font-size:0.85rem;">
</div>
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
<label>手续费</label>
<input type="number" step="any" name="open_fee" value="0" style="font-size:0.9rem;">
</div>
<button type="submit" class="btn btn-primary" style="font-size:0.82rem;padding:8px 18px;">开仓</button>
</div>
</form>
</div>
{# ── 持仓列表 ── #}
<div class="section-title">持仓中 · {{ open_trades|length }} 笔</div>
{% if open_trades %}
<div class="table-wrap">
<table>
<tr><th>品种</th><th>合约</th><th>类型</th><th>买卖</th><th>行权价</th><th>权利金</th><th>开仓日期</th><th>手续费</th><th>操作</th></tr>
{% for t in open_trades %}
<tr>
<td>{{ t.product_code }}</td>
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
<td>
{% if t.option_type == 'C' %}
<span class="badge badge-up">C</span>
{% else %}
<span class="badge badge-down">P</span>
{% endif %}
</td>
<td>
{% if t.direction == 'sell' %}
<span class="badge badge-down"></span>
{% else %}
<span class="badge badge-up"></span>
{% endif %}
</td>
<td>{{ t.strike_price }}</td>
<td>{{ t.open_price }}</td>
<td>{{ t.open_date }} {{ weekdays[t.open_date.weekday()] }}</td>
<td>{{ t.open_fee or 0 }}</td>
<td>
<button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
onclick="showCloseForm({{ t.id }})">平仓</button>
<button style="background:var(--success-bg);color:var(--success-fg);border:1px solid var(--success);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;margin-left:8px;"
onclick="showEditForm('option', {{ t.id }}, '{{ t.product_code }}', '{{ t.contract_code }}', '{{ t.option_type }}', '{{ t.direction }}', {{ t.strike_price }}, {{ t.open_price }}, {{ t.open_fee or 0 }}, '{{ t.open_date }}', null, null, null, '{{ t.status }}')">编辑</button>
<form method="post" action="/options/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
<button style="background:transparent;color:#dc2626;border:1px solid #ef4444;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确定删除此记录?', this.closest('form').action)">删除</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
{% else %}
<div style="text-align:center;padding:40px;color:var(--sub);">暂无持仓</div>
{% endif %}
{# ── 平仓弹窗 ── #}
<div id="closeFormOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:99;" onclick="hideCloseForm()"></div>
<div id="closeFormBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:100;width:360px;max-width:90vw;">
<h3 style="margin-bottom:16px;">平仓</h3>
<form method="post" id="closeForm">
<div class="form-group">
<label>平仓日期</label>
<input type="date" name="close_date" value="{{ today }}" required style="font-size:0.85rem;">
</div>
<div class="form-group">
<label>平仓价格</label>
<input type="number" step="any" name="close_price" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>手续费</label>
<input type="number" step="any" name="close_fee" value="0" style="font-size:0.9rem;">
</div>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button type="button" class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideCloseForm()">取消</button>
<button type="submit" class="btn btn-primary">确认平仓</button>
</div>
</form>
</div>
</div>
{% else %}
<div class="tab-panel active">
{# ═══════════════ 已平仓 ═══════════════ #}
{% if closed_trades %}
<div class="section-title">已平仓 · {{ closed_trades|length }} 笔</div>
<div class="table-wrap">
<table>
<tr><th>品种</th><th>合约</th><th>类型</th><th>买卖</th><th>行权价</th><th>开仓</th><th>平仓</th><th>持仓</th><th>权利金</th><th>平仓价</th><th>开仓费</th><th>平仓费</th><th>盈亏</th><th>操作</th></tr>
{% for t in closed_trades %}
<tr>
<td>{{ t.product_code }}</td>
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
<td>
{% if t.option_type == 'C' %}
<span class="badge badge-up">C</span>
{% else %}
<span class="badge badge-down">P</span>
{% endif %}
</td>
<td>
{% if t.direction == 'sell' %}
<span class="badge badge-down"></span>
{% else %}
<span class="badge badge-up"></span>
{% endif %}
</td>
<td>{{ t.strike_price }}</td>
<td style="font-size:0.8rem;">{{ t.open_date }} {{ weekdays[t.open_date.weekday()] }}</td>
<td style="font-size:0.8rem;">{{ t.close_date }} {{ weekdays[t.close_date.weekday()] }}</td>
<td>{{ (t.close_date - t.open_date).days }}天</td>
<td>{{ t.open_price }}</td>
<td>{{ t.close_price }}</td>
<td>{{ t.open_fee or 0 }}</td>
<td>{{ t.close_fee or 0 }}</td>
<td>
{% set p = t.pnl %}
{% if p is not none %}
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% else %}color:var(--sub);{% endif %}">
{% if p > 0 %}+{% endif %}{{ p }}
</span>
<span style="color:var(--sub);cursor:pointer;font-size:0.75rem;margin-left:4px;"
onclick="showPnlDrawer('{{ t.product_code }} {{ t.contract_code.replace(t.product_code, '', 1) }} {{ t.option_type }}', '{{ t.direction }}', {{ t.open_price }}, {{ t.close_price }}, {{ t.open_fee or 0 }}, {{ t.close_fee or 0 }}, {{ p }})" title="计算过程">&#9432;</span>
{% endif %}
</td>
<td>
<button style="background:var(--success-bg);color:var(--success-fg);border:1px solid var(--success);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;margin-left:8px;"
onclick="showEditForm('option', {{ t.id }}, '{{ t.product_code }}', '{{ t.contract_code }}', '{{ t.option_type }}', '{{ t.direction }}', {{ t.strike_price }}, {{ t.open_price }}, {{ t.open_fee or 0 }}, '{{ t.open_date }}', {{ t.close_price }}, {{ t.close_fee or 0 }}, '{{ t.close_date }}', '{{ t.status }}')">编辑</button>
<form method="post" action="/options/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
<button style="background:transparent;color:#dc2626;border:1px solid #ef4444;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确定删除此记录?', this.closest('form').action)">删除</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
{% set ns = namespace(total_pnl=0, total_open_fee=0, total_close_fee=0) %}
{% for t in closed_trades %}
{% set ns.total_pnl = ns.total_pnl + (t.pnl or 0) %}
{% set ns.total_open_fee = ns.total_open_fee + (t.open_fee or 0) %}
{% set ns.total_close_fee = ns.total_close_fee + (t.close_fee or 0) %}
{% endfor %}
<div style="display:flex;gap:24px;margin-bottom:24px;padding:12px 18px;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:0.88rem;flex-wrap:wrap;">
<span>盈亏合计 <b style="{% if ns.total_pnl > 0 %}color:var(--success-fg);{% elif ns.total_pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if ns.total_pnl > 0 %}+{% endif %}{{ '%.2f'|format(ns.total_pnl) }}</b></span>
<span style="color:var(--sub);">|</span>
<span>开仓手续费 <b>{{ '%.2f'|format(ns.total_open_fee) }}</b></span>
<span style="color:var(--sub);">|</span>
<span>平仓手续费 <b>{{ '%.2f'|format(ns.total_close_fee) }}</b></span>
<span style="color:var(--sub);">|</span>
<span>手续费合计 <b>{{ '%.2f'|format(ns.total_open_fee + ns.total_close_fee) }}</b></span>
</div>
{% else %}
<div style="text-align:center;padding:60px;color:var(--sub);">暂无已平仓记录</div>
{% endif %}
</div>
{% endif %}
{% if view != 'closed' %}
<script>
var productContracts = {{ product_contracts | tojson }};
function filterContracts() {
var prod = document.getElementById('productSelect').value;
var sel = document.getElementById('contractSelect');
sel.innerHTML = '<option value="">-- 选择合约 --</option>';
sel.disabled = !prod;
if (prod && productContracts[prod]) {
productContracts[prod].forEach(function(c) {
var display = c.startsWith(prod) ? c.substring(prod.length) : c;
sel.innerHTML += '<option value="' + c + '">' + display + '</option>';
});
}
}
function showCloseForm(tradeId) {
document.getElementById('closeForm').action = '/options/' + tradeId + '/close';
document.getElementById('closeFormOverlay').style.display = 'block';
document.getElementById('closeFormBox').style.display = 'block';
}
function hideCloseForm() {
document.getElementById('closeFormOverlay').style.display = 'none';
document.getElementById('closeFormBox').style.display = 'none';
}
</script>
{% endif %}
<div id="editFormOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:98;" onclick="hideEditForm()"></div>
<div id="editFormBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:99;width:400px;max-width:90vw;max-height:90vh;overflow-y:auto;">
<h3 style="margin-bottom:16px;">编辑记录</h3>
<form method="post" id="editForm">
<div class="form-group">
<label>合约</label>
<input type="text" name="contract_code" id="editContract" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>类型</label>
<select name="option_type" id="editOptionType" required>
{% for v, label in option_types %}
<option value="{{ v }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>买卖</label>
<select name="direction" id="editDirection" required>
{% for v, label in option_directions %}
<option value="{{ v }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>行权价</label>
<input type="number" step="any" name="strike_price" id="editStrike" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>开仓日期</label>
<input type="date" name="open_date" id="editOpenDate" required style="font-size:0.85rem;">
</div>
<div class="form-group">
<label>权利金</label>
<input type="number" step="any" name="open_price" id="editOpenPrice" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>手续费</label>
<input type="number" step="any" name="open_fee" id="editOpenFee" value="0" style="font-size:0.9rem;">
</div>
<div id="editCloseFields" style="display:none;">
<div class="form-group">
<label>平仓日期</label>
<input type="date" name="close_date" id="editCloseDate" style="font-size:0.85rem;">
</div>
<div class="form-group">
<label>平仓价格</label>
<input type="number" step="any" name="close_price" id="editClosePrice" style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>手续费</label>
<input type="number" step="any" name="close_fee" id="editCloseFee" value="0" style="font-size:0.9rem;">
</div>
</div>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button type="button" class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideEditForm()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
<div id="pnlOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:99;" onclick="hidePnlDrawer()"></div>
<div id="pnlBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:100;width:380px;max-width:90vw;">
<h3 style="margin-bottom:4px;" id="pnlTitle"></h3>
<p style="font-size:0.8rem;color:var(--sub);margin-bottom:14px;" id="pnlFormula"></p>
<table class="calc-table" style="width:100%;border-collapse:collapse;font-size:0.84rem;margin-bottom:12px;" id="pnlTable"></table>
<div style="font-size:1rem;" id="pnlResult"></div>
<button style="position:absolute;top:12px;right:16px;background:none;border:none;font-size:1.3rem;cursor:pointer;color:var(--sub);" onclick="hidePnlDrawer()">&times;</button>
</div>
<script>
function showPnlDrawer(title, direction, openPrice, closePrice, openFee, closeFee, result) {
document.getElementById('pnlTitle').textContent = title;
var dirLabel = direction === 'sell' ? '卖' : '买';
document.getElementById('pnlFormula').textContent = '方向: ' + dirLabel + ' | 每点 20 元';
var diff = direction === 'sell' ? (openPrice - closePrice) : (closePrice - openPrice);
var gross = diff * 20;
var fees = openFee + closeFee;
var html = '<tr style="background:var(--th-bg);"><td>项目</td><td>计算</td><td style="text-align:right;">金额</td></tr>';
html += '<tr><td>价差</td><td>' + (direction === 'sell' ? openPrice + ' - ' + closePrice : closePrice + ' - ' + openPrice) + '</td><td style="text-align:right;">' + diff.toFixed(2) + ' 点</td></tr>';
html += '<tr><td>毛利</td><td>' + diff.toFixed(2) + ' × 20</td><td style="text-align:right;">' + gross.toFixed(2) + '</td></tr>';
html += '<tr><td>开仓手续费</td><td></td><td style="text-align:right;color:var(--danger-fg);">-' + openFee.toFixed(2) + '</td></tr>';
html += '<tr><td>平仓手续费</td><td></td><td style="text-align:right;color:var(--danger-fg);">-' + closeFee.toFixed(2) + '</td></tr>';
document.getElementById('pnlTable').innerHTML = html;
document.getElementById('pnlResult').innerHTML = '<b>盈亏 = ' + gross.toFixed(2) + ' - ' + openFee.toFixed(2) + ' - ' + closeFee.toFixed(2) + ' = <span style="color:' + (result > 0 ? 'var(--success-fg)' : result < 0 ? 'var(--danger-fg)' : 'var(--sub)') + ';">' + (result > 0 ? '+' : '') + result.toFixed(2) + '</span></b>';
document.getElementById('pnlOverlay').style.display = 'block';
document.getElementById('pnlBox').style.display = 'block';
}
function showEditForm(type, id, productCode, contractCode, optionType, direction, strike, openPrice, openFee, openDate, closePrice, closeFee, closeDate, status) {
document.getElementById('editForm').action = '/' + type + 's/' + id + '/edit';
document.getElementById('editContract').value = contractCode;
document.getElementById('editOptionType').value = optionType;
document.getElementById('editDirection').value = direction;
document.getElementById('editStrike').value = strike;
document.getElementById('editOpenDate').value = openDate;
document.getElementById('editOpenPrice').value = openPrice;
document.getElementById('editOpenFee').value = openFee || 0;
if (status === 'closed' && closeDate) {
document.getElementById('editCloseFields').style.display = 'block';
document.getElementById('editCloseDate').value = closeDate;
document.getElementById('editClosePrice').value = closePrice;
document.getElementById('editCloseFee').value = closeFee || 0;
} else {
document.getElementById('editCloseFields').style.display = 'none';
}
document.getElementById('editFormOverlay').style.display = 'block';
document.getElementById('editFormBox').style.display = 'block';
}
function hideEditForm() {
document.getElementById('editFormOverlay').style.display = 'none';
document.getElementById('editFormBox').style.display = 'none';
}
function hidePnlDrawer() {
document.getElementById('pnlOverlay').style.display = 'none';
document.getElementById('pnlBox').style.display = 'none';
}
</script>
<div id="confirmOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:199;" onclick="hideConfirm()"></div>
<div id="confirmBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:28px 32px;z-index:200;text-align:center;max-width:90vw;">
<p id="confirmMsg" style="font-size:0.95rem;margin-bottom:20px;"></p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideConfirm()">取消</button>
<button class="btn" style="background:var(--danger);color:#fff;" id="confirmBtn">确认删除</button>
</div>
</div>
<script>
function confirmDelete(e, msg, url) {
e.preventDefault();
document.getElementById('confirmMsg').textContent = msg;
document.getElementById('confirmBtn').onclick = function() {
var f = document.createElement('form');
f.method = 'POST';
f.action = url;
document.body.appendChild(f);
f.submit();
};
document.getElementById('confirmOverlay').style.display = 'block';
document.getElementById('confirmBox').style.display = 'block';
}
function hideConfirm() {
document.getElementById('confirmOverlay').style.display = 'none';
document.getElementById('confirmBox').style.display = 'none';
}
</script>
{% endblock %}
+32 -3
View File
@@ -65,14 +65,14 @@
<span style="margin-left:auto;display:flex;gap:8px;"> <span style="margin-left:auto;display:flex;gap:8px;">
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;"> <form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
<input type="hidden" name="result" value="profit_taken"> <input type="hidden" name="result" value="profit_taken">
<button class="btn" style="background:var(--success);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="return confirm('确认 {{ round.contract_code }} 止盈清仓?')">止盈</button> <button class="btn" style="background:var(--success);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 止盈清仓?', this.closest('form').action)">止盈</button>
</form> </form>
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;"> <form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
<input type="hidden" name="result" value="meltdown"> <input type="hidden" name="result" value="meltdown">
<button class="btn" style="background:var(--danger);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="return confirm('确认 {{ round.contract_code }} 熔断清仓?')">熔断</button> <button class="btn" style="background:var(--danger);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 熔断清仓?', this.closest('form').action)">熔断</button>
</form> </form>
<form method="post" action="/positions/{{ round.id }}/delete" style="display:inline;"> <form method="post" action="/positions/{{ round.id }}/delete" style="display:inline;">
<button style="background:none;border:none;color:var(--na);cursor:pointer;font-size:0.78rem;" onclick="return confirm('确删除 {{ round.contract_code }} 整轮数据?')">删除</button> <button style="background:transparent;color:#dc2626;border:1px solid #ef4444;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确删除 {{ round.contract_code }} 整轮数据?', this.closest('form').action)">删除</button>
</form> </form>
</span> </span>
</div> </div>
@@ -182,4 +182,33 @@ function filterContracts() {
} }
} }
</script> </script>
<div id="confirmOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:199;" onclick="hideConfirm()"></div>
<div id="confirmBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:28px 32px;z-index:200;text-align:center;max-width:90vw;">
<p id="confirmMsg" style="font-size:0.95rem;margin-bottom:20px;"></p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideConfirm()">取消</button>
<button class="btn" style="background:var(--danger);color:#fff;" id="confirmBtn">确认</button>
</div>
</div>
<script>
function confirmDelete(e, msg, url) {
e.preventDefault();
document.getElementById('confirmMsg').textContent = msg;
document.getElementById('confirmBtn').onclick = function() {
var f = document.createElement('form');
f.method = 'POST';
f.action = url;
document.body.appendChild(f);
f.submit();
};
document.getElementById('confirmOverlay').style.display = 'block';
document.getElementById('confirmBox').style.display = 'block';
}
function hideConfirm() {
document.getElementById('confirmOverlay').style.display = 'none';
document.getElementById('confirmBox').style.display = 'none';
}
</script>
{% endblock %} {% endblock %}
+151 -8
View File
@@ -2,7 +2,6 @@
{% block title %}交易记录{% endblock %} {% block title %}交易记录{% endblock %}
{% block heading %}交易记录{% endblock %} {% block heading %}交易记录{% endblock %}
{% block breadcrumb %}开平仓记录{% endblock %} {% block breadcrumb %}开平仓记录{% endblock %}
{% block content %} {% block content %}
{% set view = request.query_params.get('view', '') %} {% set view = request.query_params.get('view', '') %}
@@ -92,14 +91,16 @@
<span class="badge badge-up"></span> <span class="badge badge-up"></span>
{% endif %} {% endif %}
</td> </td>
<td>{{ t.open_date }}</td> <td>{{ t.open_date }} {{ weekdays[t.open_date.weekday()] }}</td>
<td>{{ t.open_price }}</td> <td>{{ t.open_price }}</td>
<td>{{ t.open_fee or 0 }}</td> <td>{{ t.open_fee or 0 }}</td>
<td> <td>
<button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" <button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
onclick="showCloseForm({{ t.id }})">平仓</button> onclick="showCloseForm({{ t.id }})">平仓</button>
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;"> <button style="background:var(--success-bg);color:var(--success-fg);border:1px solid var(--success);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;margin-left:8px;"
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button> onclick="showEditForm('trade', {{ t.id }}, '{{ t.product_code }}', '{{ t.contract_code }}', '{{ t.direction }}', {{ t.open_price }}, {{ t.open_fee or 0 }}, '{{ t.open_date }}', null, null, null, '{{ t.status }}')">编辑</button>
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
<button style="background:transparent;color:#dc2626;border:1px solid #ef4444;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确定删除此记录?', this.closest('form').action)">删除</button>
</form> </form>
</td> </td>
</tr> </tr>
@@ -143,7 +144,7 @@
<div class="section-title">已平仓 · {{ closed_trades|length }} 笔</div> <div class="section-title">已平仓 · {{ closed_trades|length }} 笔</div>
<div class="table-wrap"> <div class="table-wrap">
<table> <table>
<tr><th>品种</th><th>合约</th><th>方向</th><th>开仓</th><th>平仓</th><th>开仓价</th><th>平仓价</th><th>开仓费</th><th>平仓费</th><th>盈亏</th><th>操作</th></tr> <tr><th>品种</th><th>合约</th><th>方向</th><th>开仓</th><th>平仓</th><th>持仓</th><th>开仓价</th><th>平仓价</th><th>开仓费</th><th>平仓费</th><th>盈亏</th><th>操作</th></tr>
{% for t in closed_trades %} {% for t in closed_trades %}
<tr> <tr>
<td>{{ t.product_code }}</td> <td>{{ t.product_code }}</td>
@@ -155,8 +156,9 @@
<span class="badge badge-up"></span> <span class="badge badge-up"></span>
{% endif %} {% endif %}
</td> </td>
<td style="font-size:0.8rem;">{{ t.open_date }}</td> <td style="font-size:0.8rem;">{{ t.open_date }} {{ weekdays[t.open_date.weekday()] }}</td>
<td style="font-size:0.8rem;">{{ t.close_date }}</td> <td style="font-size:0.8rem;">{{ t.close_date }} {{ weekdays[t.close_date.weekday()] }}</td>
<td>{{ (t.close_date - t.open_date).days }}天</td>
<td>{{ t.open_price }}</td> <td>{{ t.open_price }}</td>
<td>{{ t.close_price }}</td> <td>{{ t.close_price }}</td>
<td>{{ t.open_fee or 0 }}</td> <td>{{ t.open_fee or 0 }}</td>
@@ -167,11 +169,15 @@
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% else %}color:var(--sub);{% endif %}"> <span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% else %}color:var(--sub);{% endif %}">
{% if p > 0 %}+{% endif %}{{ p }} {% if p > 0 %}+{% endif %}{{ p }}
</span> </span>
<span style="color:var(--sub);cursor:pointer;font-size:0.75rem;margin-left:4px;"
onclick="showPnlDrawer('{{ t.product_code }} {{ t.contract_code.replace(t.product_code, '', 1) }}', '{{ t.direction }}', {{ t.open_price }}, {{ t.close_price }}, {{ t.open_fee or 0 }}, {{ t.close_fee or 0 }}, {{ p }})" title="计算过程">&#9432;</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
<button style="background:var(--success-bg);color:var(--success-fg);border:1px solid var(--success);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;margin-left:8px;"
onclick="showEditForm('trade', {{ t.id }}, '{{ t.product_code }}', '{{ t.contract_code }}', '{{ t.direction }}', {{ t.open_price }}, {{ t.open_fee or 0 }}, '{{ t.open_date }}', {{ t.close_price }}, {{ t.close_fee or 0 }}, '{{ t.close_date }}', '{{ t.status }}')">编辑</button>
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;"> <form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button> <button style="background:transparent;color:#dc2626;border:1px solid #ef4444;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="confirmDelete(event, '确定删除此记录?', this.closest('form').action)">删除</button>
</form> </form>
</td> </td>
</tr> </tr>
@@ -227,4 +233,141 @@ function hideCloseForm() {
} }
</script> </script>
{% endif %} {% endif %}
<div id="editFormOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:98;" onclick="hideEditForm()"></div>
<div id="editFormBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:99;width:400px;max-width:90vw;max-height:90vh;overflow-y:auto;">
<h3 style="margin-bottom:16px;">编辑记录</h3>
<form method="post" id="editForm">
<div class="form-group">
<label>合约</label>
<input type="text" name="contract_code" id="editContract" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>方向</label>
<select name="direction" id="editDirection" required>
{% for v, label in directions %}
<option value="{{ v }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>开仓日期</label>
<input type="date" name="open_date" id="editOpenDate" required style="font-size:0.85rem;">
</div>
<div class="form-group">
<label>开仓价</label>
<input type="number" step="any" name="open_price" id="editOpenPrice" required style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>开仓手续费</label>
<input type="number" step="any" name="open_fee" id="editOpenFee" value="0" style="font-size:0.9rem;">
</div>
<div id="editCloseFields" style="display:none;">
<div class="form-group">
<label>平仓日期</label>
<input type="date" name="close_date" id="editCloseDate" style="font-size:0.85rem;">
</div>
<div class="form-group">
<label>平仓价格</label>
<input type="number" step="any" name="close_price" id="editClosePrice" style="font-size:0.9rem;">
</div>
<div class="form-group">
<label>平仓手续费</label>
<input type="number" step="any" name="close_fee" id="editCloseFee" value="0" style="font-size:0.9rem;">
</div>
</div>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button type="button" class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideEditForm()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
<div id="pnlOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:99;" onclick="hidePnlDrawer()"></div>
<div id="pnlBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:100;width:380px;max-width:90vw;">
<h3 style="margin-bottom:4px;" id="pnlTitle"></h3>
<p style="font-size:0.8rem;color:var(--sub);margin-bottom:14px;" id="pnlFormula"></p>
<table class="calc-table" style="width:100%;border-collapse:collapse;font-size:0.84rem;margin-bottom:12px;" id="pnlTable"></table>
<div style="font-size:1rem;" id="pnlResult"></div>
<button style="position:absolute;top:12px;right:16px;background:none;border:none;font-size:1.3rem;cursor:pointer;color:var(--sub);" onclick="hidePnlDrawer()">&times;</button>
</div>
<script>
function showPnlDrawer(title, direction, openPrice, closePrice, openFee, closeFee, result) {
document.getElementById('pnlTitle').textContent = title;
var dirLabel = direction === 'short' ? '空' : '多';
document.getElementById('pnlFormula').textContent = '方向: ' + dirLabel + ' | 每点 20 元';
var diff = direction === 'short' ? (openPrice - closePrice) : (closePrice - openPrice);
var gross = diff * 20;
var fees = openFee + closeFee;
var html = '<tr style="background:var(--th-bg);"><td>项目</td><td>计算</td><td style="text-align:right;">金额</td></tr>';
html += '<tr><td>价差</td><td>' + (direction === 'short' ? openPrice + ' - ' + closePrice : closePrice + ' - ' + openPrice) + '</td><td style="text-align:right;">' + diff.toFixed(2) + ' 点</td></tr>';
html += '<tr><td>毛利</td><td>' + diff.toFixed(2) + ' × 20</td><td style="text-align:right;">' + gross.toFixed(2) + '</td></tr>';
html += '<tr><td>开仓手续费</td><td></td><td style="text-align:right;color:var(--danger-fg);">-' + openFee.toFixed(2) + '</td></tr>';
html += '<tr><td>平仓手续费</td><td></td><td style="text-align:right;color:var(--danger-fg);">-' + closeFee.toFixed(2) + '</td></tr>';
document.getElementById('pnlTable').innerHTML = html;
document.getElementById('pnlResult').innerHTML = '<b>盈亏 = ' + gross.toFixed(2) + ' - ' + openFee.toFixed(2) + ' - ' + closeFee.toFixed(2) + ' = <span style="color:' + (result > 0 ? 'var(--success-fg)' : result < 0 ? 'var(--danger-fg)' : 'var(--sub)') + ';">' + (result > 0 ? '+' : '') + result.toFixed(2) + '</span></b>';
document.getElementById('pnlOverlay').style.display = 'block';
document.getElementById('pnlBox').style.display = 'block';
}
function hidePnlDrawer() {
document.getElementById('pnlOverlay').style.display = 'none';
document.getElementById('pnlBox').style.display = 'none';
}
function showEditForm(type, id, productCode, contractCode, direction, openPrice, openFee, openDate, closePrice, closeFee, closeDate, status) {
document.getElementById('editForm').action = '/' + type + 's/' + id + '/edit';
document.getElementById('editContract').value = contractCode;
document.getElementById('editDirection').value = direction;
document.getElementById('editOpenDate').value = openDate;
document.getElementById('editOpenPrice').value = openPrice;
document.getElementById('editOpenFee').value = openFee || 0;
if (status === 'closed' && closeDate) {
document.getElementById('editCloseFields').style.display = 'block';
document.getElementById('editCloseDate').value = closeDate;
document.getElementById('editClosePrice').value = closePrice;
document.getElementById('editCloseFee').value = closeFee || 0;
} else {
document.getElementById('editCloseFields').style.display = 'none';
}
document.getElementById('editFormOverlay').style.display = 'block';
document.getElementById('editFormBox').style.display = 'block';
}
function hideEditForm() {
document.getElementById('editFormOverlay').style.display = 'none';
document.getElementById('editFormBox').style.display = 'none';
}
</script>
<div id="confirmOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:199;" onclick="hideConfirm()"></div>
<div id="confirmBox" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:28px 32px;z-index:200;text-align:center;max-width:90vw;">
<p id="confirmMsg" style="font-size:0.95rem;margin-bottom:20px;"></p>
<div style="display:flex;gap:10px;justify-content:center;">
<button class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideConfirm()">取消</button>
<button class="btn" style="background:var(--danger);color:#fff;" id="confirmBtn">确认删除</button>
</div>
</div>
<script>
function confirmDelete(e, msg, url) {
e.preventDefault();
document.getElementById('confirmMsg').textContent = msg;
document.getElementById('confirmBtn').onclick = function() {
var f = document.createElement('form');
f.method = 'POST';
f.action = url;
document.body.appendChild(f);
f.submit();
};
document.getElementById('confirmOverlay').style.display = 'block';
document.getElementById('confirmBox').style.display = 'block';
}
function hideConfirm() {
document.getElementById('confirmOverlay').style.display = 'none';
document.getElementById('confirmBox').style.display = 'none';
}
</script>
{% endblock %} {% endblock %}
BIN
View File
Binary file not shown.