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.

398 lines
12 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.stockName ?? item.name ?? item.tsName ?? item.tsname ?? item.secName ?? '',
  154. stockCode: item.tsCode ?? item.tscode ?? item.code ?? item.symbol ?? '',
  155. latest: item.close ?? item.lastClose ?? '',
  156. increase: formatPctChg(item.pctChg),
  157. decrease: item.change ?? item.chg ?? '',
  158. previousClose: item.preClose ?? item.preclose ?? item.prevClose ?? '',
  159. volume: item.vol ?? item.volume ?? '',
  160. turnover: item.amount ?? item.turnover ?? '',
  161. openingPrice: item.open ?? item.openPrice ?? '',
  162. highestPrice: item.high ?? item.highPrice ?? '',
  163. lowestPrice: item.low ?? item.lowPrice ?? ''
  164. }))
  165. console.log(`按名称(${apiName})加载成功,条数:`, strategyData.value.length, '首项:', strategyData.value[0])
  166. if (!strategyData.value[0]?.name) {
  167. console.warn('名称字段未命中,原始keys示例:', Object.keys(list[0] || {}))
  168. }
  169. } else {
  170. console.warn('getStrategyByName 返回空列表或结构不匹配', raw)
  171. }
  172. } catch (e) {
  173. console.error('getStrategyByName 接口调用失败', e)
  174. uni.showToast({ title: '按名称加载失败', icon: 'none' })
  175. }
  176. }
  177. const loadStrategy = async () => {
  178. try {
  179. const userStore = useUserStore()
  180. if (!userStore.userInfo?.token) {
  181. await ensureAuth()
  182. }
  183. const token = useUserStore().userInfo?.token
  184. const res = await stocSelectApi({ language: 'cn', token })
  185. const raw = res?.data
  186. const listCandidates = [
  187. raw?.list,
  188. raw?.data?.list,
  189. raw?.data?.rows,
  190. raw?.rows,
  191. Array.isArray(raw) ? raw : null
  192. ].filter(Array.isArray)
  193. let list = listCandidates.length ? listCandidates[0] : []
  194. if ((!Array.isArray(list) || !list.length) && raw && typeof raw === 'object' && !Array.isArray(raw)) {
  195. const arrays = Object.values(raw).filter(Array.isArray)
  196. if (arrays.length) list = arrays.flat()
  197. }
  198. // 排序:涨幅由大到小
  199. if (Array.isArray(list)) list = sortByPctDesc(list)
  200. if (Array.isArray(list) && list.length) {
  201. strategyData.value = list.map(item => ({
  202. name: item.stockName ?? item.name ?? item.tsName ?? item.tsname ?? item.secName ?? '',
  203. stockCode: item.tsCode ?? item.tscode ?? item.code ?? item.symbol ?? '',
  204. latest: item.close ?? item.lastClose ?? '',
  205. increase: formatPctChg(item.pctChg),
  206. decrease: item.change ?? item.chg ?? '',
  207. previousClose: item.preClose ?? item.preclose ?? item.prevClose ?? '',
  208. volume: item.vol ?? item.volume ?? '',
  209. turnover: item.amount ?? item.turnover ?? '',
  210. openingPrice: item.open ?? item.openPrice ?? '',
  211. highestPrice: item.high ?? item.highPrice ?? '',
  212. lowestPrice: item.low ?? item.lowPrice ?? ''
  213. }))
  214. console.log('stockSelectDetail 加载成功(已按涨幅降序),条数:', strategyData.value.length, '首项:', strategyData.value[0])
  215. if (!strategyData.value[0]?.name) {
  216. console.warn('名称字段未命中,原始keys示例:', Object.keys(list[0] || {}))
  217. }
  218. } else {
  219. console.warn('stockSelectDetail 接口返回空列表或结构不匹配', raw)
  220. }
  221. } catch (e) {
  222. console.error('stockSelectDetail 接口调用失败', e)
  223. uni.showToast({ title: '选股策略详情加载失败', icon: 'none' })
  224. }
  225. }
  226. onMounted(() => {
  227. loadStrategy()
  228. getUserInfo()
  229. })
  230. </script>
  231. <style scoped lang="scss">
  232. .main {
  233. width: 100%;
  234. height: 100vh;
  235. background-color: #fff;
  236. .table {
  237. margin-top: 10rpx;
  238. box-shadow: 0 -2rpx 3rpx -1rpx rgba(0, 0, 0, 0.5);
  239. .tableHeader {
  240. .tabs {
  241. white-space: nowrap;
  242. padding-top: 20rpx;
  243. padding-left: 40rpx;
  244. ::-webkit-scrollbar {
  245. //隐藏 滚动条
  246. display: none;
  247. }
  248. .tabItem {
  249. display: inline-block;
  250. color: rgb(175, 175, 175);
  251. border-radius: 10rpx;
  252. padding: 5rpx 30rpx;
  253. margin-right: 20rpx;
  254. font-size: 28rpx;
  255. background-color: rgb(243, 243, 243);
  256. }
  257. .tabItem-active {
  258. background-color: #DB1F1D; // 红色
  259. color: #fff;
  260. }
  261. }
  262. }
  263. .tableContent {
  264. width: 100%;
  265. background-color: #fff;
  266. position: relative;
  267. .showAll {
  268. position: absolute;
  269. top: 35rpx;
  270. right: 20rpx;
  271. width: 40rpx;
  272. height: 40rpx;
  273. z-index: 100;
  274. }
  275. scroll-view {
  276. width: 100%;
  277. white-space: nowrap;
  278. ::-webkit-scrollbar {
  279. //隐藏 滚动条
  280. display: none;
  281. }
  282. }
  283. .tableBox {
  284. padding-left: 40rpx;
  285. .box_header {
  286. margin-bottom: 19rpx;
  287. display: flex;
  288. width: max-content;
  289. margin-top: 40rpx;
  290. color: rgb(109, 109, 109);
  291. border-radius: 10rpx;
  292. margin-right: 20rpx;
  293. font-size: 23rpx;
  294. .name {
  295. flex: 0 0 375rpx;
  296. }
  297. .other {
  298. flex: 0 0 195rpx;
  299. text {
  300. margin-right: 5rpx;
  301. }
  302. image {
  303. width: 20rpx;
  304. height: 20rpx;
  305. }
  306. }
  307. }
  308. .box_content {
  309. width: max-content;
  310. .row {
  311. padding: 5rpx;
  312. display: flex;
  313. border-top: 1rpx dashed #eee;
  314. width: 210%;
  315. &.increase-positive {
  316. .other_colum {
  317. color: #2DD357;
  318. font-weight: 200;
  319. }
  320. }
  321. &.increase-negative {
  322. .other_colum {
  323. color: #FF4150;
  324. font-weight: 200;
  325. }
  326. }
  327. .name_colum {
  328. flex: 0 0 375rpx;
  329. display: flex;
  330. flex-direction: column;
  331. gap: 4rpx;
  332. .stockName {
  333. color: #333333;
  334. width: 100%;
  335. max-width: 305rpx;
  336. font-size: 28rpx;
  337. font-weight: 400;
  338. line-height: 36rpx;
  339. white-space: nowrap;
  340. overflow: hidden;
  341. text-overflow: ellipsis;
  342. }
  343. .stockCode {
  344. color: #c5c5c5;
  345. ;
  346. font-size: 24rpx;
  347. font-weight: 400;
  348. line-height: 30rpx;
  349. }
  350. }
  351. .other_colum {
  352. flex: 0 0 195rpx;
  353. display: flex;
  354. align-items: center;
  355. }
  356. }
  357. }
  358. }
  359. }
  360. }
  361. }
  362. </style>