You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
11 KiB

4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
  1. <template>
  2. <LoginPrompt ref="loginPrompt"></LoginPrompt>
  3. <view class="main">
  4. <view class="table">
  5. <view class="tableHeader">
  6. <scroll-view class="tabs" scroll-x="true">
  7. <view v-for="(item,index) in tabsData" :key="index" :class="['tabItem', { 'tabItem-active': item === activeTab }]" @click="handleTab(item)">
  8. {{item}}
  9. </view>
  10. </scroll-view>
  11. </view>
  12. <view class="tableContent">
  13. <image class="showAll" src="/static/deepExploration-images/showAll.png" mode="aspectFill"></image>
  14. <scroll-view scroll-x="true" show-scrollbar="false">
  15. <view class="tableBox">
  16. <view class="box_header">
  17. <view class="name">名称</view>
  18. <view class="other" v-for="(item,index) in tableContentHeaderData" :key="index">
  19. <text>{{item}}</text>
  20. <image v-show="ifASC" src="/static/deepExploration-images/ASC.png" mode="aspectFill">
  21. </image>
  22. <image v-show="!ifASC" src="/static/deepExploration-images/DESC.png" mode="aspectFill">
  23. </image>
  24. </view>
  25. </view>
  26. <view class="box_content">
  27. <view class="row" v-for="(item,index) in strategyData" :key="index" :class="{ 'increase-positive': item.increase.startsWith('+'),
  28. 'increase-negative': item.increase.startsWith('-')}">
  29. <view class="name_colum">
  30. <text class="stockName">{{item.name}}</text>
  31. <text class="stockCode">{{item.stockCode}}</text>
  32. </view>
  33. <view class="other_colum">{{item.latest}}</view>
  34. <view class="other_colum">{{item.increase}}</view>
  35. <view class="other_colum">{{item.decrease}}</view>
  36. <view class="other_colum">{{item.previousClose}}</view>
  37. <view class="other_colum">{{item.volume}}</view>
  38. <view class="other_colum">{{item.turnover}}</view>
  39. <view class="other_colum">{{item.openingPrice}}</view>
  40. <view class="other_colum">{{item.highestPrice}}</view>
  41. <view class="other_colum">{{item.lowestPrice}}</view>
  42. </view>
  43. </view>
  44. </view>
  45. </scroll-view>
  46. </view>
  47. </view>
  48. </view>
  49. </template>
  50. <script setup>
  51. import { ref, onMounted } from 'vue'
  52. import { stocSelectApi, stocSelectByNameApi } from '@/api/deepExploration/deepExploration.js'
  53. import { useUserStore } from '@/stores/modules/userInfo.js'
  54. import { useDeviceStore } from '@/stores/modules/deviceInfo.js'
  55. import { LoginApi } from '@/api/start/login.js'
  56. import {
  57. getUserInfo
  58. } from "@/api/member"
  59. const tabsData = ref(['全部', '抄底卖顶', '波段行情', '价值投资', '资金及仓位管理', ])
  60. const activeTab = ref('全部')
  61. // 点击标签:激活并按需触发名称查询
  62. const handleTab = async (item) => {
  63. activeTab.value = item
  64. const nameMap = {
  65. '抄底卖顶': '北京',
  66. '波段行情': '安徽',
  67. '价值投资': '重庆',
  68. '资金及仓位管理': '黑龙江'
  69. }
  70. if (item === '全部') {
  71. await loadStrategy()
  72. uni.showToast({ title: `查看 ${item} 详情`, icon: 'none', duration: 1500 })
  73. return
  74. }
  75. const apiName = nameMap[item]
  76. if (apiName) {
  77. await loadByName(apiName)
  78. uni.showToast({ title: `${item}数据已更新`, icon: 'none', duration: 1500 })
  79. } else {
  80. uni.showToast({ title: `暂不支持:${item}`, icon: 'none' })
  81. }
  82. }
  83. //表头是否升序
  84. const ifASC = ref(true)
  85. //表头数据
  86. const tableContentHeaderData = ref(['最新', '涨幅', '涨跌', '昨收', '成交量', '成交额', '开盘价', '最高价', '最低价'])
  87. // 渲染用数据
  88. const strategyData = ref([])
  89. // 游客登录兜底,确保移动端有token
  90. const ensureAuth = async () => {
  91. const userStore = useUserStore()
  92. if (userStore.userInfo?.token) return
  93. try {
  94. const deviceStore = useDeviceStore()
  95. let deviceId = deviceStore.deviceInfo?.deviceId
  96. if (!deviceId) {
  97. const cached = uni.getStorageSync('deviceId')
  98. deviceId = cached || `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
  99. uni.setStorageSync('deviceId', deviceId)
  100. deviceStore.setDeviceInfo({ ...(deviceStore.deviceInfo || {}), deviceId })
  101. }
  102. const res = await LoginApi({
  103. loginType: 'VISITOR',
  104. account: deviceId,
  105. useCode: false
  106. })
  107. if (res?.code === 200 && res?.data?.token) {
  108. userStore.setUserInfo(res.data)
  109. console.log('游客登录成功,token=', res.data.token)
  110. } else {
  111. console.warn('游客登录失败', res)
  112. }
  113. } catch (err) {
  114. console.warn('游客登录异常', err)
  115. }
  116. }
  117. const formatPctChg = (val) => {
  118. if (val === null || val === undefined || val === '') return ''
  119. const num = Number(val)
  120. if (!isFinite(num)) return String(val)
  121. const sign = num > 0 ? '+' : ''
  122. return `${sign}${num.toFixed(2)}%`
  123. }
  124. // 默认根据涨幅( pctChg )由大到小排序
  125. const sortByPctDesc = (arr) => arr.sort((a, b) => (Number(b.pctChg) || -Infinity) - (Number(a.pctChg) || -Infinity))
  126. // 按名称加载(抄底卖顶/波段行情/价值投资/资金及仓位管理)
  127. const loadByName = async (apiName) => {
  128. try {
  129. const userStore = useUserStore()
  130. if (!userStore.userInfo?.token) {
  131. await ensureAuth()
  132. }
  133. const token = useUserStore().userInfo?.token
  134. const res = await stocSelectByNameApi({ name: apiName, token })
  135. const raw = res?.data
  136. const dataObj = raw?.data || raw
  137. let list = []
  138. if (dataObj && typeof dataObj === 'object' && !Array.isArray(dataObj)) {
  139. const target = dataObj[apiName]
  140. if (Array.isArray(target)) list = target
  141. else {
  142. const firstArr = Object.values(dataObj).find(v => Array.isArray(v))
  143. if (Array.isArray(firstArr)) list = firstArr
  144. }
  145. }
  146. if ((!Array.isArray(list) || !list.length) && Array.isArray(raw)) {
  147. list = raw
  148. }
  149. // 排序:涨幅由大到小
  150. if (Array.isArray(list)) list = sortByPctDesc(list)
  151. if (Array.isArray(list) && list.length) {
  152. strategyData.value = list.map(item => ({
  153. name: item.tsCode ?? item.tscode ?? '',
  154. stockCode: item.tsCode ?? item.tscode ?? '',
  155. latest: item.close ?? '',
  156. increase: formatPctChg(item.pctChg),
  157. decrease: item.change ?? '',
  158. previousClose: item.preClose ?? item.preclose ?? '',
  159. volume: item.vol ?? '',
  160. turnover: item.amount ?? '',
  161. openingPrice: item.open ?? '',
  162. highestPrice: item.high ?? '',
  163. lowestPrice: item.low ?? ''
  164. }))
  165. console.log(`按名称(${apiName})加载成功,条数:`, strategyData.value.length, '首项:', strategyData.value[0])
  166. } else {
  167. console.warn('getStrategyByName 返回空列表或结构不匹配', raw)
  168. }
  169. } catch (e) {
  170. console.error('getStrategyByName 接口调用失败', e)
  171. uni.showToast({ title: '按名称加载失败', icon: 'none' })
  172. }
  173. }
  174. const loadStrategy = async () => {
  175. try {
  176. const userStore = useUserStore()
  177. if (!userStore.userInfo?.token) {
  178. await ensureAuth()
  179. }
  180. const token = useUserStore().userInfo?.token
  181. const res = await stocSelectApi({ language: 'cn', token })
  182. const raw = res?.data
  183. const listCandidates = [
  184. raw?.list,
  185. raw?.data?.list,
  186. raw?.data?.rows,
  187. raw?.rows,
  188. Array.isArray(raw) ? raw : null
  189. ].filter(Array.isArray)
  190. let list = listCandidates.length ? listCandidates[0] : []
  191. if ((!Array.isArray(list) || !list.length) && raw && typeof raw === 'object' && !Array.isArray(raw)) {
  192. const arrays = Object.values(raw).filter(Array.isArray)
  193. if (arrays.length) list = arrays.flat()
  194. }
  195. // 排序:涨幅由大到小
  196. if (Array.isArray(list)) list = sortByPctDesc(list)
  197. if (Array.isArray(list) && list.length) {
  198. strategyData.value = list.map(item => ({
  199. name: item.tsCode ?? item.tscode ?? '',
  200. stockCode: item.tsCode ?? item.tscode ?? '',
  201. latest: item.close ?? '',
  202. increase: formatPctChg(item.pctChg),
  203. decrease: item.change ?? '',
  204. previousClose: item.preClose ?? item.preclose ?? '',
  205. volume: item.vol ?? '',
  206. turnover: item.amount ?? '',
  207. openingPrice: item.open ?? '',
  208. highestPrice: item.high ?? '',
  209. lowestPrice: item.low ?? ''
  210. }))
  211. console.log('stockSelectDetail 加载成功(已按涨幅降序),条数:', strategyData.value.length, '首项:', strategyData.value[0])
  212. } else {
  213. console.warn('stockSelectDetail 接口返回空列表或结构不匹配', raw)
  214. }
  215. } catch (e) {
  216. console.error('stockSelectDetail 接口调用失败', e)
  217. uni.showToast({ title: '选股策略详情加载失败', icon: 'none' })
  218. }
  219. }
  220. onMounted(() => {
  221. loadStrategy()
  222. getUserInfo()
  223. })
  224. </script>
  225. <style scoped lang="scss">
  226. .main {
  227. width: 100%;
  228. height: 100vh;
  229. background-color: #fff;
  230. .table {
  231. margin-top: 10rpx;
  232. box-shadow: 0 -2rpx 3rpx -1rpx rgba(0, 0, 0, 0.5);
  233. .tableHeader {
  234. .tabs {
  235. white-space: nowrap;
  236. padding-top: 20rpx;
  237. padding-left: 40rpx;
  238. ::-webkit-scrollbar {
  239. //隐藏 滚动条
  240. display: none;
  241. }
  242. .tabItem {
  243. display: inline-block;
  244. color: rgb(175, 175, 175);
  245. border-radius: 10rpx;
  246. padding: 5rpx 30rpx;
  247. margin-right: 20rpx;
  248. font-size: 28rpx;
  249. background-color: rgb(243, 243, 243);
  250. }
  251. .tabItem-active {
  252. background-color: #DB1F1D; // 红色
  253. color: #fff;
  254. }
  255. }
  256. }
  257. .tableContent {
  258. width: 100%;
  259. background-color: #fff;
  260. position: relative;
  261. .showAll {
  262. position: absolute;
  263. top: 35rpx;
  264. right: 20rpx;
  265. width: 40rpx;
  266. height: 40rpx;
  267. z-index: 100;
  268. }
  269. scroll-view {
  270. width: 100%;
  271. white-space: nowrap;
  272. ::-webkit-scrollbar {
  273. //隐藏 滚动条
  274. display: none;
  275. }
  276. }
  277. .tableBox {
  278. padding-left: 40rpx;
  279. .box_header {
  280. margin-bottom: 19rpx;
  281. display: flex;
  282. width: max-content;
  283. margin-top: 40rpx;
  284. color: rgb(109, 109, 109);
  285. border-radius: 10rpx;
  286. margin-right: 20rpx;
  287. font-size: 23rpx;
  288. .name {
  289. flex: 0 0 375rpx;
  290. }
  291. .other {
  292. flex: 0 0 195rpx;
  293. text {
  294. margin-right: 5rpx;
  295. }
  296. image {
  297. width: 20rpx;
  298. height: 20rpx;
  299. }
  300. }
  301. }
  302. .box_content {
  303. width: max-content;
  304. .row {
  305. padding: 5rpx;
  306. display: flex;
  307. border-top: 1rpx dashed #eee;
  308. width: 210%;
  309. &.increase-positive {
  310. .other_colum {
  311. color: #2DD357;
  312. font-weight: 200;
  313. }
  314. }
  315. &.increase-negative {
  316. .other_colum {
  317. color: #FF4150;
  318. font-weight: 200;
  319. }
  320. }
  321. .name_colum {
  322. flex: 0 0 375rpx;
  323. display: flex;
  324. flex-direction: column;
  325. gap: 4rpx;
  326. .stockName {
  327. color: #333333;
  328. width: 100%;
  329. max-width: 305rpx;
  330. font-size: 28rpx;
  331. font-weight: 400;
  332. line-height: 36rpx;
  333. white-space: nowrap;
  334. overflow: hidden;
  335. text-overflow: ellipsis;
  336. }
  337. .stockCode {
  338. color: #c5c5c5;
  339. ;
  340. font-size: 24rpx;
  341. font-weight: 400;
  342. line-height: 30rpx;
  343. }
  344. }
  345. .other_colum {
  346. flex: 0 0 195rpx;
  347. display: flex;
  348. align-items: center;
  349. }
  350. }
  351. }
  352. }
  353. }
  354. }
  355. }
  356. </style>