pages_xunlian.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import React, { useState } from "react";
  2. import dayjs from "dayjs";
  3. import { useQuery } from "@tanstack/react-query";
  4. import type { XunlianCode } from "../share/types_stock.ts";
  5. import { useNavigate } from "react-router";
  6. import { XunlianCodeAPI } from "./api/xunlian_codes.ts";
  7. import { useAuth } from "./hooks.tsx";
  8. import { TeacherView } from "./components/Classroom/TeacherView.tsx";
  9. import { StudentView } from "./components/Classroom/StudentView.tsx";
  10. export function XunlianPage() {
  11. const [visibleStocks, setVisibleStocks] = useState<Record<number, boolean>>({});
  12. const navigate = useNavigate();
  13. const { data: codes = [], isLoading } = useQuery({
  14. queryKey: ["xunlian-codes"],
  15. queryFn: async (): Promise<XunlianCode[]> => {
  16. const response = await XunlianCodeAPI.getXunlianCodes({});
  17. return response.data;
  18. },
  19. });
  20. const toggleStockVisibility = (id: number, e: React.MouseEvent) => {
  21. e.stopPropagation();
  22. setVisibleStocks((prev: Record<number, boolean>) => ({
  23. ...prev,
  24. [id]: !prev[id]
  25. }));
  26. };
  27. const handleCardClick = (code: XunlianCode) => {
  28. navigate(`/mobile/stock?code=${code.code}`);
  29. };
  30. const { user } = useAuth();
  31. if (user?.role === 'admin') {
  32. return <TeacherView />;
  33. }
  34. return (
  35. <div className="p-4">
  36. <div className="flex justify-between items-center mb-4">
  37. <h1 className="text-2xl font-bold">训练案例</h1>
  38. </div>
  39. <div className="grid gap-4">
  40. {isLoading ? (
  41. <div className="text-center text-gray-500 py-8">
  42. <div className="animate-spin inline-block w-6 h-6 border-[3px] border-current border-t-transparent text-blue-600 rounded-full" role="status" aria-label="loading">
  43. <span className="sr-only">加载中...</span>
  44. </div>
  45. <div className="mt-2">加载中...</div>
  46. </div>
  47. ) : codes.length === 0 ? (
  48. <div className="text-center text-gray-500 py-8">
  49. 暂无训练案例
  50. </div>
  51. ) : (
  52. codes.map((code: XunlianCode) => (
  53. <div
  54. key={code.id}
  55. className="border rounded-lg p-4 hover:shadow-lg transition-shadow cursor-pointer"
  56. onClick={() => handleCardClick(code)}
  57. >
  58. <div className="flex justify-between items-start">
  59. <div>
  60. <h2 className="text-lg font-semibold flex items-center gap-2">
  61. {visibleStocks[code.id] ? (
  62. <>
  63. {code.stock_name} {code.name} ({code.code})
  64. <button
  65. onClick={(e) => toggleStockVisibility(code.id, e)}
  66. className="text-gray-500 hover:text-gray-700"
  67. title="隐藏股票信息"
  68. >
  69. <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5">
  70. <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
  71. <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
  72. </svg>
  73. </button>
  74. </>
  75. ) : (
  76. <>
  77. {code.name}
  78. <button
  79. onClick={(e) => toggleStockVisibility(code.id, e)}
  80. className="text-gray-400 hover:text-gray-600"
  81. title="显示股票信息"
  82. >
  83. <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5">
  84. <path strokeLinecap="round" strokeLinejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
  85. </svg>
  86. </button>
  87. </>
  88. )}
  89. </h2>
  90. {code.description && (
  91. <p className="text-gray-600 mt-1">{code.description}</p>
  92. )}
  93. </div>
  94. <div className="text-sm text-gray-500">
  95. <div>{dayjs(code.trade_date).format("YYYY-MM-DD")}</div>
  96. </div>
  97. </div>
  98. {code.type && (
  99. <div className="mt-2">
  100. <span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">
  101. {code.type}
  102. </span>
  103. </div>
  104. )}
  105. </div>
  106. ))
  107. )}
  108. </div>
  109. </div>
  110. );
  111. }