| New file |
| | |
| | | <template> |
| | | <div class="app-container app-page bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden"> |
| | | <!-- 顶部导航栏 --> |
| | | <assets-head :title="$t('暗池交易') + '(ETF)'" /> |
| | | |
| | | <!-- Tab切换区 --> |
| | | <div class="fixed-section bg-white py-2 px-6 flex rounded-lg mx-4 mt-4 mb-2 border border-gray-200"> |
| | | <button @click="activeTab = 'products'" :class="['tab-button flex-1 py-2 font-medium rounded-lg transition-all duration-200', |
| | | activeTab === 'products' ? 'active text-red-600' : 'text-gray-700']"> |
| | | {{ $t('产品列表') }} |
| | | </button> |
| | | <button @click="activeTab = 'records'" :class="['tab-button flex-1 py-2 font-medium rounded-lg transition-all duration-200', |
| | | activeTab === 'records' ? 'active text-red-600' : 'text-gray-700']"> |
| | | {{ $t('my') }} |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- 内容区: 可滚动 --> |
| | | <div class="content-flex hide-scrollbar px-4 pb-6 overflow-y-auto"> |
| | | <!-- 产品列表 --> |
| | | <div v-show="activeTab === 'products'" class="space-y-4"> |
| | | <div v-for="product in products" :key="product.id" |
| | | class="bg-white rounded-xl p-4 border border-gray-200 shadow-sm hover:shadow-md transition-shadow"> |
| | | <div class="flex justify-between items-start"> |
| | | <div class="flex-1"> |
| | | <!-- 产品标题区域 --> |
| | | <div class="flex items-center justify-between mb-3"> |
| | | <div class="flex items-center"> |
| | | <div> |
| | | <h3 class="text-gray-800 font-bold text-base">{{ product.stockName }}</h3> |
| | | <p class="text-gray-500 text-xs mt-1">{{ product.stockCode }}</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 价格信息区域 --> |
| | | <div class="bg-gray-50 rounded-lg p-3 mb-3"> |
| | | <div class="flex justify-between items-center"> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('开仓价格') }}</p> |
| | | <p class="text-gray-800 font-bold text-lg"> |
| | | {{ product.nowPrice }} $ |
| | | </p> |
| | | </div> |
| | | <div class="text-right"> |
| | | <div class="flex items-center"> |
| | | <span class="iconify text-gray-400 mr-1" |
| | | data-icon="mdi:clock-outline"></span> |
| | | <span class="text-gray-500 text-xs">{{ $t('最小数量') }}</span> |
| | | </div> |
| | | <div class="px-3 py-1 rounded-full text-xs font-medium"> |
| | | {{ product.stockNum }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="flex justify-between items-center"> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('委托价格') }}</p> |
| | | <p class="text-gray-800 font-bold text-lg"> |
| | | {{ product.currentPrice }} $ |
| | | </p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 下单按钮 --> |
| | | <div class="mt-4 pt-3 border-t border-gray-100"> |
| | | <button @click="openTradeModal(product)" |
| | | class="trade-btn w-full bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg text-sm font-bold transition-colors shadow-sm flex items-center justify-center"> |
| | | <span class="iconify mr-2"></span> |
| | | {{ $t('确认下单') }} |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <van-empty v-if="products.length === 0" :description="$t('暂无数据')" /> |
| | | </div> |
| | | |
| | | <!-- 购买记录 --> |
| | | <div v-show="activeTab === 'records'"> |
| | | <!-- 子Tab切换区 --> |
| | | <div class="bg-gray-100 py-1 px-4 flex rounded-lg mb-4"> |
| | | <button @click="activeSubTab = 'position'" :class="['subtab-button flex-1 py-2 text-sm font-medium rounded-lg transition-all duration-200', |
| | | activeSubTab === 'position' ? 'active text-red-600' : 'text-gray-700']"> |
| | | {{ $t('持仓') }} |
| | | </button> |
| | | <button @click="activeSubTab = 'history'" :class="['subtab-button flex-1 py-2 text-sm font-medium rounded-lg transition-all duration-200', |
| | | activeSubTab === 'history' ? 'active text-red-600' : 'text-gray-700']"> |
| | | {{ $t('历史') }} |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- 持仓部分 --> |
| | | <div v-show="activeSubTab === 'position'" class="space-y-4"> |
| | | <div v-for="position in positions" :key="position.id" |
| | | class="bg-white rounded-xl p-4 border border-gray-200 shadow-sm hover:shadow-md transition-shadow"> |
| | | <div class="flex justify-between items-center"> |
| | | <div> |
| | | <h3 class="text-gray-800 font-bold">{{ position.stockName }}</h3> |
| | | <p class="text-gray-500 text-sm mt-1">{{ position.symbol }}</p> |
| | | </div> |
| | | <div class="flex"> |
| | | <button @click="closePosition(position)" |
| | | class="close-btn bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded-lg text-sm transition-colors font-medium"> |
| | | {{ $t('平仓') }} |
| | | </button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="grid grid-cols-2 gap-4 mt-4"> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('买入价格') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ position.price }} $</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('买入金额') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ formatNumberWithComma(position.volume) }} $</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('交易时间') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ position.createTime }}</p> |
| | | </div> |
| | | |
| | | <!-- <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('状态') }}</p> |
| | | <p class="text-green-500 font-medium">{{ position.status }}</p> |
| | | </div> --> |
| | | </div> |
| | | |
| | | <div class="mt-4 pt-3 border-t border-gray-100"> |
| | | <div class="flex justify-between items-center"> |
| | | <p class="text-gray-500 text-xs">{{ $t('浮动盈亏') }}</p> |
| | | <p :class="['font-bold', position.profitLoss >= 0 ? 'text-green-500' : 'text-red-500']"> |
| | | {{ position.profitLoss >= 0 ? '+' : '' }} {{ position.profitLoss }} $ | {{ |
| | | position.profitLossPercentage }}% |
| | | </p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <van-empty v-if="positions.length === 0" :description="$t('暂无数据')" /> |
| | | </div> |
| | | |
| | | <!-- 历史部分 --> |
| | | <div v-show="activeSubTab === 'history'" class="space-y-4"> |
| | | <div v-for="history in histories" :key="history.id" |
| | | class="bg-white rounded-xl p-4 border border-gray-200 shadow-sm hover:shadow-md transition-shadow"> |
| | | <div class="flex justify-between items-center"> |
| | | <div> |
| | | <h3 class="text-gray-800 font-bold">{{ history.stockName }}</h3> |
| | | <p class="text-gray-500 text-sm mt-1">{{ history.symbol }}</p> |
| | | </div> |
| | | <span class="bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full tj" |
| | | :class="[history.state == 'position' ? 'cg' : history.state == 'failed' ? 'sb' : history.state == 'closed' ? 'pc' : '']"> |
| | | {{ startStr[history.state] }} |
| | | </span> |
| | | </div> |
| | | |
| | | <div class="grid grid-cols-2 gap-4 mt-4"> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('买入价格') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ history.price }} $</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('买入金额') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ formatNumberWithComma(history.volume) }} $</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('交易时间') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ history.createTime }}</p> |
| | | </div> |
| | | |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('卖出价格') }}</p> |
| | | <p class="text-gray-800 font-medium">{{ history.closePrice || '--' }}</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('卖出金额') }}</p> |
| | | <p class="text-gray-800 font-medium"> |
| | | {{ history.closePrice ? formatNumberWithComma(history.closePrice * |
| | | history.symbolValue) : '--' }}</p> |
| | | </div> |
| | | |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t('状态') }}</p> |
| | | <p class="text-orange-500 font-medium tj" |
| | | :class="[history.state == 'position' ? 'cg' : history.state == 'failed' ? 'sb' : history.state == 'closed' ? 'pc' : '']"> |
| | | {{ startStr[history.state] }}</p> |
| | | </div> |
| | | <div> |
| | | <p class="text-gray-500 text-xs">{{ $t("订单号") }}</p> |
| | | <p :class="['font-bold']"> |
| | | {{ history.orderNo }} |
| | | </p> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="mt-4 pt-3 border-t border-gray-100" v-if="history.state == 'closed'"> |
| | | <div class="flex justify-between items-center"> |
| | | <p class="text-gray-500 text-xs">{{ $t('盈亏') }}</p> |
| | | <p :class="['font-bold', history.profitLoss >= 0 ? 'text-green-500' : 'text-red-500']"> |
| | | {{ history.profitLoss >= 0 ? '+' : '' }} {{ history.profitLoss }} $ | {{ |
| | | history.profitLossPercentage }}% |
| | | </p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <van-empty v-if="histories.length === 0" :description="$t('暂无数据')" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 交易弹框 --> |
| | | <van-popup v-model:show="showTradeModal"> |
| | | <!-- <div v-if="showTradeModal" |
| | | class="fixed inset-0 bg-gray-800 bg-opacity-70 flex items-center justify-center p-4 z-50" |
| | | @click="closeTradeModal"> --> |
| | | <div class="bg-white rounded-xl w-full p-6" style="width:40rem" @click.stop> |
| | | <div class="flex justify-between items-center mb-4"> |
| | | <h3 class="text-gray-800 text-lg font-bold">{{ $t('确认下单') }}</h3> |
| | | <button @click="closeTradeModal" class="text-gray-500 hover:text-gray-800"> |
| | | <span class="iconify text-xl" data-icon="mdi:close"></span> |
| | | </button> |
| | | </div> |
| | | |
| | | <div class="bg-gray-100 rounded-lg p-4 mb-4"> |
| | | <div class="flex justify-between"> |
| | | <h4 class="text-gray-800 font-bold">{{ selectedProduct?.stockName }}</h4> |
| | | <span class="text-gray-500 text-sm">{{ selectedProduct?.stockCode }}</span> |
| | | </div> |
| | | <p class="text-gray-800 mt-2 font-bold text-xl">{{ selectedProduct?.nowPrice }} |
| | | </p> |
| | | </div> |
| | | |
| | | <div class="space-y-4"> |
| | | <div> |
| | | <label class="text-gray-500 text-sm mb-1 block">{{ $t('交易数量') }}</label> |
| | | <input v-model="tradeQuantity" type="number" |
| | | class="w-full bg-white border border-gray-300 rounded-lg px-4 py-3 text-gray-800 focus:outline-none focus:ring-2 focus:ring-green-500" |
| | | :placeholder="$t('请输入数量')" oninput="value = value.replace(/[^\d]/g, '').replace(/^0+/, '')" |
| | | min="1"> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex space-x-3 mt-6"> |
| | | <button @click="closeTradeModal" |
| | | class="flex-1 bg-transparent border border-red-600 text-red-600 py-3 rounded-lg transition-colors hover:bg-red-50 font-medium"> |
| | | {{ $t('取消') }} |
| | | </button> |
| | | <button @click="confirmTrade" |
| | | class="flex-1 bg-green-600 text-white py-3 rounded-lg transition-colors hover:bg-green-700 font-medium"> |
| | | {{ $t('确认') }} |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <!-- </div> --> |
| | | </van-popup> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, watch } from 'vue' |
| | | import { _getetfDpList, _buyetfStockDp, _getetfDpHistories, _getetfDpOrderList, _getetfDzCloseStockDp } from '@/service/quotes.api' |
| | | import { showConfirmDialog } from 'vant'; |
| | | import { useI18n } from 'vue-i18n' |
| | | import { showToast } from 'vant' |
| | | import { formatNumberWithComma } from '@/utils/utis'; |
| | | |
| | | // 响应式数据 |
| | | const { t } = useI18n() |
| | | const activeTab = ref('products') |
| | | const activeSubTab = ref('position') |
| | | const showTradeModal = ref(false) |
| | | const selectedProduct = ref(null) |
| | | const tradeQuantity = ref(1) |
| | | const tradeType = ref('buy') // 'buy' or 'close' |
| | | const startStr = { |
| | | submitted: t('submitted'), |
| | | position: t('持仓'), |
| | | failed: t('失败'), |
| | | closed: t('平仓') |
| | | } |
| | | |
| | | // 产品数据 |
| | | const products = ref([]) |
| | | |
| | | // 获取产品数据 |
| | | const getProducts = () => { |
| | | _getetfDpList().then(res => { |
| | | products.value = res.records |
| | | }) |
| | | } |
| | | |
| | | // 持仓数据 |
| | | const positions = ref([]) |
| | | |
| | | // 历史数据 |
| | | const histories = ref([]) |
| | | |
| | | // 获取历史数据 |
| | | const getHistories = () => { |
| | | _getetfDpHistories().then(res => { |
| | | histories.value = res.records |
| | | }) |
| | | } |
| | | |
| | | // 获取持仓数据 |
| | | const getOrderList = () => { |
| | | _getetfDpOrderList().then(res => { |
| | | positions.value = res.records |
| | | }) |
| | | } |
| | | |
| | | // 监听activeTab切换并调用接口 |
| | | watch([activeTab, activeSubTab], ([newTab, newSubTab]) => { |
| | | // 获取产品数据 |
| | | if (newTab == 'products') { |
| | | getProducts() |
| | | } |
| | | // 获取历史数据 |
| | | else if (newTab == 'records' && activeSubTab.value == 'history') { |
| | | getHistories() |
| | | } |
| | | // 获取持仓数据 |
| | | else if (newTab == 'records' && activeSubTab.value == 'position') { |
| | | getOrderList() |
| | | } |
| | | }, { immediate: true }) |
| | | |
| | | // 方法 |
| | | // 打开下单弹窗 |
| | | const openTradeModal = (product, type = 'buy') => { |
| | | selectedProduct.value = product |
| | | tradeType.value = type |
| | | tradeQuantity.value = 1 |
| | | showTradeModal.value = true |
| | | } |
| | | // 关闭下单弹窗 |
| | | const closeTradeModal = () => { |
| | | showTradeModal.value = false |
| | | selectedProduct.value = null |
| | | tradeQuantity.value = 1 |
| | | } |
| | | // 确认下单 |
| | | const confirmTrade = async () => { |
| | | if (tradeType.value === 'buy') { |
| | | if (!tradeQuantity.value || tradeQuantity.value == 0) { |
| | | showToast(t('请输入数量')) |
| | | return |
| | | } |
| | | // 买入 |
| | | const newPosition = { |
| | | dzId: selectedProduct.value.uuid, |
| | | num: tradeQuantity.value, |
| | | } |
| | | let res = await _buyetfStockDp(newPosition) |
| | | showToast(t('SuccessfulOperation')) |
| | | } |
| | | closeTradeModal() |
| | | } |
| | | // 平仓 |
| | | const closePosition = (row) => { |
| | | showConfirmDialog({ |
| | | title: t('平仓提示'), |
| | | message: t('是否平仓?'), |
| | | confirmButtonText: t('确认'), |
| | | cancelButtonText: t('取消'), |
| | | }).then(async () => { |
| | | let res = await _getetfDzCloseStockDp({ id: row.uuid }) |
| | | showToast(t('SuccessfulOperation')) |
| | | getOrderList() |
| | | }).catch(() => { }); |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | button { |
| | | /* font-size: ; */ |
| | | } |
| | | |
| | | .border, |
| | | .border-t { |
| | | border-color: #ccc; |
| | | } |
| | | |
| | | .cg { |
| | | color: greenyellow !important; |
| | | } |
| | | |
| | | .sb { |
| | | color: red !important; |
| | | } |
| | | |
| | | .pc { |
| | | color: #aaa; |
| | | } |
| | | |
| | | .tj { |
| | | color: green; |
| | | } |
| | | |
| | | .app-container { |
| | | width: 100%; |
| | | min-height: 812px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | margin: 0 auto; |
| | | font-size: 1.8rem; |
| | | } |
| | | |
| | | .fixed-section { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .content-flex { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .hide-scrollbar::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .hide-scrollbar { |
| | | -ms-overflow-style: none; |
| | | scrollbar-width: none; |
| | | } |
| | | |
| | | .app-page { |
| | | border: 1px solid #e2e8f0; |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .tab-button.active { |
| | | background-color: rgba(239, 68, 68, 0.15); |
| | | color: #dc2626; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .subtab-button.active { |
| | | background-color: rgba(239, 68, 68, 0.15); |
| | | color: #dc2626; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | /* 移动端适配 */ |
| | | @media (max-width: 768px) { |
| | | .app-container { |
| | | width: 100%; |
| | | height: 100vh; |
| | | border-radius: 0; |
| | | } |
| | | |
| | | .app-page { |
| | | border-radius: 0; |
| | | box-shadow: none; |
| | | } |
| | | } |
| | | </style> |