|
@@ -62,37 +62,28 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 指标按钮 -->
|
|
|
|
|
<div class="tab-item icon-btn" @click.stop="toggleIndicators">
|
|
<div class="tab-item icon-btn" @click.stop="toggleIndicators">
|
|
|
- <!-- 保持你的图标路径 -->
|
|
|
|
|
<img src="@/assets/icon/bitcoin/lishidingdan.svg" alt="" />
|
|
<img src="@/assets/icon/bitcoin/lishidingdan.svg" alt="" />
|
|
|
</div>
|
|
</div>
|
|
|
</nav>
|
|
</nav>
|
|
|
|
|
|
|
|
<!-- 3. 指标设置面板 -->
|
|
<!-- 3. 指标设置面板 -->
|
|
|
<div class="indicator-panel" v-show="showIndicatorMenu" @click.stop>
|
|
<div class="indicator-panel" v-show="showIndicatorMenu" @click.stop>
|
|
|
- <!-- 主图设置 (单选) -->
|
|
|
|
|
<div class="panel-section">
|
|
<div class="panel-section">
|
|
|
<div class="section-title">主图指标</div>
|
|
<div class="section-title">主图指标</div>
|
|
|
<div class="btn-group">
|
|
<div class="btn-group">
|
|
|
<div v-for="m in ['MA', 'EMA', 'BOLL', 'SAR']" :key="m"
|
|
<div v-for="m in ['MA', 'EMA', 'BOLL', 'SAR']" :key="m"
|
|
|
class="idx-btn" :class="{ active: mainIdx === m }"
|
|
class="idx-btn" :class="{ active: mainIdx === m }"
|
|
|
- @click="changeMain(m)">
|
|
|
|
|
- {{ m }}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ @click="changeMain(m)">{{ m }}</div>
|
|
|
<div class="idx-btn" :class="{ active: mainIdx === 'Hide' }" @click="changeMain('Hide')">隐藏</div>
|
|
<div class="idx-btn" :class="{ active: mainIdx === 'Hide' }" @click="changeMain('Hide')">隐藏</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <!-- 副图设置 (多选) -->
|
|
|
|
|
<div class="panel-section">
|
|
<div class="panel-section">
|
|
|
<div class="section-title">副图指标 (可多选)</div>
|
|
<div class="section-title">副图指标 (可多选)</div>
|
|
|
<div class="btn-group">
|
|
<div class="btn-group">
|
|
|
<div v-for="s in ['VOL', 'MACD', 'KDJ', 'RSI', 'WR', 'CCI', 'OBV']" :key="s"
|
|
<div v-for="s in ['VOL', 'MACD', 'KDJ', 'RSI', 'WR', 'CCI', 'OBV']" :key="s"
|
|
|
class="idx-btn" :class="{ active: subIdx.includes(s) }"
|
|
class="idx-btn" :class="{ active: subIdx.includes(s) }"
|
|
|
- @click="toggleSub(s)">
|
|
|
|
|
- {{ s }}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ @click="toggleSub(s)">{{ s }}</div>
|
|
|
<div class="idx-btn" :class="{ active: subIdx.length === 0 }" @click="clearSub">隐藏</div>
|
|
<div class="idx-btn" :class="{ active: subIdx.length === 0 }" @click="clearSub">隐藏</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -100,82 +91,74 @@
|
|
|
|
|
|
|
|
<!-- 4. K线图组件 -->
|
|
<!-- 4. K线图组件 -->
|
|
|
<div class="k-line-main">
|
|
<div class="k-line-main">
|
|
|
- <KlineChart
|
|
|
|
|
|
|
+ <KLineChart
|
|
|
ref="klineRef"
|
|
ref="klineRef"
|
|
|
:data="kLineData"
|
|
:data="kLineData"
|
|
|
height="70vh"
|
|
height="70vh"
|
|
|
- :precision="{ price: getPricePrecision(marketInfo.price), volume: 2 }" />
|
|
|
|
|
|
|
+ :precision="{ price: getPricePrecision(marketInfo.price), volume: 2 }"
|
|
|
|
|
+ @load-more="handleLoadMore" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 5. 底部 -->
|
|
|
|
|
|
|
+ <!-- 5. 底部原有的 委托挂单/最新成交 -->
|
|
|
<div class="notifi-classifi">
|
|
<div class="notifi-classifi">
|
|
|
<div class="pf600 fs14" :class="current === 'entrustingOrder' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('entrustingOrder')">委托挂单</div>
|
|
<div class="pf600 fs14" :class="current === 'entrustingOrder' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('entrustingOrder')">委托挂单</div>
|
|
|
<div class="sys-notifi pf600 fs14" :class="current === 'latestTransactions' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('latestTransactions')">最新成交</div>
|
|
<div class="sys-notifi pf600 fs14" :class="current === 'latestTransactions' ? 'fc121212' : 'fcA8A8A8'" @click="messageChange('latestTransactions')">最新成交</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<component :is="currentComponent" :symbol-id="symbolId" :latestTransactionData="latestTransactionData" :orderPlacement="orderPlacement" />
|
|
<component :is="currentComponent" :symbol-id="symbolId" :latestTransactionData="latestTransactionData" :orderPlacement="orderPlacement" />
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ style="
|
|
|
|
|
+ box-shadow: 0 -0.05333rem 0.26667rem rgba(109 109 109 / 5%);
|
|
|
|
|
+ box-sizing: border-box;margin-top: 20px;
|
|
|
|
|
+ width:100%;z-index: 99;position: sticky; bottom: 0;background-color: #FFFFFF;padding: 20px;">
|
|
|
|
|
+ <div @click="router.push('/bitcoin/CryptocurrencyTrading')"
|
|
|
|
|
+ style="font-size: 17px; margin:0 auto;border-radius:19px;color:#FFFFFF; text-align:center; width:80%; line-height: 38px; font-weight: 500; background-color: #F6465D; ">交易</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
|
|
|
|
|
-import { useRoute } from "vue-router";
|
|
|
|
|
|
|
+import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";
|
|
|
|
|
+import { useRoute,useRouter } from "vue-router";
|
|
|
import { GetCandlestickChart } from "@/api/index.js";
|
|
import { GetCandlestickChart } from "@/api/index.js";
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter(); // 【新增】 实例化路由
|
|
|
|
|
+
|
|
|
|
|
+// 引入组件
|
|
|
|
|
+import KLineChart from "@/views/bitcoin/lever/components/KLineChart.vue";
|
|
|
import EntrustingOrder from "./EntrustingOrder.vue";
|
|
import EntrustingOrder from "./EntrustingOrder.vue";
|
|
|
import LatestTransactions from "./LatestTransactions.vue";
|
|
import LatestTransactions from "./LatestTransactions.vue";
|
|
|
-import KlineChart from "@/views/bitcoin/lever/components/KLineChart.vue";
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
const route = useRoute();
|
|
|
const symbolId = computed(() => route.query.id || "6");
|
|
const symbolId = computed(() => route.query.id || "6");
|
|
|
|
|
|
|
|
-// 周期
|
|
|
|
|
|
|
+// --- 周期 ---
|
|
|
const currentTab = ref("1d");
|
|
const currentTab = ref("1d");
|
|
|
const visibleTabs = ["15m", "1h", "4h", "1d"];
|
|
const visibleTabs = ["15m", "1h", "4h", "1d"];
|
|
|
const moreTabs = ["1m", "5m", "30m", "1w", "1M"];
|
|
const moreTabs = ["1m", "5m", "30m", "1w", "1M"];
|
|
|
const isMoreActive = computed(() => moreTabs.includes(currentTab.value));
|
|
const isMoreActive = computed(() => moreTabs.includes(currentTab.value));
|
|
|
const getTabLabel = (t) => ({ '1m':'1分', '5m':'5分', '15m':'15分', '30m':'30分', '1h':'1小时', '4h':'4小时', '1d':'日线', '1w':'周线', '1M':'月线' }[t] || t);
|
|
const getTabLabel = (t) => ({ '1m':'1分', '5m':'5分', '15m':'15分', '30m':'30分', '1h':'1小时', '4h':'4小时', '1d':'日线', '1w':'周线', '1M':'月线' }[t] || t);
|
|
|
|
|
|
|
|
-// --- 指标管理 (升级版) ---
|
|
|
|
|
|
|
+// --- 指标管理 ---
|
|
|
const showMoreMenu = ref(false);
|
|
const showMoreMenu = ref(false);
|
|
|
const showIndicatorMenu = ref(false);
|
|
const showIndicatorMenu = ref(false);
|
|
|
const klineRef = ref(null);
|
|
const klineRef = ref(null);
|
|
|
-
|
|
|
|
|
-// 主图状态 (单选)
|
|
|
|
|
const mainIdx = ref('MA');
|
|
const mainIdx = ref('MA');
|
|
|
-
|
|
|
|
|
-// 副图状态 (多选,默认选两个)
|
|
|
|
|
const subIdx = ref(['VOL', 'MACD']);
|
|
const subIdx = ref(['VOL', 'MACD']);
|
|
|
|
|
|
|
|
-// 切换主图
|
|
|
|
|
-const changeMain = (name) => {
|
|
|
|
|
- mainIdx.value = name;
|
|
|
|
|
- if (klineRef.value) klineRef.value.setMainIndicator(name);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 切换副图 (多选逻辑)
|
|
|
|
|
|
|
+const changeMain = (name) => { mainIdx.value = name; if (klineRef.value) klineRef.value.setMainIndicator(name); };
|
|
|
const toggleSub = (name) => {
|
|
const toggleSub = (name) => {
|
|
|
const index = subIdx.value.indexOf(name);
|
|
const index = subIdx.value.indexOf(name);
|
|
|
- if (index > -1) {
|
|
|
|
|
- // 已经选中 -> 取消选中
|
|
|
|
|
- subIdx.value.splice(index, 1);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 未选中 -> 添加选中 (限制最多3个防止太挤,或者不做限制)
|
|
|
|
|
- if (subIdx.value.length >= 3) {
|
|
|
|
|
- subIdx.value.shift(); // 移除最早选的一个,保持最多3个
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (index > -1) subIdx.value.splice(index, 1);
|
|
|
|
|
+ else {
|
|
|
|
|
+ if (subIdx.value.length >= 3) subIdx.value.shift();
|
|
|
subIdx.value.push(name);
|
|
subIdx.value.push(name);
|
|
|
}
|
|
}
|
|
|
- // 更新图表
|
|
|
|
|
if (klineRef.value) klineRef.value.setSubIndicators(subIdx.value);
|
|
if (klineRef.value) klineRef.value.setSubIndicators(subIdx.value);
|
|
|
};
|
|
};
|
|
|
|
|
+const clearSub = () => { subIdx.value = []; if (klineRef.value) klineRef.value.setSubIndicators([]); };
|
|
|
|
|
|
|
|
-// 隐藏所有副图
|
|
|
|
|
-const clearSub = () => {
|
|
|
|
|
- subIdx.value = [];
|
|
|
|
|
- if (klineRef.value) klineRef.value.setSubIndicators([]);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// --- 交互 ---
|
|
|
|
|
const toggleMore = () => { showIndicatorMenu.value = false; showMoreMenu.value = !showMoreMenu.value; };
|
|
const toggleMore = () => { showIndicatorMenu.value = false; showMoreMenu.value = !showMoreMenu.value; };
|
|
|
const toggleIndicators = () => { showMoreMenu.value = false; showIndicatorMenu.value = !showIndicatorMenu.value; };
|
|
const toggleIndicators = () => { showMoreMenu.value = false; showIndicatorMenu.value = !showIndicatorMenu.value; };
|
|
|
const closePopups = () => { showMoreMenu.value = false; showIndicatorMenu.value = false; };
|
|
const closePopups = () => { showMoreMenu.value = false; showIndicatorMenu.value = false; };
|
|
@@ -188,44 +171,92 @@ const switchPeriod = (period) => {
|
|
|
getKlineData();
|
|
getKlineData();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// --- 数据/WS (保持不变) ---
|
|
|
|
|
|
|
+// --- 数据/WS ---
|
|
|
const kLineData = ref([]);
|
|
const kLineData = ref([]);
|
|
|
const socket = ref(null);
|
|
const socket = ref(null);
|
|
|
const WS_BASE_URL = "ws://backend.66linknow.com/ws/kline/";
|
|
const WS_BASE_URL = "ws://backend.66linknow.com/ws/kline/";
|
|
|
const HEARTBEAT_INTERVAL = 15000;
|
|
const HEARTBEAT_INTERVAL = 15000;
|
|
|
const RECONNECT_DELAY = 3000;
|
|
const RECONNECT_DELAY = 3000;
|
|
|
let heartbeatTimer = null, reconnectTimer = null, isUnmounted = false;
|
|
let heartbeatTimer = null, reconnectTimer = null, isUnmounted = false;
|
|
|
|
|
+const isLoadingMore = ref(false);
|
|
|
|
|
|
|
|
const marketInfo = ref({ price: "0.00", fiatPrice: "0.00", change: 0.0, high: "0.00", low: "0.00", vol: "0", amount: "0" });
|
|
const marketInfo = ref({ price: "0.00", fiatPrice: "0.00", change: 0.0, high: "0.00", low: "0.00", vol: "0", amount: "0" });
|
|
|
|
|
+
|
|
|
const orderPlacement = ref();
|
|
const orderPlacement = ref();
|
|
|
const latestTransactionData = ref();
|
|
const latestTransactionData = ref();
|
|
|
const current = ref("entrustingOrder");
|
|
const current = ref("entrustingOrder");
|
|
|
const componentsMap = { entrustingOrder: EntrustingOrder, latestTransactions: LatestTransactions };
|
|
const componentsMap = { entrustingOrder: EntrustingOrder, latestTransactions: LatestTransactions };
|
|
|
const currentComponent = computed(() => componentsMap[current.value]);
|
|
const currentComponent = computed(() => componentsMap[current.value]);
|
|
|
|
|
+const messageChange = (key) => current.value = key;
|
|
|
|
|
|
|
|
-const getKlineData = async () => {
|
|
|
|
|
|
|
+// --- 获取 K线数据 (核心) ---
|
|
|
|
|
+const getKlineData = async (endTime = null) => {
|
|
|
if (typeof GetCandlestickChart !== "function") return;
|
|
if (typeof GetCandlestickChart !== "function") return;
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- const res = await GetCandlestickChart({ symbol: symbolId.value, period: currentTab.value });
|
|
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ symbol: symbolId.value,
|
|
|
|
|
+ period: currentTab.value,
|
|
|
|
|
+ limit: 150,
|
|
|
|
|
+ to: endTime || undefined
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const res = await GetCandlestickChart(params);
|
|
|
|
|
+
|
|
|
let rawList = Array.isArray(res) ? res : (res && res.data ? res.data : []);
|
|
let rawList = Array.isArray(res) ? res : (res && res.data ? res.data : []);
|
|
|
|
|
+
|
|
|
if (rawList.length > 0) {
|
|
if (rawList.length > 0) {
|
|
|
const formattedData = rawList.map((item) => ({
|
|
const formattedData = rawList.map((item) => ({
|
|
|
- timestamp: Number(item[0]), open: parseFloat(item[1]), high: parseFloat(item[2]),
|
|
|
|
|
- low: parseFloat(item[3]), close: parseFloat(item[4]), volume: parseFloat(item[5])
|
|
|
|
|
|
|
+ timestamp: Number(item[0]),
|
|
|
|
|
+ open: parseFloat(item[1]),
|
|
|
|
|
+ high: parseFloat(item[2]),
|
|
|
|
|
+ low: parseFloat(item[3]),
|
|
|
|
|
+ close: parseFloat(item[4]),
|
|
|
|
|
+ volume: parseFloat(item[5])
|
|
|
}));
|
|
}));
|
|
|
|
|
+
|
|
|
|
|
+ // 保证按时间升序
|
|
|
formattedData.sort((a, b) => a.timestamp - b.timestamp);
|
|
formattedData.sort((a, b) => a.timestamp - b.timestamp);
|
|
|
- kLineData.value = formattedData;
|
|
|
|
|
|
|
|
|
|
- if (marketInfo.value.price === "0.00") {
|
|
|
|
|
- const lastBar = formattedData[formattedData.length - 1];
|
|
|
|
|
- let totalVol = 0; formattedData.forEach(i => totalVol += i.volume);
|
|
|
|
|
- const changeRate = formattedData[0].open ? ((lastBar.close - formattedData[0].open) / formattedData[0].open) * 100 : 0;
|
|
|
|
|
- marketInfo.value = { price: lastBar.close, fiatPrice: lastBar.close, change: changeRate.toFixed(2), high: lastBar.high, low: lastBar.low, vol: totalVol, amount: totalVol * lastBar.close };
|
|
|
|
|
|
|
+ if (endTime) {
|
|
|
|
|
+ // --- 加载历史模式 ---
|
|
|
|
|
+ if (klineRef.value) {
|
|
|
|
|
+ const hasMore = formattedData.length >= 150;
|
|
|
|
|
+ // 核心点:子组件 KLineChart 内部现在会自动处理 overlap
|
|
|
|
|
+ klineRef.value.applyHistoryData(formattedData, hasMore);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // --- 初始化/重置模式 ---
|
|
|
|
|
+ kLineData.value = formattedData;
|
|
|
|
|
+ if (formattedData.length > 0) {
|
|
|
|
|
+ const lastBar = formattedData[formattedData.length - 1];
|
|
|
|
|
+ marketInfo.value = { ...marketInfo.value, price: lastBar.close, fiatPrice: lastBar.close, high: lastBar.high, low: lastBar.low };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 没数据了
|
|
|
|
|
+ if (endTime && klineRef.value) {
|
|
|
|
|
+ klineRef.value.applyHistoryData([], false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- } catch (error) { console.error("API Error", error); }
|
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("API Error", error);
|
|
|
|
|
+ if (endTime && klineRef.value) {
|
|
|
|
|
+ // 发生错误时也要告知 KLineChart 停止加载,否则 loading 动画会一直转
|
|
|
|
|
+ klineRef.value.applyHistoryData([], false);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ isLoadingMore.value = false;
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const handleLoadMore = (timestamp) => {
|
|
|
|
|
+ if (isLoadingMore.value) return;
|
|
|
|
|
+ isLoadingMore.value = true;
|
|
|
|
|
+ getKlineData(timestamp);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// --- WebSocket ---
|
|
|
const connectWebSocket = () => {
|
|
const connectWebSocket = () => {
|
|
|
closeWebSocket();
|
|
closeWebSocket();
|
|
|
const symbolStr = String(route.query.type || "btcusdt").toLowerCase();
|
|
const symbolStr = String(route.query.type || "btcusdt").toLowerCase();
|
|
@@ -267,17 +298,25 @@ const updateKlineData = (newBar) => {
|
|
|
if (!kLineData.value?.length) { kLineData.value = [newBar]; return; }
|
|
if (!kLineData.value?.length) { kLineData.value = [newBar]; return; }
|
|
|
const lastIndex = kLineData.value.length - 1;
|
|
const lastIndex = kLineData.value.length - 1;
|
|
|
const lastBar = kLineData.value[lastIndex];
|
|
const lastBar = kLineData.value[lastIndex];
|
|
|
|
|
+ // 简单的 WebSocket 去重
|
|
|
if (newBar.timestamp === lastBar.timestamp) kLineData.value.splice(lastIndex, 1, newBar);
|
|
if (newBar.timestamp === lastBar.timestamp) kLineData.value.splice(lastIndex, 1, newBar);
|
|
|
else if (newBar.timestamp > lastBar.timestamp) kLineData.value.push(newBar);
|
|
else if (newBar.timestamp > lastBar.timestamp) kLineData.value.push(newBar);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const handleVisibilityChange = () => { if (document.visibilityState === "visible" && socket.value?.readyState !== 1) connectWebSocket(); };
|
|
|
|
|
-onMounted(() => { isUnmounted = false; getKlineData(); connectWebSocket(); document.addEventListener("visibilitychange", handleVisibilityChange); });
|
|
|
|
|
|
|
+const handleVisibilityChange = () => {
|
|
|
|
|
+ if (document.visibilityState === "visible" && socket.value?.readyState !== 1)
|
|
|
|
|
+ connectWebSocket(); };
|
|
|
|
|
+onMounted(() => { isUnmounted = false;
|
|
|
|
|
+ getKlineData(); connectWebSocket();
|
|
|
|
|
+ document.addEventListener("visibilitychange", handleVisibilityChange); });
|
|
|
onBeforeUnmount(() => { isUnmounted = true; closeWebSocket(); document.removeEventListener("visibilitychange", handleVisibilityChange); });
|
|
onBeforeUnmount(() => { isUnmounted = true; closeWebSocket(); document.removeEventListener("visibilitychange", handleVisibilityChange); });
|
|
|
watch(symbolId, () => { kLineData.value = []; getKlineData(); connectWebSocket(); });
|
|
watch(symbolId, () => { kLineData.value = []; getKlineData(); connectWebSocket(); });
|
|
|
-const messageChange = (key) => current.value = key;
|
|
|
|
|
const formatNumber = (num) => num ? Number(num).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 }) : "0.00";
|
|
const formatNumber = (num) => num ? Number(num).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 }) : "0.00";
|
|
|
-const abbreviateNumber = (v) => { if (!v) return "0.00"; let n = parseFloat(v); if (n>=1e9) return (n/1e9).toFixed(2)+"B"; if (n>=1e6) return (n/1e6).toFixed(2)+"M"; if (n>=1e3) return (n/1e3).toFixed(2)+"K"; return n.toFixed(2); };
|
|
|
|
|
|
|
+const abbreviateNumber = (v) => { if (!v) return "0.00"; let n = parseFloat(v);
|
|
|
|
|
+ if (n>=1e9) return (n/1e9).toFixed(2)+"B";
|
|
|
|
|
+ if (n>=1e6) return (n/1e6).toFixed(2)+"M";
|
|
|
|
|
+ if (n>=1e3) return (n/1e3).toFixed(2)+"K";
|
|
|
|
|
+ return n.toFixed(2); };
|
|
|
const getPricePrecision = (p) => (p < 1 ? 6 : p < 10 ? 4 : 2);
|
|
const getPricePrecision = (p) => (p < 1 ? 6 : p < 10 ? 4 : 2);
|
|
|
const getPriceColor = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
|
|
const getPriceColor = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
|
|
|
const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
|
|
const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
|
|
@@ -287,50 +326,38 @@ const getUpDownClass = (c) => (c >= 0 ? "fc45B26B" : "fcF6465D");
|
|
|
.fc45B26B { color: #2ebd85 !important; }
|
|
.fc45B26B { color: #2ebd85 !important; }
|
|
|
.fcF6465D { color: #f6465d !important; }
|
|
.fcF6465D { color: #f6465d !important; }
|
|
|
.fc1F2937 { color: #1f2937; }
|
|
.fc1F2937 { color: #1f2937; }
|
|
|
-
|
|
|
|
|
-.time-tabs {
|
|
|
|
|
- display: flex; align-items: center; justify-content: space-between;
|
|
|
|
|
- width: 100%; box-sizing: border-box; padding: 0 15px 0 15px; position: relative; z-index: 20;
|
|
|
|
|
-}
|
|
|
|
|
-.tab-item {
|
|
|
|
|
- font-size: 14px; color: #929aa5; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-weight: 500; display: flex; align-items: center; position: relative;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+.time-tabs { display: flex; align-items: center; justify-content: space-between; width: 100%;
|
|
|
|
|
+ box-sizing: border-box; padding:5px 0 15px 15px; position: relative; z-index: 20; }
|
|
|
|
|
+.tab-item { font-size: 14px; color: #929aa5; padding: 4px 8px; border-radius: 4px; cursor: pointer;
|
|
|
|
|
+ font-weight: 500; display: flex; align-items: center; position: relative; }
|
|
|
.tab-item.icon-btn { padding: 4px 4px; }
|
|
.tab-item.icon-btn { padding: 4px 4px; }
|
|
|
.active-text { color: #fff; font-weight: 600; }
|
|
.active-text { color: #fff; font-weight: 600; }
|
|
|
.triangle { font-size: 8px; margin-left: 2px; transform: scale(0.9); }
|
|
.triangle { font-size: 8px; margin-left: 2px; transform: scale(0.9); }
|
|
|
.tab-item img { display: block; height: 16px; width: auto; }
|
|
.tab-item img { display: block; height: 16px; width: auto; }
|
|
|
-
|
|
|
|
|
-.dropdown-menu {
|
|
|
|
|
- position: absolute; top: 30px; left: -20px; width: 80px; background: #fff;
|
|
|
|
|
- box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 6px; padding: 5px 0;
|
|
|
|
|
- z-index: 100; display: flex; flex-direction: column;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+.dropdown-menu { position: absolute; top: 30px; left: -20px; width: 80px; background: #fff;
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 6px; padding: 5px 0; z-index: 100;
|
|
|
|
|
+ display: flex; flex-direction: column; }
|
|
|
.drop-item { padding: 8px 15px; font-size: 13px; color: #666; text-align: center; }
|
|
.drop-item { padding: 8px 15px; font-size: 13px; color: #666; text-align: center; }
|
|
|
.drop-item.active { color: #F6465D; background: #fff5f5; }
|
|
.drop-item.active { color: #F6465D; background: #fff5f5; }
|
|
|
-
|
|
|
|
|
-.indicator-panel {
|
|
|
|
|
- position: absolute; top: 155px; left: 15px; right: 15px; background: #fff;
|
|
|
|
|
- box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-radius: 8px; padding: 15px; z-index: 99;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+.indicator-panel { position: absolute; top: 155px; left: 15px; right: 15px; background: #fff;
|
|
|
|
|
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1); border-radius: 8px; padding: 15px; z-index: 99; }
|
|
|
.panel-section { margin-bottom: 15px; }
|
|
.panel-section { margin-bottom: 15px; }
|
|
|
.section-title { font-size: 12px; color: #999; margin-bottom: 8px; }
|
|
.section-title { font-size: 12px; color: #999; margin-bottom: 8px; }
|
|
|
.btn-group { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
.btn-group { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
|
-.idx-btn { padding: 4px 12px; border: 1px solid #eee; border-radius: 14px; font-size: 12px; color: #666; cursor: pointer; }
|
|
|
|
|
|
|
+.idx-btn { padding: 4px 12px; border: 1px solid #eee; border-radius: 14px; font-size: 12px;
|
|
|
|
|
+ color: #666; cursor: pointer; }
|
|
|
.idx-btn.active { border-color: #F6465D; color: #F6465D; background-color: #fff5f5; }
|
|
.idx-btn.active { border-color: #F6465D; color: #F6465D; background-color: #fff5f5; }
|
|
|
-
|
|
|
|
|
-.market-conditions { display: flex; flex-direction: column; align-items: center; margin-bottom: 50px; width: 100%; position: relative; }
|
|
|
|
|
-.market-price {
|
|
|
|
|
- display: flex; justify-content: space-between; margin-top: 8px;
|
|
|
|
|
|
|
+.market-conditions { display: flex; flex-direction: column; align-items: center; width: 100%;
|
|
|
|
|
+ position: relative; min-height: 100vh; background: #fff; }
|
|
|
|
|
+.market-price { display: flex; justify-content: space-between; margin-top: 4px;
|
|
|
width: 100%; padding: 0 15px; box-sizing: border-box;
|
|
width: 100%; padding: 0 15px; box-sizing: border-box;
|
|
|
- .price-left { display: flex; flex-direction: column; width: 144px; line-height: 20px;
|
|
|
|
|
- height: 69px;
|
|
|
|
|
- .left-price { display: flex; align-items: center; height: 18px; }
|
|
|
|
|
- .left-number { margin-top: 5px; }
|
|
|
|
|
- .left-appro { display: flex; align-items: center;margin-top: 3px;
|
|
|
|
|
- .appro { margin-left: 9px; } } }
|
|
|
|
|
- .price-right { display: flex; flex-direction: column; height: 100%; line-height: 20px;
|
|
|
|
|
- .right-number-top, .right-number-bottom { display: flex; justify-content: flex-end; width: 100%; height: 32px; .right-number-top-price, .right-number-top-number { margin-left: 10px; text-align: right; div { height: 16px; line-height: 16px; text-align: end; } } } .right-number-bottom { } }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ .price-left { display: flex;line-height: 20px; flex-direction: column; width: 144px;
|
|
|
|
|
+ .left-price { height: 18px; line-height: 18px; } .left-number { margin-top: 5px; }
|
|
|
|
|
+ .left-appro { margin-top: 3px; .appro { margin-left: 9px; } } }
|
|
|
|
|
+ .price-right { display: flex; flex-direction: column; height: 100%;
|
|
|
|
|
+ .right-number-top, .right-number-bottom { display: flex; justify-content: flex-end; width: 100%; height: 32px; .right-number-top-price, .right-number-top-number { margin-left: 10px; text-align: right; div { height: 16px; line-height: 16px; text-align: end; } } } } }
|
|
|
.k-line-main { height: 50vh; min-height: 350px; width: 100%; padding: 0 15px; }
|
|
.k-line-main { height: 50vh; min-height: 350px; width: 100%; padding: 0 15px; }
|
|
|
-.notifi-classifi { display: flex; align-items: flex-end; margin-top: 15px; width: 100%; padding: 0 15px; box-sizing: border-box; height: 24px; border-bottom: 1px solid #f5f5f5; padding-bottom: 5px; .sys-notifi { margin-left: 47px; } }
|
|
|
|
|
|
|
+.notifi-classifi { display: flex; align-items: flex-end; margin-top: 140px;
|
|
|
|
|
+ width: 100%; padding: 0 15px 0 15px; box-sizing: border-box; height: 24px;
|
|
|
|
|
+ border-bottom: 1px solid #f5f5f5; .sys-notifi { margin-left: 47px; } }
|
|
|
</style>
|
|
</style>
|