新增:合单拆单部分国际化

This commit is contained in:
ivan 2026-02-26 16:54:46 +08:00
parent ddca28e51c
commit 6e3981a637
11 changed files with 135 additions and 25 deletions

View File

@ -14,6 +14,7 @@ HTTP 请求基础封装,提供 `get` 和 `post` 方法,支持自定义请求
- GET 请求:支持 query 参数,自动序列化 - GET 请求:支持 query 参数,自动序列化
- POST 请求:支持 JSON body - POST 请求:支持 JSON body
- 自定义 headers通过 `RequestConfig.headers` 传入 - 自定义 headers通过 `RequestConfig.headers` 传入
- **Accept-Language**:所有 GET/POST 请求自动附带标准 `Accept-Language` 头,值为当前 vue-i18n 的 `locale`(如 `zh-CN``en`),可在 `config.headers` 中覆盖
## 使用方式 ## 使用方式

View File

@ -11,7 +11,7 @@
- Trade Up / Trade Down Tab - Trade Up / Trade Down Tab
- Asks、Bids 列表,带 `HorizontalProgressBar` 深度条 - Asks、Bids 列表,带 `HorizontalProgressBar` 深度条
- Last price、Spread 展示 - Last price、Spread 展示
- Live / 连接中 状态展示 - Live / 连接中 状态展示(均通过 i18n 国际化)
## Props ## Props
@ -41,6 +41,14 @@
<OrderBook /> <OrderBook />
``` ```
## 国际化
组件内所有展示文案均使用 `trade.*``activity.live` 键:
- `trade.orderBook``trade.orderBookConnecting``trade.orderBookPrice``trade.orderBookShares``trade.orderBookTotal`
- `trade.orderBookAsks``trade.orderBookBids``trade.orderBookLast``trade.orderBookSpread`
- `activity.live`(实时状态)
## 扩展方式 ## 扩展方式
1. **点击下单**:点击某行价格时,将价格传入 TradeComponent 1. **点击下单**:点击某行价格时,将价格传入 TradeComponent

View File

@ -33,6 +33,15 @@
/> />
``` ```
## 国际化
Merge/Split 弹窗文案均通过 `trade.*` 键国际化:
- **Merge 弹窗**`mergeDialogTitle``mergeDialogDesc``mergeAvailableShares``mergeNoMarket``mergeSubmitBtn`;复用 `trade.amount``trade.max`
- **Split 弹窗**`splitDialogTitle``splitDialogDesc``splitAmountLabel``splitNoMarket``splitSubmitBtn`
`mergeDialogDesc``splitDialogDesc``mergeNoMarket``splitNoMarket` 支持 `{yesLabel}``{noLabel}` 插值。
## 扩展方式 ## 扩展方式
1. **Limit 单**:完善 Limit 模式下的价格输入与下单逻辑 1. **Limit 单**:完善 Limit 模式下的价格输入与下单逻辑

View File

