ai_trade_assistance/scripts/fix-encoding-1.mjs

453 lines
11 KiB
JavaScript

import { writeFileSync } from 'node:fs'
import { join } from 'node:path'
const root = 'e:/Gitea/ai_trade_assistance'
function w(rel, content) {
const path = join(root, rel)
writeFileSync(path, content, 'utf8')
console.log('ok', rel)
}
// Use unicode escapes so this repair script itself stays ASCII-safe on Windows shells.
const S = {
mai: '\u4e70',
mai2: '\u5356',
shezhi: '\u8bbe\u7f6e',
quxiao: '\u53d6\u6d88',
baocun: '\u4fdd\u5b58',
eci: '\u989d\u5916\u5173\u952e\u8bcd',
eciPh: '\u7528\u4e8e\u65b0\u95fb\u8fc7\u6ee4\u4e0e\u5206\u6790\u589e\u5f3a\uff0c\u591a\u4e2a\u8bcd\u7528\u9017\u53f7\u6216\u6362\u884c\u5206\u9694',
saved: '\u8bbe\u7f6e\u5df2\u4fdd\u5b58\u5230\u672c\u5730',
zongcc: '\u603b\u6301\u4ed3',
cjl: '\u6210\u4ea4\u91cf',
jingcc: '\u51c0\u6301\u4ed3',
placeholder: '\u5f53\u524d\u4e3a\u5360\u4f4d\u6570\u636e\uff08\u7ed3\u6784\u4e0e\u603b\u6301\u4ed3\u4e00\u81f4\uff09',
long20: '\u591a\u5355\u6301\u4ed3\u524d20\u540d',
short20: '\u7a7a\u5355\u6301\u4ed3\u524d20\u540d',
duodan: '\u591a\u5355',
kongdan: '\u7a7a\u5355',
mingci: '\u540d\u6b21',
hyjc: '\u4f1a\u5458\u7b80\u79f0',
zengjian: '\u589e\u51cf',
shuliang: '\u6570\u91cf',
kaipan: '\u5f00\u76d8',
zhangdie: '\u6da8\u8dcc',
junjia: '\u5747\u4ef7',
zuigao: '\u6700\u9ad8',
jiesuan: '\u7ed3\u7b97',
cje: '\u6210\u4ea4\u989d',
yi: '\u4ebf',
ccl: '\u6301\u4ed3\u91cf',
zhenfu: '\u632f\u5e45',
waipan: '\u5916\u76d8',
zuoshou: '\u6628\u6536',
neipan: '\u5185\u76d8',
zuidi: '\u6700\u4f4e',
zuojie: '\u6628\u7ed3',
fenshi: '\u5206\u65f6',
wuri: '\u4e94\u65e5',
riK: '\u65e5K',
zhouK: '\u5468K',
yueK: '\u6708K',
junjia2: '\u5747\u4ef7',
xindu: '\u7f6e\u4fe1\u5ea6',
yijian: '\u4e00\u952e\u83b7\u53d6\u5efa\u8bae',
shouqi: '\u6536\u8d77',
zhankai: '\u5c55\u5f00',
hint: '\u57fa\u4e8e\u884c\u60c5\u3001\u76d8\u53e3\u3001\u65b0\u95fb\u4e0e\u6301\u4ed3\u7684\u7efc\u5408\u5206\u6790\uff08\u5f53\u524d\u4e3a Mock\uff09',
warn: '\u8bf7\u5148\u5728\u8bbe\u7f6e\u4e2d\u914d\u7f6e DeepSeek API Key\uff08\u672c\u9636\u6bb5\u4ecd\u8fd4\u56de Mock \u5efa\u8bae\uff09',
qita: '\u5176\u4ed6',
shuliang2: '\u6570\u91cf',
zhanbi: '\u5360\u6bd4',
}
w(
'src/components/quote/OrderBook.vue',
`<template>
<div class="order-book card">
<div class="sentiment">
<div class="buy" :style="{ flex: quote.buyRatio }">${S.mai} {{ quote.buyRatio }}%</div>
<div class="sell" :style="{ flex: quote.sellRatio }">${S.mai2} {{ quote.sellRatio }}%</div>
</div>
<div class="levels">
<div
v-for="ask in quote.orderBook.asks"
:key="'a' + ask.level"
class="level ask"
>
<span class="side">${S.mai2}{{ ask.level }}</span>
<span class="price price-down">{{ ask.price.toFixed(2) }}</span>
<span class="vol">{{ ask.volume }}</span>
</div>
<div class="divider" />
<div v-for="bid in quote.orderBook.bids" :key="'b' + bid.level" class="level bid">
<span class="side">${S.mai}{{ bid.level }}</span>
<span class="price price-up">{{ bid.price.toFixed(2) }}</span>
<span class="vol">{{ bid.volume }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { QuoteData } from '../../types'
defineProps<{
quote: QuoteData
}>()
</script>
<style scoped>
.order-book {
padding: 12px;
}
.sentiment {
display: flex;
height: 22px;
border-radius: 4px;
overflow: hidden;
font-size: 12px;
margin-bottom: 10px;
}
.buy,
.sell {
display: flex;
align-items: center;
justify-content: center;
color: #fff;
min-width: 48px;
}
.buy {
background: var(--up);
}
.sell {
background: var(--down);
}
.level {
display: grid;
grid-template-columns: 40px 1fr 48px;
font-size: 13px;
line-height: 1.9;
}
.side {
color: var(--text-secondary);
}
.price,
.vol {
font-variant-numeric: tabular-nums;
text-align: right;
}
.divider {
height: 1px;
background: var(--border);
margin: 4px 0;
}
</style>
`,
)
w(
'src/components/header/SettingsDialog.vue',
`<template>
<el-dialog
:model-value="modelValue"
title="AI ${S.shezhi}"
width="480px"
destroy-on-close
@update:model-value="emit('update:modelValue', $event)"
>
<el-form label-position="top">
<el-form-item label="DeepSeek API Key">
<el-input
v-model="localKey"
type="password"
show-password
placeholder="sk-..."
autocomplete="off"
/>
</el-form-item>
<el-form-item label="${S.eci}">
<el-input
v-model="localKeywords"
type="textarea"
:rows="3"
placeholder="${S.eciPh}"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="emit('update:modelValue', false)">${S.quxiao}</el-button>
<el-button type="primary" @click="save">${S.baocun}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { useSettingsStore } from '../../stores/settings'
const props = withDefaults(
defineProps<{
modelValue?: boolean
}>(),
{ modelValue: false },
)
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
const settings = useSettingsStore()
const localKey = ref('')
const localKeywords = ref('')
watch(
() => props.modelValue,
(open) => {
if (open) {
localKey.value = settings.apiKey
localKeywords.value = settings.keywords
}
},
)
function save() {
settings.apiKey = localKey.value.trim()
settings.keywords = localKeywords.value.trim()
ElMessage.success('${S.saved}')
emit('update:modelValue', false)
}
</script>
`,
)
w(
'src/components/position/PositionPanel.vue',
`<template>
<section class="position-panel card">
<el-tabs v-model="tab">
<el-tab-pane label="${S.zongcc}" name="total" />
<el-tab-pane label="${S.cjl}" name="volume" />
<el-tab-pane label="${S.jingcc}" name="net" />
</el-tabs>
<div v-if="tab !== 'total'" class="placeholder-tip">${S.placeholder}</div>
<div class="cols">
<div class="col">
<h3 class="long-title">${S.long20}</h3>
<PositionDonut :list="data.long" />
<PositionTable :list="data.long" qty-label="${S.duodan}" />
</div>
<div class="col">
<h3 class="short-title">${S.short20}</h3>
<PositionDonut :list="data.short" />
<PositionTable :list="data.short" qty-label="${S.kongdan}" />
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { PositionsData } from '../../types'
import PositionDonut from './PositionDonut.vue'
import PositionTable from './PositionTable.vue'
defineProps<{
data: PositionsData
}>()
const tab = ref('total')
</script>
<style scoped>
.position-panel {
padding: 12px 16px 16px;
margin-top: 16px;
}
.placeholder-tip {
font-size: 12px;
color: var(--text-secondary);
margin: -4px 0 8px;
}
.cols {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.long-title {
margin: 0 0 8px;
color: var(--long);
font-size: 14px;
}
.short-title {
margin: 0 0 8px;
color: var(--short);
font-size: 14px;
}
@media (max-width: 960px) {
.cols {
grid-template-columns: 1fr;
}
}
</style>
`,
)
w(
'src/components/position/PositionTable.vue',
`<template>
<el-table :data="list" size="small" stripe class="pos-table" max-height="320">
<el-table-column prop="rank" label="${S.mingci}" width="56" />
<el-table-column prop="name" label="${S.hyjc}" min-width="110" />
<el-table-column :prop="String(qtyKey)" :label="qtyLabel" min-width="90" />
<el-table-column label="${S.zengjian}" min-width="80">
<template #default="{ row }">
<span :class="row.change >= 0 ? 'price-up' : 'chg-down'">
{{ row.change > 0 ? '+' : '' }}{{ row.change }}
</span>
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import type { PositionRow } from '../../types'
withDefaults(
defineProps<{
list: PositionRow[]
qtyKey?: keyof PositionRow
qtyLabel?: string
}>(),
{
qtyKey: 'qty',
qtyLabel: '${S.shuliang}',
},
)
</script>
<style scoped>
.pos-table {
width: 100%;
}
.chg-down {
color: var(--short);
}
</style>
`,
)
w(
'src/components/quote/QuoteStats.vue',
`<template>
<div class="quote-stats">
<div class="col" v-for="(col, i) in columns" :key="i">
<div class="row" v-for="item in col" :key="item.label">
<span class="label">{{ item.label }}</span>
<span class="value" :class="item.className">{{ item.value }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { QuoteData } from '../../types'
interface StatCell {
label: string
value: string
className?: string
}
const props = defineProps<{
quote: QuoteData
}>()
const columns = computed((): StatCell[][] => {
const q = props.quote
const chgClass = q.change >= 0 ? 'price-up' : 'price-down'
return [
[
{ label: '${S.kaipan}', value: q.open.toFixed(2) },
{ label: '${S.zhangdie}', value: \`\${q.change.toFixed(2)}\`, className: chgClass },
{ label: '${S.junjia}', value: q.avg.toFixed(2) },
],
[
{ label: '${S.zuigao}', value: q.high.toFixed(2), className: 'price-up' },
{ label: '${S.jiesuan}', value: q.settlement.toFixed(2) },
{ label: '${S.cje}', value: \`\${q.amount}${S.yi}\` },
],
[
{ label: '${S.ccl}', value: q.openInterest.toLocaleString() },
{ label: '${S.zhenfu}', value: \`\${q.amplitude}%\` },
{ label: '${S.waipan}', value: q.outerVol.toLocaleString(), className: 'price-up' },
],
[
{ label: '${S.zuoshou}', value: q.prevClose.toFixed(2) },
{ label: '${S.cjl}', value: q.volume.toLocaleString() },
{ label: '${S.neipan}', value: q.innerVol.toLocaleString(), className: 'price-down' },
],
[
{ label: '${S.zuidi}', value: q.low.toFixed(2), className: 'price-down' },
{ label: '${S.zuojie}', value: q.prevSettlement.toFixed(2) },
],
]
})
</script>
<style scoped>
.quote-stats {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px 16px;
padding: 12px 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 12px;
}
.row {
display: flex;
justify-content: space-between;
gap: 8px;
font-size: 13px;
line-height: 1.8;
}
.label {
color: var(--text-secondary);
}
.value {
font-variant-numeric: tabular-nums;
font-weight: 500;
}
@media (max-width: 900px) {
.quote-stats {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
`,
)
console.log('batch1 done')