Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2025-12-29 03:06:49 +08:00
commit f43312a858
292 changed files with 103739 additions and 0 deletions
@@ -0,0 +1,539 @@
<template>
<div class="ai-decision-records">
<a-spin :spinning="loading">
<a-table
:columns="columns"
:data-source="decisions"
:pagination="paginationConfig"
:scroll="{ x: 'max-content' }"
row-key="id"
size="small"
>
<template slot="reasoning" slot-scope="text">
<a-tooltip :title="text">
<div class="reasoning-text">{{ text }}</div>
</a-tooltip>
</template>
<template slot="decisions" slot-scope="decisionList">
<a-tag
v-for="(d, index) in decisionList"
:key="index"
:color="getActionColor(d.action)"
style="margin-right: 4px;"
>
{{ getActionText(d.action) }}: {{ d.symbol }}
</a-tag>
</template>
<template slot="confidence" slot-scope="confidence">
<a-progress
:percent="Math.round(confidence * 100)"
:status="confidence > 0.7 ? 'success' : confidence > 0.4 ? 'active' : 'exception'"
:show-info="true"
size="small"
/>
</template>
</a-table>
</a-spin>
</div>
</template>
<script>
import { getAIDecisions } from '@/api/ai-trading'
export default {
name: 'AIDecisionRecords',
props: {
strategyId: {
type: Number,
required: true
}
},
data () {
return {
loading: false,
decisions: [],
pagination: {
current: 1,
pageSize: 10,
total: 0
}
}
},
computed: {
paginationConfig () {
return {
...this.pagination,
showTotal: (total) => this.$t('ai-trading-assistant.table.totalRecords', { total }),
onChange: this.handlePageChange,
onShowSizeChange: this.handlePageSizeChange
}
},
columns () {
return [
{
title: this.$t('ai-trading-assistant.table.time'),
dataIndex: 'created_at',
key: 'created_at',
width: 180,
customRender: (text) => {
return new Date(text * 1000).toLocaleString('zh-CN')
}
},
{
title: this.$t('ai-trading-assistant.table.reasoning'),
dataIndex: 'decision.reasoning',
key: 'reasoning',
width: 300,
scopedSlots: { customRender: 'reasoning' }
},
{
title: this.$t('ai-trading-assistant.table.decisions'),
dataIndex: 'decision.decisions',
key: 'decisions',
width: 200,
scopedSlots: { customRender: 'decisions' }
},
{
title: this.$t('ai-trading-assistant.table.riskAssessment'),
dataIndex: 'decision.risk_assessment',
key: 'risk_assessment',
width: 100
},
{
title: this.$t('ai-trading-assistant.table.confidence'),
dataIndex: 'decision.confidence',
key: 'confidence',
width: 150,
scopedSlots: { customRender: 'confidence' }
}
]
}
},
watch: {
strategyId: {
immediate: true,
handler () {
this.loadDecisions()
}
}
},
methods: {
async loadDecisions () {
if (!this.strategyId) return
this.loading = true
try {
const res = await getAIDecisions(this.strategyId, {
page: this.pagination.current,
limit: this.pagination.pageSize
})
if (res.code === 1) {
// 处理返回的数据格式
const data = res.data
let decisions = []
let total = 0
// 兼容新旧格式
if (data && typeof data === 'object') {
if (Array.isArray(data)) {
// 旧格式:直接是数组
decisions = data
total = data.length
} else if (data.decisions) {
// 新格式:包含 decisions 和 total
decisions = data.decisions || []
total = data.total || 0
}
}
this.decisions = decisions.map(item => ({
...item,
decision: item.decision || {}
}))
this.pagination.total = total
}
} catch (error) {
this.$message.error(this.$t('ai-trading-assistant.messages.loadDecisionsFailed'))
} finally {
this.loading = false
}
},
getActionColor (action) {
const colorMap = {
buy: 'green',
sell: 'red',
short: 'orange',
close_short: 'blue',
hold: 'cyan'
}
return colorMap[action] || 'default'
},
getActionText (action) {
const actionMap = {
buy: this.$t('ai-trading-assistant.table.buy'),
sell: this.$t('ai-trading-assistant.table.sell'),
short: this.$t('ai-trading-assistant.table.short'),
close_short: this.$t('ai-trading-assistant.table.closeShort'),
hold: this.$t('ai-trading-assistant.table.hold')
}
return actionMap[action] || action
},
handlePageChange (page, pageSize) {
this.pagination.current = page
this.pagination.pageSize = pageSize
this.loadDecisions()
},
handlePageSizeChange (current, size) {
this.pagination.current = 1
this.pagination.pageSize = size
this.loadDecisions()
}
}
}
</script>
<style lang="less" scoped>
.ai-decision-records {
width: 100%;
min-height: 300px;
overflow-x: auto;
overflow-y: visible;
-webkit-overflow-scrolling: touch;
color: var(--text-color, #1f1f1f);
.reasoning-text {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-color, #1f1f1f);
}
// 确保 hold 标签(cyan)在所有主题下都有足够的对比度
/deep/ .ant-tag {
&.ant-tag-cyan {
background-color: #13c2c2 !important;
border-color: #13c2c2 !important;
color: #fff !important;
}
}
// 表格容器横向滚动
/deep/ .ant-table-wrapper {
min-width: 100%;
}
// 适配主题色
/deep/ .ant-table {
background: var(--table-row-bg, #fff);
color: var(--text-color, #1f1f1f);
.ant-table-thead > tr > th {
background: var(--table-header-bg, #fafafa);
color: var(--text-color, #1f1f1f);
border-bottom-color: var(--table-border-color, #e8e8e8);
.ant-table-column-title {
color: var(--text-color, #1f1f1f);
}
}
.ant-table-tbody > tr:hover > td {
background-color: var(--hover-bg-color, #fafafa);
}
.ant-table-tbody > tr > td {
background: var(--table-row-bg, #fff);
color: var(--text-color, #1f1f1f);
border-bottom-color: var(--table-border-color, #e8e8e8);
}
.ant-table-body {
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: var(--table-row-bg, #fff);
}
&::-webkit-scrollbar-thumb {
background: #bfbfbf;
border-radius: 4px;
&:hover {
background: #a6a6a6;
}
}
}
}
// 进度条使用主题色
/deep/ .ant-progress {
.ant-progress-bg {
background-color: var(--primary-color, #1890ff);
}
&.ant-progress-status-success .ant-progress-bg {
background-color: #52c41a;
}
&.ant-progress-status-exception .ant-progress-bg {
background-color: #ff4d4f;
}
}
// 自定义滚动条样式
&::-webkit-scrollbar {
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
&:hover {
background: #555;
}
}
// 分页器主题适配
/deep/ .ant-pagination {
color: var(--text-color, #1f1f1f);
.ant-pagination-total-text {
color: var(--text-color-secondary, #8c8c8c);
}
.ant-pagination-item {
background: var(--table-row-bg, #fff);
border-color: var(--table-border-color, #e8e8e8);
a {
color: var(--text-color, #1f1f1f);
}
&:hover {
border-color: var(--primary-color, #1890ff);
a {
color: var(--primary-color, #1890ff);
}
}
&.ant-pagination-item-active {
background: var(--primary-color, #1890ff);
border-color: var(--primary-color, #1890ff);
a {
color: #fff;
}
}
}
.ant-pagination-prev,
.ant-pagination-next {
.ant-pagination-item-link {
background: var(--table-row-bg, #fff);
border-color: var(--table-border-color, #e8e8e8);
color: var(--text-color, #1f1f1f);
&:hover {
border-color: var(--primary-color, #1890ff);
color: var(--primary-color, #1890ff);
}
}
}
.ant-pagination-options {
.ant-select-selector {
background: var(--table-row-bg, #fff);
border-color: var(--table-border-color, #e8e8e8);
color: var(--text-color, #1f1f1f);
}
}
}
}
</style>
<style lang="less">
// 全局样式:暗色主题下的标签优化
.trading-assistant.theme-dark {
.ai-decision-records {
/deep/ .ant-table {
background: var(--table-row-bg, #1e222d);
color: var(--text-color, #d1d4dc);
.ant-table-thead > tr > th {
background: var(--table-header-bg, #252932) !important;
color: var(--text-color, #d1d4dc) !important;
border-bottom-color: var(--table-border-color, #2a2e39);
.ant-table-column-title {
color: var(--text-color, #d1d4dc) !important;
}
}
.ant-table-tbody > tr > td {
background: var(--table-row-bg, #1e222d);
color: var(--text-color, #d1d4dc);
border-bottom-color: var(--table-border-color, #2a2e39);
}
.ant-table-tbody > tr:hover > td {
background-color: var(--table-hover-bg, #252932);
}
.ant-table-body {
&::-webkit-scrollbar-track {
background: var(--table-row-bg, #1e222d);
}
&::-webkit-scrollbar-thumb {
background: #555;
&:hover {
background: #777;
}
}
}
}
.reasoning-text {
color: var(--text-color, #d1d4dc);
}
/deep/ .ant-tag {
// hold 标签使用青色,确保在暗色背景下可见
&.ant-tag-cyan {
background-color: #13c2c2 !important;
border-color: #13c2c2 !important;
color: #fff !important;
}
// 其他标签在暗色主题下的优化
&.ant-tag-green {
background-color: #52c41a !important;
border-color: #52c41a !important;
color: #fff !important;
}
&.ant-tag-red {
background-color: #ff4d4f !important;
border-color: #ff4d4f !important;
color: #fff !important;
}
&.ant-tag-orange {
background-color: #fa8c16 !important;
border-color: #fa8c16 !important;
color: #fff !important;
}
&.ant-tag-blue {
background-color: var(--primary-color, #1890ff) !important;
border-color: var(--primary-color, #1890ff) !important;
color: #fff !important;
}
// default 标签在暗色主题下的优化
&.ant-tag-default {
background-color: #434343 !important;
border-color: #434343 !important;
color: #fff !important;
}
}
// 进度条在暗色主题下使用主题色
/deep/ .ant-progress {
.ant-progress-bg {
background-color: var(--primary-color, #1890ff);
}
&.ant-progress-status-success .ant-progress-bg {
background-color: #52c41a;
}
&.ant-progress-status-exception .ant-progress-bg {
background-color: #ff4d4f;
}
}
// 暗色主题下的滚动条样式
&::-webkit-scrollbar-track {
background: #2a2e39;
}
&::-webkit-scrollbar-thumb {
background: #555;
&:hover {
background: #777;
}
}
// 暗色主题下的分页器
/deep/ .ant-pagination {
color: var(--text-color, #d1d4dc);
.ant-pagination-total-text {
color: var(--text-color-secondary, #868993);
}
.ant-pagination-item {
background: var(--table-row-bg, #1e222d);
border-color: var(--table-border-color, #2a2e39);
a {
color: var(--text-color, #d1d4dc);
}
&:hover {
border-color: var(--primary-color, #1890ff);
a {
color: var(--primary-color, #1890ff);
}
}
&.ant-pagination-item-active {
background: var(--primary-color, #1890ff);
border-color: var(--primary-color, #1890ff);
a {
color: #fff;
}
}
}
.ant-pagination-prev,
.ant-pagination-next {
.ant-pagination-item-link {
background: var(--table-row-bg, #1e222d);
border-color: var(--table-border-color, #2a2e39);
color: var(--text-color, #d1d4dc);
&:hover {
border-color: var(--primary-color, #1890ff);
color: var(--primary-color, #1890ff);
}
}
}
.ant-pagination-options {
.ant-select-selector {
background: var(--table-row-bg, #1e222d);
border-color: var(--table-border-color, #2a2e39);
color: var(--text-color, #d1d4dc);
}
}
}
}
}
</style>
@@ -0,0 +1,733 @@
<template>
<div class="position-records">
<div v-if="positions.length === 0 && !loading" class="empty-state">
<a-empty :description="$t('trading-assistant.table.noPositions')" />
</div>
<a-table
v-else
:columns="columns"
:data-source="positions"
:loading="loading"
:pagination="false"
size="small"
rowKey="id"
:scroll="{ x: 800 }"
>
<template slot="symbol" slot-scope="text, record">
<strong>{{ record.symbol || text }}</strong>
</template>
<template slot="side" slot-scope="text, record">
<a-tag :color="(record.side || text) === 'long' ? 'green' : 'red'">
{{ (record.side || text) === 'long' ? $t('trading-assistant.table.long') : $t('trading-assistant.table.short') }}
</a-tag>
</template>
<template slot="entryPrice" slot-scope="text, record">
${{ parseFloat(record.entry_price || text || 0).toFixed(4) }}
</template>
<template slot="currentPrice" slot-scope="text, record">
${{ parseFloat(record.current_price || text || 0).toFixed(4) }}
</template>
<template slot="size" slot-scope="text, record">
{{ parseFloat(record.size || text || 0).toFixed(4) }}
</template>
<template slot="unrealizedPnl" slot-scope="text, record">
<span :class="{ 'profit': parseFloat(record.unrealized_pnl || text || 0) > 0, 'loss': parseFloat(record.unrealized_pnl || text || 0) < 0 }">
${{ parseFloat(record.unrealized_pnl || text || 0).toFixed(2) }}
</span>
</template>
<template slot="pnlPercent" slot-scope="text, record">
<span :class="{ 'profit': parseFloat(record.pnl_percent || text || 0) > 0, 'loss': parseFloat(record.pnl_percent || text || 0) < 0 }">
{{ parseFloat(record.pnl_percent || text || 0).toFixed(2) }}%
</span>
</template>
</a-table>
</div>
</template>
<script>
import { getStrategyPositions } from '@/api/strategy'
export default {
name: 'PositionRecords',
props: {
strategyId: {
type: Number,
required: true
},
marketType: {
type: String,
default: 'swap'
},
leverage: {
type: [Number, String],
default: 1
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
positions: []
}
},
computed: {
columns () {
return [
{
title: this.$t('trading-assistant.table.symbol'),
dataIndex: 'symbol',
key: 'symbol',
width: 120,
scopedSlots: { customRender: 'symbol' }
},
{
title: this.$t('trading-assistant.table.side'),
dataIndex: 'side',
key: 'side',
width: 80,
scopedSlots: { customRender: 'side' }
},
{
title: this.$t('trading-assistant.table.size'),
dataIndex: 'size',
key: 'size',
width: 120,
scopedSlots: { customRender: 'size' }
},
{
title: this.$t('trading-assistant.table.entryPrice'),
dataIndex: 'entry_price',
key: 'entry_price',
width: 120,
scopedSlots: { customRender: 'entryPrice' }
},
{
title: this.$t('trading-assistant.table.currentPrice'),
dataIndex: 'current_price',
key: 'current_price',
width: 120,
scopedSlots: { customRender: 'currentPrice' }
},
{
title: this.$t('trading-assistant.table.unrealizedPnl'),
dataIndex: 'unrealized_pnl',
key: 'unrealized_pnl',
width: 120,
scopedSlots: { customRender: 'unrealizedPnl' }
},
{
title: this.$t('trading-assistant.table.pnlPercent'),
dataIndex: 'pnl_percent',
key: 'pnl_percent',
width: 100,
scopedSlots: { customRender: 'pnlPercent' }
}
]
}
},
watch: {
strategyId: {
handler (val) {
if (val) {
this.loadPositions()
// 每5秒刷新一次持仓
this.startPolling()
} else {
this.stopPolling()
}
},
immediate: true
}
},
beforeDestroy () {
this.stopPolling()
},
methods: {
async loadPositions () {
if (!this.strategyId) return
try {
const res = await getStrategyPositions(this.strategyId)
if (res.code === 1) {
// 确保数据格式正确,处理可能的字段名不一致
const rawPositions = res.data.positions || []
this.positions = rawPositions.map((position, index) => {
const mt = String(this.marketType || 'swap').toLowerCase()
let lev = parseFloat(this.leverage)
if (!isFinite(lev) || lev <= 0) lev = 1
if (mt === 'spot') lev = 1
const entryPrice = parseFloat(position.entry_price || position.entryPrice || '0') || 0
const size = parseFloat(position.size || '0') || 0
const pnl = parseFloat(position.unrealized_pnl || position.unrealizedPnl || '0') || 0
let pnlPercent = parseFloat(position.pnl_percent || position.pnlPercent || '0') || 0
// Prefer margin-based pnl% (pnl / (notional / leverage)).
// If backend already returns pnl_percent, we still recompute from pnl/entry/size to keep it consistent.
if (entryPrice > 0 && size > 0) {
pnlPercent = (pnl / (entryPrice * size)) * 100 * lev
} else if (mt !== 'spot') {
pnlPercent = pnlPercent * lev
}
const mapped = {
id: position.id || index,
symbol: position.symbol || '',
side: position.side || 'long',
size: position.size || '0',
entry_price: position.entry_price || position.entryPrice || '0',
current_price: position.current_price || position.currentPrice || '0',
unrealized_pnl: position.unrealized_pnl || position.unrealizedPnl || '0',
pnl_percent: pnlPercent,
updated_at: position.updated_at || position.updatedAt || ''
}
return mapped
})
} else {
// 不显示错误,可能策略还没有持仓
this.positions = []
}
} catch (error) {
this.positions = []
}
},
startPolling () {
this.stopPolling()
this.pollingTimer = setInterval(() => {
this.loadPositions()
}, 5000)
},
stopPolling () {
if (this.pollingTimer) {
clearInterval(this.pollingTimer)
this.pollingTimer = null
}
}
}
}
</script>
<style lang="less" scoped>
.position-records {
width: 100%;
min-height: 300px;
padding: 0;
.empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
padding: 40px 0;
}
::v-deep .ant-table {
font-size: 13px;
color: #333;
}
// 自定义细滚动条
::v-deep .ant-table-body {
overflow-x: auto;
scrollbar-width: thin; // Firefox - 细滚动条
scrollbar-color: rgba(0, 0, 0, 0.2) transparent; // Firefox - 滑块颜色和轨道颜色
&::-webkit-scrollbar {
height: 6px; // 横向滚动条高度
width: 6px; // 纵向滚动条宽度
}
&::-webkit-scrollbar-track {
background: transparent; // 轨道背景透明
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2); // 滑块颜色
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3); // 悬停时颜色
}
}
}
// 表格容器的滚动条样式
::v-deep .ant-table-container {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
}
// 所有可能的表格滚动容器的滚动条样式
::v-deep .ant-table-content,
::v-deep .ant-table-wrapper {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
}
::v-deep .ant-table-thead > tr > th {
background: #fafafa;
font-weight: 600;
color: #333;
border-bottom: 1px solid #e8e8e8;
}
::v-deep .ant-table-tbody > tr > td {
padding: 12px 16px;
color: #333;
border-bottom: 1px solid #e8e8e8;
}
// 暗黑主题适配
&.theme-dark,
.theme-dark & {
::v-deep .ant-table {
background: #1e222d !important;
color: #d1d4dc !important;
}
::v-deep .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
font-weight: 600;
}
::v-deep .ant-table-tbody > tr > td {
background: #1e222d !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
::v-deep .ant-table-tbody > tr:hover > td {
background: #2a2e39 !important;
}
::v-deep .ant-table-tbody > tr > td strong {
color: #d1d4dc !important;
}
}
::v-deep .ant-table-tbody > tr:hover > td {
background: #fafafa;
}
::v-deep .ant-empty {
margin: 40px 0;
.ant-empty-description {
color: #8c8c8c;
}
}
.profit {
color: #52c41a;
font-weight: 600;
}
.loss {
color: #ff4d4f;
font-weight: 600;
}
// 移动端适配
@media (max-width: 768px) {
min-height: 200px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
// 移动端也使用细滚动条
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
&::-webkit-scrollbar {
height: 4px;
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 2px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 2px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
.empty-state {
min-height: 150px;
padding: 20px 0;
}
::v-deep .ant-table {
font-size: 12px;
min-width: 700px; // 确保表格最小宽度,触发横向滚动
}
// 移动端也使用细滚动条
::v-deep .ant-table-body,
::v-deep .ant-table-container,
::v-deep .ant-table-wrapper {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
&::-webkit-scrollbar {
height: 4px;
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 2px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 2px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
}
::v-deep .ant-table-thead > tr > th {
padding: 8px 10px;
font-size: 11px;
white-space: nowrap;
}
::v-deep .ant-table-tbody > tr > td {
padding: 8px 10px;
font-size: 11px;
white-space: nowrap;
}
::v-deep .ant-empty {
margin: 20px 0;
}
}
@media (max-width: 480px) {
::v-deep .ant-table {
font-size: 11px;
min-width: 600px;
}
::v-deep .ant-table-thead > tr > th {
padding: 6px 8px;
font-size: 10px;
}
::v-deep .ant-table-tbody > tr > td {
padding: 6px 8px;
font-size: 10px;
}
.profit,
.loss {
font-size: 11px;
}
}
}
</style>
<style lang="less">
// 暗黑主题适配 - 使用更高优先级的选择器覆盖 scoped 样式
// 必须使用完整的 scoped 选择器路径来覆盖
.theme-dark .position-records .ant-table-tbody > tr > td,
.theme-dark .position-records[data-v] .ant-table-tbody > tr > td,
body.dark .position-records .ant-table-tbody > tr > td,
body.realdark .position-records .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .position-records .ant-table-thead > tr > th,
.theme-dark .position-records[data-v] .ant-table-thead > tr > th,
body.dark .position-records .ant-table-thead > tr > th,
body.realdark .position-records .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
font-weight: 600 !important;
}
.theme-dark .position-records .ant-table,
.theme-dark .position-records[data-v] .ant-table,
body.dark .position-records .ant-table,
body.realdark .position-records .ant-table {
background: #1e222d !important;
color: #d1d4dc !important;
}
.theme-dark .position-records .ant-table-tbody > tr > td *,
.theme-dark .position-records[data-v] .ant-table-tbody > tr > td *,
body.dark .position-records .ant-table-tbody > tr > td *,
body.realdark .position-records .ant-table-tbody > tr > td * {
color: #d1d4dc !important;
}
.theme-dark .position-records .ant-table-tbody > tr:hover > td,
.theme-dark .position-records[data-v] .ant-table-tbody > tr:hover > td,
body.dark .position-records .ant-table-tbody > tr:hover > td,
body.realdark .position-records .ant-table-tbody > tr:hover > td {
background: #2a2e39 !important;
}
// 确保表头文字可见
.theme-dark .position-records .ant-table-thead > tr > th,
.theme-dark .position-records[data-v] .ant-table-thead > tr > th,
body.dark .position-records .ant-table-thead > tr > th,
body.realdark .position-records .ant-table-thead > tr > th {
.ant-table-column-title {
color: #d1d4dc !important;
}
}
// 通用选择器作为后备
.theme-dark .position-records[data-v] .ant-table-tbody > tr > td,
.theme-dark [class*="position-records"][data-v] .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .position-records[data-v] .ant-table-thead > tr > th,
.theme-dark [class*="position-records"][data-v] .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
</style>
<style lang="less">
// 暗黑主题适配 - 使用最高优先级的选择器覆盖 scoped 样式
// 关键:必须使用与 scoped 样式完全相同的选择器结构,加上 theme-dark 前缀
// 使用属性选择器的精确匹配来覆盖 scoped 样式
// 方法1:精确匹配 data-v-6c1eb557
.theme-dark .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
.theme-dark [data-v-6c1eb557].position-records .ant-table-tbody > tr > td,
.theme-dark [data-v-6c1eb557] .position-records .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
// 方法2:使用属性选择器前缀匹配(更通用)
.theme-dark [data-v-6c1eb557] .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
.theme-dark [data-v-6c1eb557].position-records .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .position-records[data-v-6c1eb557] .ant-table,
.theme-dark [data-v-6c1eb557].position-records .ant-table {
background: #1e222d !important;
color: #d1d4dc !important;
}
.theme-dark .position-records[data-v-6c1eb557] .ant-table-tbody > tr:hover > td,
.theme-dark [data-v-6c1eb557].position-records .ant-table-tbody > tr:hover > td {
background: #2a2e39 !important;
}
// body.dark 和 body.realdark 支持
body.dark .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
body.dark [data-v-6c1eb557].position-records .ant-table-tbody > tr > td,
body.dark [data-v-6c1eb557] .position-records .ant-table-tbody > tr > td,
body.realdark .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
body.realdark [data-v-6c1eb557].position-records .ant-table-tbody > tr > td,
body.realdark [data-v-6c1eb557] .position-records .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
// 方法2:使用属性选择器前缀匹配(更通用)
body.dark [data-v-6c1eb557] .ant-table-tbody > tr > td,
body.realdark [data-v-6c1eb557] .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
body.dark .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
body.dark [data-v-6c1eb557].position-records .ant-table-thead > tr > th,
body.dark [data-v-6c1eb557] .position-records .ant-table-thead > tr > th,
body.realdark .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
body.realdark [data-v-6c1eb557].position-records .ant-table-thead > tr > th,
body.realdark [data-v-6c1eb557] .position-records .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
// 方法2:使用属性选择器前缀匹配(更通用)
body.dark [data-v-6c1eb557] .ant-table-thead > tr > th,
body.realdark [data-v-6c1eb557] .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
// 通用后备选择器(如果 data-v 值变化)
.theme-dark .position-records[data-v] .ant-table-tbody > tr > td,
body.dark .position-records[data-v] .ant-table-tbody > tr > td,
body.realdark .position-records[data-v] .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .position-records[data-v] .ant-table-thead > tr > th,
body.dark .position-records[data-v] .ant-table-thead > tr > th,
body.realdark .position-records[data-v] .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
// 其他样式
.theme-dark .position-records[data-v-6c1eb557] .ant-empty .ant-empty-description,
body.dark .position-records[data-v-6c1eb557] .ant-empty .ant-empty-description,
body.realdark .position-records[data-v-6c1eb557] .ant-empty .ant-empty-description {
color: #868993 !important;
}
.theme-dark .position-records[data-v-6c1eb557] .profit,
body.dark .position-records[data-v-6c1eb557] .profit,
body.realdark .position-records[data-v-6c1eb557] .profit {
color: #52c41a !important;
}
.theme-dark .position-records[data-v-6c1eb557] .loss,
body.dark .position-records[data-v-6c1eb557] .loss,
body.realdark .position-records[data-v-6c1eb557] .loss {
color: #ff4d4f !important;
}
</style>
<style lang="less">
// 最终覆盖方案:使用更长的选择器链确保最高优先级
// 直接匹配 scoped 样式生成的完整选择器路径
.theme-dark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
.theme-dark .trading-assistant [data-v-6c1eb557].position-records .ant-table-tbody > tr > td,
body.dark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
body.dark .trading-assistant [data-v-6c1eb557].position-records .ant-table-tbody > tr > td,
body.realdark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-tbody > tr > td,
body.realdark .trading-assistant [data-v-6c1eb557].position-records .ant-table-tbody > tr > td {
color: #d1d4dc !important;
background: #1e222d !important;
border-bottom-color: #363c4e !important;
}
.theme-dark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
.theme-dark .trading-assistant [data-v-6c1eb557].position-records .ant-table-thead > tr > th,
body.dark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
body.dark .trading-assistant [data-v-6c1eb557].position-records .ant-table-thead > tr > th,
body.realdark .trading-assistant .position-records[data-v-6c1eb557] .ant-table-thead > tr > th,
body.realdark .trading-assistant [data-v-6c1eb557].position-records .ant-table-thead > tr > th {
background: #2a2e39 !important;
color: #d1d4dc !important;
border-bottom-color: #363c4e !important;
}
// 暗黑主题滚动条样式
.theme-dark .position-records[data-v-6c1eb557] .ant-table-body,
.theme-dark .position-records[data-v-6c1eb557] .ant-table-container,
.theme-dark .position-records[data-v-6c1eb557] .ant-table-content,
.theme-dark .position-records[data-v-6c1eb557] .ant-table-wrapper,
body.dark .position-records[data-v-6c1eb557] .ant-table-body,
body.dark .position-records[data-v-6c1eb557] .ant-table-container,
body.dark .position-records[data-v-6c1eb557] .ant-table-content,
body.dark .position-records[data-v-6c1eb557] .ant-table-wrapper,
body.realdark .position-records[data-v-6c1eb557] .ant-table-body,
body.realdark .position-records[data-v-6c1eb557] .ant-table-container,
body.realdark .position-records[data-v-6c1eb557] .ant-table-content,
body.realdark .position-records[data-v-6c1eb557] .ant-table-wrapper {
scrollbar-width: thin;
scrollbar-color: rgba(209, 212, 220, 0.3) transparent;
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(209, 212, 220, 0.3);
border-radius: 3px;
&:hover {
background: rgba(209, 212, 220, 0.5);
}
}
}
// 通用后备选择器
.theme-dark .position-records[data-v] .ant-table-body,
.theme-dark .position-records[data-v] .ant-table-container,
.theme-dark .position-records[data-v] .ant-table-content,
.theme-dark .position-records[data-v] .ant-table-wrapper,
body.dark .position-records[data-v] .ant-table-body,
body.dark .position-records[data-v] .ant-table-container,
body.dark .position-records[data-v] .ant-table-content,
body.dark .position-records[data-v] .ant-table-wrapper,
body.realdark .position-records[data-v] .ant-table-body,
body.realdark .position-records[data-v] .ant-table-container,
body.realdark .position-records[data-v] .ant-table-content,
body.realdark .position-records[data-v] .ant-table-wrapper {
scrollbar-width: thin;
scrollbar-color: rgba(209, 212, 220, 0.3) transparent;
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(209, 212, 220, 0.3);
border-radius: 3px;
&:hover {
background: rgba(209, 212, 220, 0.5);
}
}
}
</style>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff