|
|
|
@ -355,6 +355,28 @@
|
|
|
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
|
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.symbol-table .symbol-category-select {
|
|
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
|
|
|
border: 1px solid var(--gray-200);
|
|
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.symbol-table .symbol-category-select:hover {
|
|
|
|
|
|
|
|
border-color: var(--primary-light);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.symbol-table .symbol-category-select:focus {
|
|
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
|
|
border-color: var(--primary-light);
|
|
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.symbol-table .symbol-actions {
|
|
|
|
.symbol-table .symbol-actions {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
gap: 6px;
|
|
|
|
gap: 6px;
|
|
|
|
@ -902,7 +924,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
<div class="add-symbol-form">
|
|
|
|
<div class="add-symbol-form">
|
|
|
|
<div class="card-title" style="margin-bottom: 16px; font-size: 15px;">手动添加品种</div>
|
|
|
|
<div class="card-title" style="margin-bottom: 16px; font-size: 15px;">手动添加品种</div>
|
|
|
|
<div class="form-row">
|
|
|
|
<div class="form-row" style="grid-template-columns: 1fr 1fr 1fr auto;">
|
|
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
|
|
<label class="form-label">品种名称</label>
|
|
|
|
<label class="form-label">品种名称</label>
|
|
|
|
<input class="form-input" id="newSymbolName" placeholder="如: 沪银">
|
|
|
|
<input class="form-input" id="newSymbolName" placeholder="如: 沪银">
|
|
|
|
@ -918,6 +940,12 @@
|
|
|
|
<option value="stock">股票</option>
|
|
|
|
<option value="stock">股票</option>
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
|
|
|
|
|
|
<label class="form-label">品类</label>
|
|
|
|
|
|
|
|
<select class="form-select" id="newSymbolCategory">
|
|
|
|
|
|
|
|
<option value="">请选择品类</option>
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
|
|
<div class="form-group" style="margin-bottom: 0;">
|
|
|
|
<button class="btn btn-primary" onclick="addSymbol()" style="height: 42px;">
|
|
|
|
<button class="btn btn-primary" onclick="addSymbol()" style="height: 42px;">
|
|
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
|
|
@ -1614,16 +1642,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
// 生成表格内容(每个分类一个表格,默认只显示第一个)
|
|
|
|
// 生成表格内容(每个分类一个表格,默认只显示第一个)
|
|
|
|
const tablesHtml = activeCategories.map((category, index) => {
|
|
|
|
const tablesHtml = activeCategories.map((category, index) => {
|
|
|
|
const rows = grouped[category].map(({ name, code }) =>
|
|
|
|
const rows = grouped[category].map(({ name, code }) => {
|
|
|
|
`<tr>
|
|
|
|
const currentCategory = SYMBOL_CATEGORIES[name] || '其他';
|
|
|
|
|
|
|
|
const categoryOptions = CATEGORY_ORDER.map(cat =>
|
|
|
|
|
|
|
|
`<option value="${cat}" ${cat === currentCategory ? 'selected' : ''}>${cat}</option>`
|
|
|
|
|
|
|
|
).join('');
|
|
|
|
|
|
|
|
return `<tr>
|
|
|
|
<td class="symbol-name">${highlightText(name)}</td>
|
|
|
|
<td class="symbol-name">${highlightText(name)}</td>
|
|
|
|
<td><input type="text" class="symbol-code-input" value="${code}" data-type="${type}" data-name="${name}" data-original="${code}"></td>
|
|
|
|
<td><input type="text" class="symbol-code-input" value="${code}" data-type="${type}" data-name="${name}" data-original="${code}"></td>
|
|
|
|
|
|
|
|
<td>
|
|
|
|
|
|
|
|
<select class="symbol-category-select" data-name="${name}" data-original="${currentCategory}">
|
|
|
|
|
|
|
|
${categoryOptions}
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
</td>
|
|
|
|
<td class="symbol-actions">
|
|
|
|
<td class="symbol-actions">
|
|
|
|
<button class="btn btn-primary btn-sm" onclick="saveSymbolCode('${type}', '${name}', this)">保存</button>
|
|
|
|
<button class="btn btn-primary btn-sm" onclick="saveSymbolRow('${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>
|
|
|
|
</td>
|
|
|
|
</td>
|
|
|
|
</tr>`
|
|
|
|
</tr>`;
|
|
|
|
).join('');
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
return `<div class="category-table-wrapper" data-category="${category}" style="display: ${index === 0 ? 'block' : 'none'}">
|
|
|
|
return `<div class="category-table-wrapper" data-category="${category}" style="display: ${index === 0 ? 'block' : 'none'}">
|
|
|
|
<table class="symbol-table">
|
|
|
|
<table class="symbol-table">
|
|
|
|
@ -1631,6 +1668,7 @@
|
|
|
|
<tr>
|
|
|
|
<tr>
|
|
|
|
<th>品种名称</th>
|
|
|
|
<th>品种名称</th>
|
|
|
|
<th>合约代码</th>
|
|
|
|
<th>合约代码</th>
|
|
|
|
|
|
|
|
<th>品类</th>
|
|
|
|
<th>操作</th>
|
|
|
|
<th>操作</th>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
</thead>
|
|
|
|
@ -1672,11 +1710,16 @@
|
|
|
|
const name = document.getElementById('newSymbolName').value.trim();
|
|
|
|
const name = document.getElementById('newSymbolName').value.trim();
|
|
|
|
const code = document.getElementById('newSymbolCode').value.trim();
|
|
|
|
const code = document.getElementById('newSymbolCode').value.trim();
|
|
|
|
const type = document.getElementById('newSymbolType').value;
|
|
|
|
const type = document.getElementById('newSymbolType').value;
|
|
|
|
|
|
|
|
const category = document.getElementById('newSymbolCategory').value;
|
|
|
|
|
|
|
|
|
|
|
|
if (!name || !code) {
|
|
|
|
if (!name || !code) {
|
|
|
|
return showToast('请填写品种名称和代码', 'error');
|
|
|
|
return showToast('请填写品种名称和代码', 'error');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!category) {
|
|
|
|
|
|
|
|
return showToast('请选择品种品类', 'error');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {});
|
|
|
|
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {});
|
|
|
|
|
|
|
|
|
|
|
|
if (symbols[name]) {
|
|
|
|
if (symbols[name]) {
|
|
|
|
@ -1685,6 +1728,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
symbols[name] = code;
|
|
|
|
symbols[name] = code;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存品类到 SYMBOL_CATEGORIES
|
|
|
|
|
|
|
|
SYMBOL_CATEGORIES[name] = category;
|
|
|
|
|
|
|
|
|
|
|
|
const fullConfig = {
|
|
|
|
const fullConfig = {
|
|
|
|
futures: currentConfig.futures || {},
|
|
|
|
futures: currentConfig.futures || {},
|
|
|
|
stock: currentConfig.stock || {}
|
|
|
|
stock: currentConfig.stock || {}
|
|
|
|
@ -1779,23 +1825,34 @@
|
|
|
|
openEditSymbolModal(type, code, name);
|
|
|
|
openEditSymbolModal(type, code, name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveSymbolCode(type, name, btn) {
|
|
|
|
async function saveSymbolRow(type, name, btn) {
|
|
|
|
const row = btn.closest('tr');
|
|
|
|
const row = btn.closest('tr');
|
|
|
|
const input = row.querySelector('.symbol-code-input');
|
|
|
|
const codeInput = row.querySelector('.symbol-code-input');
|
|
|
|
const newCode = input.value.trim();
|
|
|
|
const categorySelect = row.querySelector('.symbol-category-select');
|
|
|
|
const originalCode = input.dataset.original;
|
|
|
|
const newCode = codeInput.value.trim();
|
|
|
|
|
|
|
|
const originalCode = codeInput.dataset.original;
|
|
|
|
|
|
|
|
const newCategory = categorySelect.value;
|
|
|
|
|
|
|
|
const originalCategory = categorySelect.dataset.original;
|
|
|
|
|
|
|
|
|
|
|
|
if (!newCode) {
|
|
|
|
if (!newCode) {
|
|
|
|
return showToast('合约代码不能为空', 'error');
|
|
|
|
return showToast('合约代码不能为空', 'error');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (newCode === originalCode) {
|
|
|
|
const codeChanged = newCode !== originalCode;
|
|
|
|
return showToast('合约代码未修改', 'info');
|
|
|
|
const categoryChanged = newCategory !== originalCategory;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!codeChanged && !categoryChanged) {
|
|
|
|
|
|
|
|
return showToast('未做任何修改', 'info');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新配置
|
|
|
|
// 更新配置
|
|
|
|
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {});
|
|
|
|
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {});
|
|
|
|
|
|
|
|
if (codeChanged) {
|
|
|
|
symbols[name] = newCode;
|
|
|
|
symbols[name] = newCode;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (categoryChanged) {
|
|
|
|
|
|
|
|
SYMBOL_CATEGORIES[name] = newCategory;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const fullConfig = {
|
|
|
|
const fullConfig = {
|
|
|
|
futures: currentConfig.futures || {},
|
|
|
|
futures: currentConfig.futures || {},
|
|
|
|
@ -1811,17 +1868,27 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
if (res.ok) {
|
|
|
|
showToast('合约代码修改成功', 'success');
|
|
|
|
const changes = [];
|
|
|
|
input.dataset.original = newCode;
|
|
|
|
if (codeChanged) changes.push('合约代码');
|
|
|
|
|
|
|
|
if (categoryChanged) changes.push('品类');
|
|
|
|
|
|
|
|
showToast(`${changes.join('、')}修改成功`, 'success');
|
|
|
|
|
|
|
|
if (codeChanged) codeInput.dataset.original = newCode;
|
|
|
|
|
|
|
|
if (categoryChanged) categorySelect.dataset.original = newCategory;
|
|
|
|
|
|
|
|
// 如果品类修改了,需要重新渲染以更新 tab 分组
|
|
|
|
|
|
|
|
if (categoryChanged) {
|
|
|
|
|
|
|
|
loadConfig();
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
const data = await res.json();
|
|
|
|
const data = await res.json();
|
|
|
|
showToast(data.detail || '修改失败', 'error');
|
|
|
|
showToast(data.detail || '修改失败', 'error');
|
|
|
|
// 恢复原始值
|
|
|
|
// 恢复原始值
|
|
|
|
input.value = originalCode;
|
|
|
|
codeInput.value = originalCode;
|
|
|
|
|
|
|
|
categorySelect.value = originalCategory;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
showToast(`修改失败: ${e.message}`, 'error');
|
|
|
|
showToast(`修改失败: ${e.message}`, 'error');
|
|
|
|
input.value = originalCode;
|
|
|
|
codeInput.value = originalCode;
|
|
|
|
|
|
|
|
categorySelect.value = originalCategory;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -3381,8 +3448,22 @@
|
|
|
|
document.getElementById('batchExportEndTime').value = today;
|
|
|
|
document.getElementById('batchExportEndTime').value = today;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化品类下拉框
|
|
|
|
|
|
|
|
function initCategorySelect() {
|
|
|
|
|
|
|
|
const select = document.getElementById('newSymbolCategory');
|
|
|
|
|
|
|
|
if (!select) return;
|
|
|
|
|
|
|
|
select.innerHTML = '<option value="">请选择品类</option>';
|
|
|
|
|
|
|
|
CATEGORY_ORDER.forEach(category => {
|
|
|
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
|
|
|
option.value = category;
|
|
|
|
|
|
|
|
option.textContent = category;
|
|
|
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
// 初始化
|
|
|
|
initDefaultDate();
|
|
|
|
initDefaultDate();
|
|
|
|
|
|
|
|
initCategorySelect();
|
|
|
|
loadConfig();
|
|
|
|
loadConfig();
|
|
|
|
loadTasks();
|
|
|
|
loadTasks();
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|