|
|
@@ -42,45 +42,95 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 热力图视图 -->
|
|
|
- <view v-show="viewMode === 'list'" class="stock-list" :class="{ 'hidden-list': myStocks.length === 0 }">
|
|
|
- <stock-list-item
|
|
|
- v-for="(stock, index) in myStocks"
|
|
|
- :key="stock.code"
|
|
|
- :stock="stock"
|
|
|
- :show-delete="true"
|
|
|
- @delete="removeStock(index)"
|
|
|
- @click="handleStockClick(stock, index)"
|
|
|
- />
|
|
|
+ <view v-show="viewMode === 'list'" class="stock-list-card" :class="{ 'hidden-list': myStocks.length === 0 }">
|
|
|
+ <view class="list-header">
|
|
|
+ <view class="list-dot"></view>
|
|
|
+ <text class="list-title">我的自选</text>
|
|
|
+ <text class="list-count">{{ myStocks.length }}只</text>
|
|
|
+ </view>
|
|
|
+ <view class="stock-list">
|
|
|
+ <view class="stock-item-wrapper" v-for="(stock, index) in myStocks" :key="stock.code">
|
|
|
+ <movable-area class="movable-area">
|
|
|
+ <movable-view
|
|
|
+ class="movable-view"
|
|
|
+ direction="horizontal"
|
|
|
+ :x="slideStates[stock.code]?.x || 0"
|
|
|
+ :damping="40"
|
|
|
+ :friction="5"
|
|
|
+ :out-of-bounds="false"
|
|
|
+ @change="(e) => handleSlideChange(e, stock.code)"
|
|
|
+ @touchend="() => handleSlideEnd(stock.code)"
|
|
|
+ >
|
|
|
+ <view class="stock-item">
|
|
|
+ <view class="stock-main">
|
|
|
+ <view class="stock-info">
|
|
|
+ <text class="stock-name">{{ stock.name }}</text>
|
|
|
+ <view class="stock-code-row">
|
|
|
+ <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
|
|
|
+ <text class="stock-code">{{ stock.code }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 趋势图 -->
|
|
|
+ <view class="stock-chart">
|
|
|
+ <canvas
|
|
|
+ :canvas-id="'chart-' + stock.code"
|
|
|
+ :id="'chart-' + stock.code"
|
|
|
+ class="trend-canvas"
|
|
|
+ ></canvas>
|
|
|
+ </view>
|
|
|
+ <view class="stock-quote">
|
|
|
+ <text class="stock-price">{{ stock.currentPrice || '-' }}</text>
|
|
|
+ <text :class="['stock-change', getProfitClass(stock.changePercent)]">{{ stock.changePercent || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 滑动删除按钮 -->
|
|
|
+ <view class="slide-delete-btn" @click.stop="removeStock(index)">
|
|
|
+ <view class="delete-icon-circle">
|
|
|
+ <text class="delete-icon-text">−</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </movable-view>
|
|
|
+ </movable-area>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 详情表格视图 -->
|
|
|
- <view v-show="viewMode === 'table'" class="stock-table" :class="{ 'hidden-list': myStocks.length === 0 }">
|
|
|
+ <view v-show="viewMode === 'table'" class="stock-table-card" :class="{ 'hidden-list': myStocks.length === 0 }">
|
|
|
+ <view class="list-header">
|
|
|
+ <view class="list-dot list-dot-blue"></view>
|
|
|
+ <text class="list-title">详情数据</text>
|
|
|
+ </view>
|
|
|
<!-- 表头 -->
|
|
|
<view class="table-header">
|
|
|
<text class="th-name">股票</text>
|
|
|
<text class="th-date">自选日</text>
|
|
|
<text class="th-price">自选价</text>
|
|
|
- <text class="th-profit">自选收益</text>
|
|
|
+ <text class="th-profit">收益</text>
|
|
|
</view>
|
|
|
<!-- 表格内容 -->
|
|
|
- <view
|
|
|
- v-for="(stock, index) in myStocks"
|
|
|
- :key="stock.code"
|
|
|
- class="table-row"
|
|
|
- @click="handleStockClick(stock, index)"
|
|
|
- >
|
|
|
- <view class="td-name">
|
|
|
- <text class="stock-name">{{ stock.name }}</text>
|
|
|
- <view class="stock-code-row">
|
|
|
- <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
|
|
|
- <text class="stock-code">{{ stock.code }}</text>
|
|
|
+ <view class="table-list">
|
|
|
+ <view
|
|
|
+ v-for="(stock, index) in myStocks"
|
|
|
+ :key="stock.code"
|
|
|
+ class="table-row"
|
|
|
+ >
|
|
|
+ <view class="td-name">
|
|
|
+ <text class="stock-name">{{ stock.name }}</text>
|
|
|
+ <view class="stock-code-row">
|
|
|
+ <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
|
|
|
+ <text class="stock-code">{{ stock.code }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="td-date">{{ stock.addDate || '--' }}</text>
|
|
|
+ <text class="td-price">{{ formatPrice(stock.addPrice) }}</text>
|
|
|
+ <view class="td-profit-wrap">
|
|
|
+ <text :class="['td-profit', getProfitClass(stock.profitPercent)]">
|
|
|
+ {{ stock.profitPercent || '--' }}
|
|
|
+ </text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <text class="td-date">{{ stock.addDate || '--' }}</text>
|
|
|
- <text class="td-price">{{ formatPrice(stock.addPrice) }}</text>
|
|
|
- <text :class="['td-profit', getProfitClass(stock.profitPercent)]">
|
|
|
- {{ stock.profitPercent || '--' }}
|
|
|
- </text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
@@ -111,11 +161,13 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref } from 'vue'
|
|
|
+import { ref, nextTick, getCurrentInstance, reactive } from 'vue'
|
|
|
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
|
|
|
import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
|
|
|
import { getStockQuotes, getIndexQuote, getUserStocks, deleteUserStock } from '../../utils/api.js'
|
|
|
-import StockListItem from '../../components/StockListItem.vue'
|
|
|
+
|
|
|
+// 保存组件实例
|
|
|
+let componentInstance = null
|
|
|
|
|
|
const isLoggedIn = ref(false)
|
|
|
const myStocks = ref([])
|
|
|
@@ -123,6 +175,74 @@ const viewMode = ref('list') // 'list' 或 'table'
|
|
|
const isLoading = ref(false) // 加载状态
|
|
|
const lastLoadTime = ref(0) // 上次加载时间
|
|
|
const CACHE_DURATION = 5000 // 缓存有效期5秒
|
|
|
+const isPageVisible = ref(false) // 页面是否可见
|
|
|
+
|
|
|
+// 滑动删除相关
|
|
|
+const SLIDE_WIDTH = 100 // 滑动距离(三分之一宽度约100rpx转换)
|
|
|
+const slideStates = reactive({}) // 每个股票的滑动状态
|
|
|
+let slideTimers = {} // 自动还原计时器
|
|
|
+const AUTO_RESET_DELAY = 2000 // 2秒后自动还原
|
|
|
+
|
|
|
+// 初始化滑动状态
|
|
|
+const initSlideState = (code) => {
|
|
|
+ if (!slideStates[code]) {
|
|
|
+ slideStates[code] = { x: 0, currentX: 0 }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理滑动变化
|
|
|
+const handleSlideChange = (e, code) => {
|
|
|
+ initSlideState(code)
|
|
|
+ slideStates[code].currentX = e.detail.x
|
|
|
+}
|
|
|
+
|
|
|
+// 处理滑动结束
|
|
|
+const handleSlideEnd = (code) => {
|
|
|
+ initSlideState(code)
|
|
|
+ const state = slideStates[code]
|
|
|
+
|
|
|
+ // 滑动超过20px就显示删除按钮(吸附到三分之一处)
|
|
|
+ if (state.currentX < -20) {
|
|
|
+ state.x = -SLIDE_WIDTH
|
|
|
+ // 启动自动还原计时器(2秒)
|
|
|
+ startAutoResetTimer(code)
|
|
|
+ } else {
|
|
|
+ // 滑回初始位置
|
|
|
+ resetSlide(code)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 重置滑动位置
|
|
|
+const resetSlide = (code) => {
|
|
|
+ if (slideStates[code]) {
|
|
|
+ slideStates[code].x = 0
|
|
|
+ slideStates[code].currentX = 0
|
|
|
+ }
|
|
|
+ clearSlideTimer(code)
|
|
|
+}
|
|
|
+
|
|
|
+// 启动自动还原计时器
|
|
|
+const startAutoResetTimer = (code) => {
|
|
|
+ clearSlideTimer(code)
|
|
|
+ slideTimers[code] = setTimeout(() => {
|
|
|
+ resetSlide(code)
|
|
|
+ }, AUTO_RESET_DELAY) // 2秒后自动还原
|
|
|
+}
|
|
|
+
|
|
|
+// 清除计时器
|
|
|
+const clearSlideTimer = (code) => {
|
|
|
+ if (slideTimers[code]) {
|
|
|
+ clearTimeout(slideTimers[code])
|
|
|
+ delete slideTimers[code]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 清除所有计时器
|
|
|
+const clearAllSlideTimers = () => {
|
|
|
+ Object.keys(slideTimers).forEach(code => {
|
|
|
+ clearSlideTimer(code)
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 设置视图模式
|
|
|
const setViewMode = (mode) => {
|
|
|
@@ -197,6 +317,117 @@ const getMarketClass = (code) => {
|
|
|
return 'market-sh'
|
|
|
}
|
|
|
|
|
|
+// 绘制单个股票的趋势图
|
|
|
+const drawTrendChart = (stock) => {
|
|
|
+ if (!componentInstance) return
|
|
|
+
|
|
|
+ const canvasId = 'chart-' + stock.code
|
|
|
+ let trendData = stock.trendData
|
|
|
+
|
|
|
+ // 如果没有趋势数据,生成模拟数据
|
|
|
+ if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
|
|
|
+ trendData = generateMockTrendData(stock.changePercent)
|
|
|
+ }
|
|
|
+
|
|
|
+ const ctx = uni.createCanvasContext(canvasId, componentInstance)
|
|
|
+
|
|
|
+ const width = 100
|
|
|
+ const height = 30
|
|
|
+ const padding = 2
|
|
|
+
|
|
|
+ const maxValue = Math.max(...trendData)
|
|
|
+ const minValue = Math.min(...trendData)
|
|
|
+ const dataRange = maxValue - minValue
|
|
|
+ const avgValue = (maxValue + minValue) / 2
|
|
|
+ const minRange = avgValue * 0.03 || 1
|
|
|
+ const range = Math.max(dataRange, minRange)
|
|
|
+
|
|
|
+ const baseValue = trendData[0]
|
|
|
+ const baseY = height - padding - ((baseValue - minValue) / range) * (height - padding * 2)
|
|
|
+
|
|
|
+ const changePercent = parseFloat(String(stock.changePercent || '0').replace('%', '').replace('+', ''))
|
|
|
+ const isUp = changePercent >= 0
|
|
|
+ const lineColor = isUp ? '#ef4444' : '#22c55e'
|
|
|
+ const fillColor = isUp ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'
|
|
|
+
|
|
|
+ // 绘制基准虚线
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.setStrokeStyle('#e5e7eb')
|
|
|
+ ctx.setLineWidth(0.5)
|
|
|
+ ctx.setLineDash([2, 2], 0)
|
|
|
+ ctx.moveTo(padding, baseY)
|
|
|
+ ctx.lineTo(width - padding, baseY)
|
|
|
+ ctx.stroke()
|
|
|
+ ctx.setLineDash([], 0)
|
|
|
+
|
|
|
+ // 绘制填充区域
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(padding, baseY)
|
|
|
+
|
|
|
+ trendData.forEach((value, index) => {
|
|
|
+ const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
|
|
|
+ const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
|
|
|
+ ctx.lineTo(x, y)
|
|
|
+ })
|
|
|
+
|
|
|
+ ctx.lineTo(width - padding, baseY)
|
|
|
+ ctx.closePath()
|
|
|
+ ctx.setFillStyle(fillColor)
|
|
|
+ ctx.fill()
|
|
|
+
|
|
|
+ // 绘制折线
|
|
|
+ ctx.beginPath()
|
|
|
+
|
|
|
+ trendData.forEach((value, index) => {
|
|
|
+ const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
|
|
|
+ const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
|
|
|
+
|
|
|
+ if (index === 0) {
|
|
|
+ ctx.moveTo(x, y)
|
|
|
+ } else {
|
|
|
+ ctx.lineTo(x, y)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ ctx.setStrokeStyle(lineColor)
|
|
|
+ ctx.setLineWidth(1.5)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+}
|
|
|
+
|
|
|
+// 生成模拟趋势数据
|
|
|
+const generateMockTrendData = (changePercent) => {
|
|
|
+ const change = parseFloat(String(changePercent || '0').replace('%', '').replace('+', ''))
|
|
|
+ const points = 15
|
|
|
+ const data = []
|
|
|
+
|
|
|
+ let baseValue = 100
|
|
|
+ const trend = change / 100
|
|
|
+
|
|
|
+ for (let i = 0; i < points; i++) {
|
|
|
+ const randomChange = (Math.random() - 0.5) * 6
|
|
|
+ const trendChange = (i / points) * trend * 100
|
|
|
+ baseValue = baseValue + randomChange + trendChange / points
|
|
|
+ data.push(baseValue)
|
|
|
+ }
|
|
|
+
|
|
|
+ return data
|
|
|
+}
|
|
|
+
|
|
|
+// 绘制所有股票的趋势图
|
|
|
+const drawAllTrendCharts = () => {
|
|
|
+ if (myStocks.value.length === 0) return
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ myStocks.value.forEach(stock => {
|
|
|
+ drawTrendChart(stock)
|
|
|
+ })
|
|
|
+ }, 300)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
// 加载我的股票列表(从服务器数据库查询)
|
|
|
const loadMyStocks = async (forceRefresh = false) => {
|
|
|
console.log('[我的股票] loadMyStocks 开始执行, isLoggedIn=', isLoggedIn.value, 'forceRefresh=', forceRefresh)
|
|
|
@@ -265,6 +496,8 @@ const loadMyStocks = async (forceRefresh = false) => {
|
|
|
// 如果有股票数据,刷新行情
|
|
|
if (myStocks.value.length > 0) {
|
|
|
await refreshAllQuotes()
|
|
|
+ // 绘制趋势图
|
|
|
+ drawAllTrendCharts()
|
|
|
}
|
|
|
|
|
|
// 登录后启动定时刷新
|
|
|
@@ -320,12 +553,24 @@ const refreshAllQuotes = async () => {
|
|
|
|
|
|
// 启动定时刷新
|
|
|
const startAutoRefresh = () => {
|
|
|
- if (!isLoggedIn.value) return
|
|
|
+ if (!isLoggedIn.value || !isPageVisible.value) return
|
|
|
stopAutoRefresh()
|
|
|
|
|
|
const scheduleNextRefresh = () => {
|
|
|
+ // 每次刷新前检查页面是否可见
|
|
|
+ if (!isPageVisible.value) {
|
|
|
+ stopAutoRefresh()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
const delay = 3000 + Math.random() * 1000
|
|
|
refreshTimer = setTimeout(async () => {
|
|
|
+ // 再次检查页面是否可见
|
|
|
+ if (!isPageVisible.value) {
|
|
|
+ stopAutoRefresh()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
await fetchIndexData()
|
|
|
if (myStocks.value.length > 0) {
|
|
|
await refreshAllQuotes()
|
|
|
@@ -350,42 +595,14 @@ const goToLogin = () => {
|
|
|
uni.navigateTo({ url: '/pages/login/login' })
|
|
|
}
|
|
|
|
|
|
-// 点击股票项
|
|
|
-const handleStockClick = async (stockItem, idx) => {
|
|
|
- console.log('点击股票:', stockItem.name, idx)
|
|
|
- // 刷新该股票的最新行情
|
|
|
- try {
|
|
|
- const quoteRes = await getStockQuotes(stockItem.code)
|
|
|
- if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
|
|
|
- const quoteData = quoteRes.data[0]
|
|
|
- const stock = myStocks.value[idx]
|
|
|
- if (stock) {
|
|
|
- stock.priceChange = quoteData.priceChange
|
|
|
- stock.changePercent = quoteData.changePercent
|
|
|
- stock.currentPrice = quoteData.currentPrice
|
|
|
- stock.name = quoteData.stockName || stock.name
|
|
|
- stock.trendData = quoteData.trendData || null
|
|
|
-
|
|
|
- // 计算自选收益
|
|
|
- if (stock.addPrice && quoteData.currentPrice) {
|
|
|
- const addPrice = parseFloat(stock.addPrice)
|
|
|
- const currentPrice = parseFloat(quoteData.currentPrice)
|
|
|
- if (addPrice > 0) {
|
|
|
- const profit = ((currentPrice - addPrice) / addPrice * 100).toFixed(2)
|
|
|
- stock.profitPercent = profit >= 0 ? `+${profit}%` : `${profit}%`
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('刷新股票行情失败:', e)
|
|
|
- }
|
|
|
- uni.showToast({ title: '股票详情功能开发中', icon: 'none' })
|
|
|
-}
|
|
|
+
|
|
|
|
|
|
// 删除股票
|
|
|
const removeStock = async (idx) => {
|
|
|
const stock = myStocks.value[idx]
|
|
|
+ // 先重置滑动状态
|
|
|
+ resetSlide(stock.code)
|
|
|
+
|
|
|
uni.showModal({
|
|
|
title: '确认删除',
|
|
|
content: `确定要删除 ${stock.name} 吗?`,
|
|
|
@@ -396,6 +613,9 @@ const removeStock = async (idx) => {
|
|
|
try {
|
|
|
// 调用服务器删除接口
|
|
|
await deleteUserStock(stock.code)
|
|
|
+ // 清理滑动状态
|
|
|
+ delete slideStates[stock.code]
|
|
|
+ clearSlideTimer(stock.code)
|
|
|
// 从本地列表删除
|
|
|
myStocks.value.splice(idx, 1)
|
|
|
uni.showToast({ title: '删除成功', icon: 'success' })
|
|
|
@@ -414,12 +634,15 @@ const removeStock = async (idx) => {
|
|
|
|
|
|
onLoad(() => {
|
|
|
console.log('[我的股票] onLoad 触发')
|
|
|
+ componentInstance = getCurrentInstance()
|
|
|
isLoggedIn.value = checkLoginStatus()
|
|
|
+ isPageVisible.value = true
|
|
|
loadMyStocks(true) // 首次加载强制刷新
|
|
|
})
|
|
|
|
|
|
onShow(() => {
|
|
|
console.log('[我的股票] onShow 触发')
|
|
|
+ isPageVisible.value = true
|
|
|
const wasLoggedIn = isLoggedIn.value
|
|
|
isLoggedIn.value = checkLoginStatus()
|
|
|
|
|
|
@@ -435,11 +658,16 @@ onShow(() => {
|
|
|
})
|
|
|
|
|
|
onHide(() => {
|
|
|
+ console.log('[我的股票] onHide 触发')
|
|
|
+ isPageVisible.value = false
|
|
|
stopAutoRefresh()
|
|
|
})
|
|
|
|
|
|
onUnload(() => {
|
|
|
+ console.log('[我的股票] onUnload 触发')
|
|
|
+ isPageVisible.value = false
|
|
|
stopAutoRefresh()
|
|
|
+ clearAllSlideTimers()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -583,78 +811,129 @@ onUnload(() => {
|
|
|
display: none !important;
|
|
|
}
|
|
|
|
|
|
-/* 股票列表 */
|
|
|
-.stock-list {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
+/* 股票列表卡片 */
|
|
|
+.stock-list-card,
|
|
|
+.stock-table-card {
|
|
|
background: #ffffff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 0 32rpx;
|
|
|
- box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
|
|
|
-}
|
|
|
-
|
|
|
-/* 表格视图 */
|
|
|
-.stock-table {
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 0 24rpx;
|
|
|
- box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
|
|
|
+ border-radius: 32rpx;
|
|
|
+ padding: 32rpx;
|
|
|
+ box-shadow: 0 16rpx 48rpx rgba(37, 52, 94, 0.1);
|
|
|
}
|
|
|
|
|
|
-.table-header {
|
|
|
+.list-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- padding: 24rpx 0;
|
|
|
- border-bottom: 1rpx solid #f1f2f6;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
}
|
|
|
|
|
|
-.table-header text {
|
|
|
+.list-dot {
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #3abf81;
|
|
|
+ margin-right: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.list-dot-blue {
|
|
|
+ background: #5B5AEA;
|
|
|
+}
|
|
|
+
|
|
|
+.list-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a2e;
|
|
|
+ letter-spacing: 1rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.list-count {
|
|
|
font-size: 24rpx;
|
|
|
- color: #999999;
|
|
|
- font-weight: 500;
|
|
|
+ color: #9ca3af;
|
|
|
+ margin-left: 12rpx;
|
|
|
}
|
|
|
|
|
|
-.th-name {
|
|
|
- flex: 1;
|
|
|
+/* 股票列表 */
|
|
|
+.stock-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20rpx;
|
|
|
}
|
|
|
|
|
|
-.th-date {
|
|
|
- width: 160rpx;
|
|
|
- text-align: center;
|
|
|
+/* 滑动删除容器 */
|
|
|
+.stock-item-wrapper {
|
|
|
+ position: relative;
|
|
|
+ height: 140rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ background: #fafbfc;
|
|
|
}
|
|
|
|
|
|
-.th-price {
|
|
|
- width: 140rpx;
|
|
|
- text-align: center;
|
|
|
+.movable-area {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
}
|
|
|
|
|
|
-.th-profit {
|
|
|
- width: 140rpx;
|
|
|
- text-align: right;
|
|
|
+.movable-view {
|
|
|
+ width: calc(100% + 200rpx);
|
|
|
+ height: 100%;
|
|
|
}
|
|
|
|
|
|
-.table-row {
|
|
|
+.stock-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- padding: 24rpx 0;
|
|
|
- border-bottom: 1rpx solid #f1f2f6;
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ height: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
-.table-row:last-child {
|
|
|
- border-bottom: none;
|
|
|
+/* 滑动删除按钮 */
|
|
|
+.slide-delete-btn {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 200rpx;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #fafbfc;
|
|
|
}
|
|
|
|
|
|
-.td-name {
|
|
|
+.delete-icon-circle {
|
|
|
+ width: 72rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ background: #ef4444;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 6rpx 16rpx rgba(239, 68, 68, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.delete-icon-text {
|
|
|
+ font-size: 40rpx;
|
|
|
+ color: #ffffff;
|
|
|
+ font-weight: bold;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.stock-main {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.stock-info {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
+ min-width: 140rpx;
|
|
|
}
|
|
|
|
|
|
-.td-name .stock-name {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #222222;
|
|
|
- margin-bottom: 4rpx;
|
|
|
+.stock-name {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a2e;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+ letter-spacing: 1rpx;
|
|
|
}
|
|
|
|
|
|
.stock-code-row {
|
|
|
@@ -664,57 +943,165 @@ onUnload(() => {
|
|
|
|
|
|
.stock-tag {
|
|
|
font-size: 18rpx;
|
|
|
- padding: 2rpx 6rpx;
|
|
|
- border-radius: 4rpx;
|
|
|
+ padding: 2rpx 8rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
color: #ffffff;
|
|
|
- font-weight: 500;
|
|
|
+ font-weight: 600;
|
|
|
margin-right: 8rpx;
|
|
|
}
|
|
|
|
|
|
.market-sh {
|
|
|
- background: #FF3B30;
|
|
|
+ background: #ef4444;
|
|
|
}
|
|
|
|
|
|
.market-sz {
|
|
|
- background: #34C759;
|
|
|
+ background: #22c55e;
|
|
|
}
|
|
|
|
|
|
.market-cy {
|
|
|
- background: #FF9500;
|
|
|
+ background: #f59e0b;
|
|
|
}
|
|
|
|
|
|
.stock-code {
|
|
|
font-size: 22rpx;
|
|
|
- color: #9ca2b5;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+
|
|
|
+.stock-quote {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 趋势图 */
|
|
|
+.stock-chart {
|
|
|
+ flex: 1;
|
|
|
+ height: 60rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin: 0 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-canvas {
|
|
|
+ width: 200rpx;
|
|
|
+ height: 60rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.stock-price {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #1a1a2e;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.stock-change {
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 6rpx 12rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格视图 */
|
|
|
+.table-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20rpx 16rpx;
|
|
|
+ background: #f8f9fc;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.table-header text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #9ca3af;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.th-name {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.th-date {
|
|
|
+ width: 140rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.th-price {
|
|
|
+ width: 120rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.th-profit {
|
|
|
+ width: 120rpx;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.table-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.table-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 24rpx 16rpx;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-radius: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.td-name {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.td-name .stock-name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1a1a2e;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
}
|
|
|
|
|
|
.td-date {
|
|
|
- width: 160rpx;
|
|
|
+ width: 140rpx;
|
|
|
text-align: center;
|
|
|
font-size: 24rpx;
|
|
|
- color: #666666;
|
|
|
+ color: #6b7280;
|
|
|
}
|
|
|
|
|
|
.td-price {
|
|
|
- width: 140rpx;
|
|
|
+ width: 120rpx;
|
|
|
text-align: center;
|
|
|
font-size: 26rpx;
|
|
|
- color: #333333;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1a1a2e;
|
|
|
+}
|
|
|
+
|
|
|
+.td-profit-wrap {
|
|
|
+ width: 120rpx;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
.td-profit {
|
|
|
- width: 140rpx;
|
|
|
- text-align: right;
|
|
|
- font-size: 26rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
font-weight: 600;
|
|
|
+ padding: 6rpx 12rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
}
|
|
|
|
|
|
.profit-up {
|
|
|
- color: #FF3B30;
|
|
|
+ color: #ef4444;
|
|
|
+ background: rgba(239, 68, 68, 0.1);
|
|
|
}
|
|
|
|
|
|
.profit-down {
|
|
|
- color: #34C759;
|
|
|
+ color: #22c55e;
|
|
|
+ background: rgba(34, 197, 94, 0.1);
|
|
|
}
|
|
|
|
|
|
/* 空状态 */
|