tweak: 品种配置从卡片改为列表视图,支持直接修改合约代码

refactor3.0
Lxy 1 week ago
parent 83e81bc3cb
commit 2009968023

@ -297,43 +297,74 @@
.badge-info { background: #dbeafe; color: #1e40af; } .badge-info { background: #dbeafe; color: #1e40af; }
.badge-gray { background: var(--gray-100); color: var(--gray-600); } .badge-gray { background: var(--gray-100); color: var(--gray-600); }
/* Symbol Grid */ /* Symbol Table */
.symbol-grid { .symbol-table {
display: grid; width: 100%;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); border-collapse: collapse;
gap: 12px;
margin-top: 16px; margin-top: 16px;
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
padding-right: 8px; display: block;
}
.symbol-table thead {
position: sticky;
top: 0;
background: var(--gray-50);
z-index: 1;
}
.symbol-table th {
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: var(--gray-700);
font-size: 13px;
border-bottom: 2px solid var(--gray-200);
}
.symbol-table td {
padding: 12px 16px;
border-bottom: 1px solid var(--gray-100);
font-size: 14px;
} }
.symbol-card { .symbol-table tr:hover {
background: var(--gray-50); background: var(--gray-50);
}
.symbol-table .symbol-name {
font-weight: 600;
color: var(--gray-800);
}
.symbol-table .symbol-code-input {
font-family: 'SF Mono', 'Cascadia Code', monospace;
font-size: 13px;
color: var(--primary);
padding: 6px 10px;
border: 1px solid var(--gray-200); border: 1px solid var(--gray-200);
border-radius: var(--radius); border-radius: 6px;
padding: 14px; width: 120px;
text-align: center;
transition: all 0.2s; transition: all 0.2s;
position: relative;
} }
.symbol-card:hover { border-color: var(--primary-light); box-shadow: var(--shadow-md); } .symbol-table .symbol-code-input:focus {
.symbol-name { font-weight: 600; color: var(--gray-800); font-size: 14px; } outline: none;
.symbol-code { font-family: 'SF Mono', 'Cascadia Code', monospace; font-size: 13px; color: var(--primary); margin-top: 4px; } border-color: var(--primary-light);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
.symbol-actions { }
.symbol-table .symbol-actions {
display: flex; display: flex;
gap: 6px; gap: 6px;
margin-top: 10px;
justify-content: center;
} }
.symbol-actions .btn-sm { .symbol-table .symbol-actions .btn-sm {
padding: 4px 8px; padding: 4px 10px;
font-size: 12px; font-size: 12px;
} }
.add-symbol-form { .add-symbol-form {
background: var(--gray-50); background: var(--gray-50);
border: 2px dashed var(--gray-300); border: 2px dashed var(--gray-300);
@ -1451,7 +1482,7 @@
const stockCount = Object.keys(stock).length; const stockCount = Object.keys(stock).length;
document.getElementById('configCount').textContent = `期货 ${futuresCount} 个, 股票 ${stockCount} 个`; document.getElementById('configCount').textContent = `期货 ${futuresCount} 个, 股票 ${stockCount} 个`;
const renderGrid = (items, type) => { const renderTable = (items, type) => {
let filteredItems = items; let filteredItems = items;
if (currentSearchTerm) { if (currentSearchTerm) {
@ -1476,20 +1507,31 @@
return text.replace(regex, '<span class="highlight">$1</span>'); return text.replace(regex, '<span class="highlight">$1</span>');
}; };
return `<div class="symbol-grid">${Object.entries(filteredItems).map(([name, code]) => const rows = Object.entries(filteredItems).map(([name, code]) =>
`<div class="symbol-card"> `<tr>
<div class="symbol-name">${highlightText(name)}</div> <td class="symbol-name">${highlightText(name)}</td>
<div class="symbol-code">${highlightText(code)}</div> <td><input type="text" class="symbol-code-input" value="${code}" data-type="${type}" data-name="${name}" data-original="${code}"></td>
<div class="symbol-actions"> <td class="symbol-actions">
<button class="btn btn-primary btn-sm" onclick="editSymbol('${type}', '${code}', '${name}')">修改</button> <button class="btn btn-primary btn-sm" onclick="saveSymbolCode('${type}', '${name}', this)">保存</button>
<button class="btn btn-danger btn-sm" onclick="deleteSymbol('${type}', '${code}')">删除</button> <button class="btn btn-danger btn-sm" onclick="deleteSymbol('${type}', '${code}')">删除</button>
</div> </td>
</div>` </tr>`
).join('')}</div>`; ).join('');
return `<table class="symbol-table">
<thead>
<tr>
<th>品种名称</th>
<th>合约代码</th>
<th>操作</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>`;
}; };
document.getElementById('tab-futures').innerHTML = renderGrid(futures, 'futures'); document.getElementById('tab-futures').innerHTML = renderTable(futures, 'futures');
document.getElementById('tab-stock').innerHTML = renderGrid(stock, 'stock'); document.getElementById('tab-stock').innerHTML = renderTable(stock, 'stock');
} }
function filterSymbols(searchTerm) { function filterSymbols(searchTerm) {
@ -1608,6 +1650,52 @@
openEditSymbolModal(type, code, name); openEditSymbolModal(type, code, name);
} }
async function saveSymbolCode(type, name, btn) {
const row = btn.closest('tr');
const input = row.querySelector('.symbol-code-input');
const newCode = input.value.trim();
const originalCode = input.dataset.original;
if (!newCode) {
return showToast('合约代码不能为空', 'error');
}
if (newCode === originalCode) {
return showToast('合约代码未修改', 'info');
}
// 更新配置
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {});
symbols[name] = newCode;
const fullConfig = {
futures: currentConfig.futures || {},
stock: currentConfig.stock || {}
};
fullConfig[type] = symbols;
try {
const res = await fetch(`${API}/config/upload`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fullConfig)
});
if (res.ok) {
showToast('合约代码修改成功', 'success');
input.dataset.original = newCode;
} else {
const data = await res.json();
showToast(data.detail || '修改失败', 'error');
// 恢复原始值
input.value = originalCode;
}
} catch (e) {
showToast(`修改失败: ${e.message}`, 'error');
input.value = originalCode;
}
}
async function deleteSymbol(type, code) { async function deleteSymbol(type, code) {
if (!confirm('确定删除此品种?')) return; if (!confirm('确定删除此品种?')) return;

Binary file not shown.

Binary file not shown.

@ -0,0 +1,8 @@
name: symbol-config-list-view
phase: build
workflow: tweak
created_at: "2026-07-07T23:08:00+08:00"
updated_at: "2026-07-07T23:08:00+08:00"
build_mode: direct
verify_result: null
archived: false

@ -0,0 +1,27 @@
# 设计文档
## 实现方案
### 1. 列表视图结构
使用表格替代卡片网格:
```
| 品种名称 | 合约代码 | 操作 |
|----------|----------|------|
| 螺纹钢 | rb | [保存] [删除] |
| 铁矿石 | i | [保存] [删除] |
```
### 2. 合约代码编辑
- 合约代码列使用 input 输入框
- 修改后点击"保存"按钮提交
- 保存时调用 API 更新配置
### 3. CSS 调整
- 移除 `.symbol-grid``.symbol-card` 样式
- 添加 `.symbol-table` 列表样式
- 保持与现有 UI 风格一致
### 4. JavaScript 调整
- `renderConfig()` 函数改为渲染表格
- 新增 `saveSymbolCode(type, oldCode, newCode, name)` 函数
- 保留删除功能

@ -0,0 +1,16 @@
# 品种配置列表视图改造
## 动机
当前品种配置使用卡片网格展示,修改配置不方便:
1. 卡片占用空间大,品种多时需要大量滚动
2. 修改合约代码需要点击"修改"按钮打开弹窗,操作繁琐
## 目标
1. 将品种卡片改为紧凑列表视图
2. 支持直接在列表中修改合约代码
## 范围
- 修改 `app/static/index.html` 中的品种配置页面
- 将 `symbol-grid` + `symbol-card` 改为表格列表
- 在列表中直接显示可编辑的合约代码输入框
- 保留删除功能

@ -0,0 +1,5 @@
# 任务列表
- [x] 1. 修改 CSS`.symbol-grid` + `.symbol-card` 改为 `.symbol-table` 列表样式
- [x] 2. 修改 JavaScript`renderConfig()` 中的卡片渲染改为表格渲染,合约代码使用 input 输入框
- [x] 3. 新增 `saveSymbolCode()` 函数,支持直接在列表中保存修改的合约代码
Loading…
Cancel
Save