@ -1,3 +1,5 @@
import { i18n } from '@/plugins/i18n'
/** /**
* URL https://api.xtrader.vip可通过环境变量 VITE_API_BASE_URL 覆盖 * URL https://api.xtrader.vip可通过环境变量 VITE_API_BASE_URL 覆盖
*/ */
@ -49,6 +51,7 @@ export async function get<T = unknown>(
} }
const headers: Record<string, string> = { const headers: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept-Language': i18n.global.locale.value as string,
...config?.headers, ...config?.headers,
} }
const res = await fetch(url.toString(), { method: 'GET', headers }) const res = await fetch(url.toString(), { method: 'GET', headers })
@ -69,6 +72,7 @@ export async function post<T = unknown>(
const url = new URL(path, BASE_URL || window.location.origin) const url = new URL(path, BASE_URL || window.location.origin)
const headers: Record<string, string> = { const headers: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept-Language': i18n.global.locale.value as string,
...config?.headers, ...config?.headers,
} }
const res = await fetch(url.toString(), { const res = await fetch(url.toString(), {

View File

@ -8,7 +8,7 @@
<span class="live-dot"></span> <span class="live-dot"></span>
{{ t('activity.live') }} {{ t('activity.live') }}
</span> </span>
<span v-else-if="loading" class="loading-badge">连接中...</span> <span v-else-if="loading" class="loading-badge">{{ t('trade.orderBookConnecting') }}</span>
<span v-else class="vol-text">$4.4k Vol.</span> <span v-else class="vol-text">$4.4k Vol.</span>
<v-icon size="14" class="order-book-icon">mdi-chevron-up</v-icon> <v-icon size="14" class="order-book-icon">mdi-chevron-up</v-icon>
</div> </div>
@ -28,9 +28,9 @@
<div class="order-list"> <div class="order-list">
<div class="order-list-header"> <div class="order-list-header">
<div class="order-list-header-spacer"></div> <div class="order-list-header-spacer"></div>
<div class="order-list-header-price">PRICE</div> <div class="order-list-header-price">{{ t('trade.orderBookPrice') }}</div>
<div class="order-list-header-shares">SHARES</div> <div class="order-list-header-shares">{{ t('trade.orderBookShares') }}</div>
<div class="order-list-header-total">TOTAL</div> <div class="order-list-header-total">{{ t('trade.orderBookTotal') }}</div>
</div> </div>
<!-- Asks Orders --> <!-- Asks Orders -->
@ -48,8 +48,8 @@
<div class="order-total">{{ ask.cumulativeTotal.toFixed(2) }}</div> <div class="order-total">{{ ask.cumulativeTotal.toFixed(2) }}</div>
</div> </div>
<!-- Bids Orders --> <!-- Bids Orders -->
<div class="asks-label">Asks</div> <div class="asks-label">{{ t('trade.orderBookAsks') }}</div>
<div class="bids-label">Bids</div> <div class="bids-label">{{ t('trade.orderBookBids') }}</div>
<div <div
v-for="(bid, index) in bidsWithCumulativeTotal" v-for="(bid, index) in bidsWithCumulativeTotal"
:key="index" :key="index"
@ -72,8 +72,8 @@
<!-- Footer --> <!-- Footer -->
<div class="order-book-footer"> <div class="order-book-footer">
<div class="last-price">Last: {{ displayLastPrice }}¢</div> <div class="last-price">{{ t('trade.orderBookLast') }}: {{ displayLastPrice }}¢</div>
<div class="spread">Spread: {{ displaySpread }}¢</div> <div class="spread">{{ t('trade.orderBookSpread') }}: {{ displaySpread }}¢</div>
</div> </div>
</v-card> </v-card>
</template> </template>

View File

@ -1174,7 +1174,7 @@
> >
<v-card class="merge-dialog-card" rounded="lg" elevation="0"> <v-card class="merge-dialog-card" rounded="lg" elevation="0">
<div class="merge-dialog-header"> <div class="merge-dialog-header">
<h3 class="merge-dialog-title">Merge shares</h3> <h3 class="merge-dialog-title">{{ t('trade.mergeDialogTitle') }}</h3>
<v-btn <v-btn
icon icon
variant="text" variant="text"
@ -1187,11 +1187,10 @@
</div> </div>
<v-card-text class="merge-dialog-body"> <v-card-text class="merge-dialog-body">
<p class="merge-dialog-desc"> <p class="merge-dialog-desc">
Merge a share of {{ yesLabel }} and {{ noLabel }} to get 1 USDC. You can do this to save {{ t('trade.mergeDialogDesc', { yesLabel, noLabel }) }}
cost when trying to get rid of a position.
</p> </p>
<div class="merge-amount-row"> <div class="merge-amount-row">
<label class="merge-amount-label">Amount</label> <label class="merge-amount-label">{{ t('trade.amount') }}</label>
<v-text-field <v-text-field
v-model.number="mergeAmount" v-model.number="mergeAmount"
type="number" type="number"
@ -1203,11 +1202,11 @@
/> />
</div> </div>
<p class="merge-available"> <p class="merge-available">
Available shares: {{ availableMergeShares }} {{ t('trade.mergeAvailableShares') }} {{ availableMergeShares }}
<button type="button" class="merge-max-link" @click="setMergeMax">Max</button> <button type="button" class="merge-max-link" @click="setMergeMax">{{ t('trade.max') }}</button>
</p> </p>
<p v-if="!props.market?.marketId" class="merge-no-market"> <p v-if="!props.market?.marketId" class="merge-no-market">
Please select a market first (e.g. click Buy {{ yesLabel }}/{{ noLabel }} on a market). {{ t('trade.mergeNoMarket', { yesLabel, noLabel }) }}
</p> </p>
<p v-if="mergeError" class="merge-error">{{ mergeError }}</p> <p v-if="mergeError" class="merge-error">{{ mergeError }}</p>
</v-card-text> </v-card-text>
@ -1221,7 +1220,7 @@
:disabled="mergeLoading || mergeAmount <= 0" :disabled="mergeLoading || mergeAmount <= 0"
@click="submitMerge" @click="submitMerge"
> >
Merge Shares {{ t('trade.mergeSubmitBtn') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -1237,7 +1236,7 @@
> >
<v-card class="split-dialog-card" rounded="lg" elevation="0"> <v-card class="split-dialog-card" rounded="lg" elevation="0">
<div class="split-dialog-header"> <div class="split-dialog-header">
<h3 class="split-dialog-title">Split</h3> <h3 class="split-dialog-title">{{ t('trade.splitDialogTitle') }}</h3>
<v-btn <v-btn
icon icon
variant="text" variant="text"
@ -1250,11 +1249,10 @@
</div> </div>
<v-card-text class="split-dialog-body"> <v-card-text class="split-dialog-body">
<p class="split-dialog-desc"> <p class="split-dialog-desc">
Use USDC to get one share of {{ yesLabel }} and one share of {{ noLabel }} for this {{ t('trade.splitDialogDesc', { yesLabel, noLabel }) }}
market. 1 USDC 1 complete set.
</p> </p>
<div class="split-amount-row"> <div class="split-amount-row">
<label class="split-amount-label">Amount (USDC)</label> <label class="split-amount-label">{{ t('trade.splitAmountLabel') }}</label>
<v-text-field <v-text-field
v-model.number="splitAmount" v-model.number="splitAmount"
type="number" type="number"
@ -1267,7 +1265,7 @@
/> />
</div> </div>
<p v-if="!props.market?.marketId" class="split-no-market"> <p v-if="!props.market?.marketId" class="split-no-market">
Please select a market first (e.g. click Buy {{ yesLabel }}/{{ noLabel }} on a market). {{ t('trade.splitNoMarket', { yesLabel, noLabel }) }}
</p> </p>
<p v-if="splitError" class="split-error">{{ splitError }}</p> <p v-if="splitError" class="split-error">{{ splitError }}</p>
</v-card-text> </v-card-text>
@ -1281,7 +1279,7 @@
:disabled="splitLoading || splitAmount <= 0" :disabled="splitLoading || splitAmount <= 0"
@click="submitSplit" @click="submitSplit"
> >
Split {{ t('trade.splitSubmitBtn') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@ -21,10 +21,28 @@
"buy": "Buy", "buy": "Buy",
"sell": "Sell", "sell": "Sell",
"orderBook": "Order Book", "orderBook": "Order Book",
"orderBookConnecting": "Connecting...",
"orderBookPrice": "PRICE",
"orderBookShares": "SHARES",
"orderBookTotal": "TOTAL",
"orderBookAsks": "Asks",
"orderBookBids": "Bids",
"orderBookLast": "Last",
"orderBookSpread": "Spread",
"buyLabel": "Trade {label}", "buyLabel": "Trade {label}",
"sellLabel": "Sell {label}", "sellLabel": "Sell {label}",
"merge": "Merge", "merge": "Merge",
"mergeDialogTitle": "Merge shares",
"mergeDialogDesc": "Merge a share of {yesLabel} and {noLabel} to get 1 USDC. You can do this to save cost when trying to get rid of a position.",
"mergeAvailableShares": "Available shares:",
"mergeNoMarket": "Please select a market first (e.g. click Buy {yesLabel}/{noLabel} on a market).",
"mergeSubmitBtn": "Merge Shares",
"split": "Split", "split": "Split",
"splitDialogTitle": "Split",
"splitDialogDesc": "Use USDC to get one share of {yesLabel} and one share of {noLabel} for this market. 1 USDC ≈ 1 complete set.",
"splitAmountLabel": "Amount (USDC)",
"splitNoMarket": "Please select a market first (e.g. click Buy {yesLabel}/{noLabel} on a market).",
"splitSubmitBtn": "Split",
"market": "Market", "market": "Market",
"limit": "Limit", "limit": "Limit",
"deposit": "Deposit", "deposit": "Deposit",

View File

@ -21,10 +21,28 @@
"buy": "買う", "buy": "買う",
"sell": "売る", "sell": "売る",
"orderBook": "オーダーブック", "orderBook": "オーダーブック",
"orderBookConnecting": "接続中...",
"orderBookPrice": "価格",
"orderBookShares": "数量",
"orderBookTotal": "合計",
"orderBookAsks": "売り",
"orderBookBids": "買い",
"orderBookLast": "最新",
"orderBookSpread": "スプレッド",
"buyLabel": "{label}を取引", "buyLabel": "{label}を取引",
"sellLabel": "{label}を売る", "sellLabel": "{label}を売る",
"merge": "マージ", "merge": "マージ",
"mergeDialogTitle": "シェアをマージ",
"mergeDialogDesc": "{yesLabel} と {noLabel} のシェアを 1 つずつマージすると 1 USDC を獲得できます。ポジションを解消する際のコスト削減に使えます。",
"mergeAvailableShares": "利用可能シェア:",
"mergeNoMarket": "先に市場を選択してください(例:市場の 買 {yesLabel}/{noLabel} をクリック)。",
"mergeSubmitBtn": "シェアをマージ",
"split": "スプリット", "split": "スプリット",
"splitDialogTitle": "スプリット",
"splitDialogDesc": "USDC でこの市場の {yesLabel} と {noLabel} のシェアを 1 つずつ獲得できます。1 USDC ≈ 1 セット。",
"splitAmountLabel": "金額 (USDC)",
"splitNoMarket": "先に市場を選択してください(例:市場の 買 {yesLabel}/{noLabel} をクリック)。",
"splitSubmitBtn": "スプリット",
"market": "成行", "market": "成行",
"limit": "指値", "limit": "指値",
"deposit": "入金", "deposit": "入金",

View File

@ -21,10 +21,28 @@
"buy": "매수", "buy": "매수",
"sell": "매도", "sell": "매도",
"orderBook": "호가창", "orderBook": "호가창",
"orderBookConnecting": "연결 중...",
"orderBookPrice": "가격",
"orderBookShares": "수량",
"orderBookTotal": "합계",
"orderBookAsks": "매도",
"orderBookBids": "매수",
"orderBookLast": "최신",
"orderBookSpread": "스프레드",
"buyLabel": "{label} 거래", "buyLabel": "{label} 거래",
"sellLabel": "{label} 매도", "sellLabel": "{label} 매도",
"merge": "병합", "merge": "병합",
"mergeDialogTitle": "주식 병합",
"mergeDialogDesc": "{yesLabel} 1주와 {noLabel} 1주를 병합하면 1 USDC를 받습니다. 포지션 청산 시 비용 절감에 사용할 수 있습니다.",
"mergeAvailableShares": "사용 가능 주식:",
"mergeNoMarket": "먼저 시장을 선택하세요 (예: 시장에서 {yesLabel}/{noLabel} 매수 클릭).",
"mergeSubmitBtn": "주식 병합",
"split": "분할", "split": "분할",
"splitDialogTitle": "분할",
"splitDialogDesc": "USDC로 이 시장의 {yesLabel} 1주와 {noLabel} 1주를 받을 수 있습니다. 1 USDC ≈ 1 세트.",
"splitAmountLabel": "금액 (USDC)",
"splitNoMarket": "먼저 시장을 선택하세요 (예: 시장에서 {yesLabel}/{noLabel} 매수 클릭).",
"splitSubmitBtn": "분할",
"market": "시장가", "market": "시장가",
"limit": "지정가", "limit": "지정가",
"deposit": "입금", "deposit": "입금",

View File

@ -21,10 +21,28 @@
"buy": "买入", "buy": "买入",
"sell": "卖出", "sell": "卖出",
"orderBook": "订单簿", "orderBook": "订单簿",
"buyLabel": "交易{label}", "orderBookConnecting": "连接中...",
"orderBookPrice": "价格",
"orderBookShares": "份额",
"orderBookTotal": "合计",
"orderBookAsks": "卖单",
"orderBookBids": "买单",
"orderBookLast": "最新",
"orderBookSpread": "价差",
"buyLabel": "{label} 交易",
"sellLabel": "卖{label}", "sellLabel": "卖{label}",
"merge": "合并", "merge": "合并",
"mergeDialogTitle": "合并份额",
"mergeDialogDesc": "将 1 份 {yesLabel} 和 1 份 {noLabel} 合并可获得 1 USDC。可用于平仓时降低成本。",
"mergeAvailableShares": "可用份额:",
"mergeNoMarket": "请先选择市场(例如点击某市场的买入 {yesLabel}/{noLabel})。",
"mergeSubmitBtn": "合并份额",
"split": "拆分", "split": "拆分",
"splitDialogTitle": "拆分",
"splitDialogDesc": "使用 USDC 可获得 1 份 {yesLabel} 和 1 份 {noLabel}。1 USDC ≈ 1 完整套。",
"splitAmountLabel": "金额 (USDC)",
"splitNoMarket": "请先选择市场(例如点击某市场的买入 {yesLabel}/{noLabel})。",
"splitSubmitBtn": "拆分",
"market": "市价", "market": "市价",
"limit": "限价", "limit": "限价",
"deposit": "入金", "deposit": "入金",
@ -169,4 +187,4 @@
"ja": "日本語", "ja": "日本語",
"ko": "한국어" "ko": "한국어"
} }
} }

View File

@ -21,10 +21,28 @@
"buy": "買入", "buy": "買入",
"sell": "賣出", "sell": "賣出",
"orderBook": "訂單簿", "orderBook": "訂單簿",
"orderBookConnecting": "連接中...",
"orderBookPrice": "價格",
"orderBookShares": "份額",
"orderBookTotal": "合計",
"orderBookAsks": "賣單",
"orderBookBids": "買單",
"orderBookLast": "最新",
"orderBookSpread": "價差",
"buyLabel": "交易{label}", "buyLabel": "交易{label}",
"sellLabel": "賣{label}", "sellLabel": "賣{label}",
"merge": "合併", "merge": "合併",
"mergeDialogTitle": "合併份額",
"mergeDialogDesc": "將 1 份 {yesLabel} 和 1 份 {noLabel} 合併可獲得 1 USDC。可用於平倉時降低成本。",
"mergeAvailableShares": "可用份額:",
"mergeNoMarket": "請先選擇市場(例如點擊某市場的買入 {yesLabel}/{noLabel})。",
"mergeSubmitBtn": "合併份額",
"split": "拆分", "split": "拆分",
"splitDialogTitle": "拆分",
"splitDialogDesc": "使用 USDC 可獲得 1 份 {yesLabel} 和 1 份 {noLabel}。1 USDC ≈ 1 完整套。",
"splitAmountLabel": "金額 (USDC)",
"splitNoMarket": "請先選擇市場(例如點擊某市場的買入 {yesLabel}/{noLabel})。",
"splitSubmitBtn": "拆分",
"market": "市價", "market": "市價",
"limit": "限價", "limit": "限價",
"deposit": "入金", "deposit": "入金",