SentEditInnerDemo.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /**
  2. * SentEditInnerDemo.tsx
  3. * ──────────────────────────────────────────────────────────────────
  4. * 用于在页面上直观测试 <SentEditInner> 组件的演示页面。
  5. * 所有数据均为 mock,不依赖真实 API。
  6. *
  7. * 使用方式(开发环境临时路由):
  8. * <Route path="/dev/sent-edit-demo" element={<SentEditInnerDemo />} />
  9. * ──────────────────────────────────────────────────────────────────
  10. */
  11. import React, { useState } from "react";
  12. import { ConfigProvider, Space, Switch, Tag, Typography, Divider } from "antd";
  13. import type { ISentence } from "../../api/sentence";
  14. import type { ITocPathNode } from "../../api/pali-text";
  15. import type { IUser } from "../../api/Auth";
  16. import { SentEditInner, type IWidgetSentEditInner } from "./SentEdit";
  17. import type { IWbw } from "../../types/wbw";
  18. const { Title, Text } = Typography;
  19. // ─── Mock 数据 ────────────────────────────────────────────────────
  20. const mockEditor: IUser = {
  21. id: "user-001",
  22. avatar: "",
  23. nickName: "测试用户",
  24. userName: "测试用户",
  25. };
  26. const mockTranslationChannel = {
  27. id: "ch-translation-01",
  28. name: "汉译频道",
  29. type: "translation" as const,
  30. studio: { id: "studio-01", name: "测试工作室" },
  31. };
  32. const mockCommentaryChannel = {
  33. id: "ch-commentary-01",
  34. name: "注释频道",
  35. type: "commentary" as const,
  36. studio: { id: "studio-01", name: "测试工作室" },
  37. };
  38. /** 原文(json 格式的 wbw 数据 + html) */
  39. const mockOrigin: ISentence[] = [
  40. {
  41. id: "origin-001",
  42. book: 1,
  43. para: 1,
  44. wordStart: 1,
  45. wordEnd: 5,
  46. content: JSON.stringify([
  47. {
  48. uid: "w1",
  49. book: 1,
  50. para: 1,
  51. sn: [1],
  52. word: { value: "Evaṃ", status: 7 },
  53. real: { value: "evaṃ", status: 7 },
  54. meaning: { value: "如是", status: 7 },
  55. type: { value: "ind", status: 7 },
  56. grammar: { value: "不变词", status: 7 },
  57. confidence: 1,
  58. },
  59. {
  60. uid: "w2",
  61. book: 1,
  62. para: 1,
  63. sn: [2],
  64. word: { value: "me", status: 7 },
  65. real: { value: "ahaṃ", status: 7 },
  66. meaning: { value: "我", status: 7 },
  67. type: { value: "pron", status: 7 },
  68. grammar: { value: "代词 主格 单数", status: 7 },
  69. confidence: 1,
  70. },
  71. {
  72. uid: "w3",
  73. book: 1,
  74. para: 1,
  75. sn: [3],
  76. word: { value: "sutaṃ", status: 7 },
  77. real: { value: "suta", status: 7 },
  78. meaning: { value: "所闻", status: 7 },
  79. type: { value: "pp", status: 7 },
  80. grammar: { value: "过去分词 主格 单数 中性", status: 7 },
  81. confidence: 1,
  82. },
  83. {
  84. uid: "w4",
  85. book: 1,
  86. para: 1,
  87. sn: [4],
  88. word: { value: "—", status: 0 },
  89. real: { value: null, status: 0 },
  90. meaning: { value: "——", status: 0 },
  91. confidence: 0,
  92. },
  93. {
  94. uid: "w5",
  95. book: 1,
  96. para: 1,
  97. sn: [5],
  98. word: { value: "ekaṃ", status: 7 },
  99. real: { value: "eka", status: 7 },
  100. meaning: { value: "一", status: 7 },
  101. type: { value: "num", status: 7 },
  102. grammar: { value: "数词 业格 单数 中性", status: 7 },
  103. confidence: 1,
  104. },
  105. ] satisfies IWbw[]),
  106. contentType: "json",
  107. html: "<span>Evaṃ me sutaṃ — ekaṃ</span>",
  108. editor: mockEditor,
  109. channel: { id: "origin", name: "原文", type: "translation" as const },
  110. updateAt: "2024-01-01T00:00:00Z",
  111. },
  112. {
  113. id: "origin-002",
  114. book: 1,
  115. para: 1,
  116. wordStart: 1,
  117. wordEnd: 5,
  118. content: "Evaṃ me sutaṃ — ekaṃ",
  119. contentType: "markdown",
  120. html: "<span>Evaṃ me sutaṃ — ekaṃ</span>",
  121. editor: mockEditor,
  122. channel: {
  123. id: "origin-text",
  124. name: "原文(文本)",
  125. type: "translation" as const,
  126. },
  127. updateAt: "2024-01-01T00:00:00Z",
  128. },
  129. ];
  130. /** 翻译列表(translation + nissaya + commentary) */
  131. const mockTranslation: ISentence[] = [
  132. {
  133. id: "trans-001",
  134. book: 1,
  135. para: 1,
  136. wordStart: 1,
  137. wordEnd: 5,
  138. content: "如是我闻——一时,",
  139. contentType: "markdown",
  140. html: "<p>如是我闻——一时,</p>",
  141. editor: mockEditor,
  142. channel: mockTranslationChannel,
  143. updateAt: "2024-03-01T08:00:00Z",
  144. suggestionCount: { suggestion: 3, discussion: 1 },
  145. },
  146. {
  147. id: "nissaya-001",
  148. book: 1,
  149. para: 1,
  150. wordStart: 1,
  151. wordEnd: 5,
  152. content: "(evaṃ)如是(me)我(sutaṃ)所闻",
  153. contentType: "markdown",
  154. html: "<p>(evaṃ)如是(me)我(sutaṃ)所闻</p>",
  155. editor: mockEditor,
  156. channel: mockTranslationChannel,
  157. updateAt: "2024-03-02T09:00:00Z",
  158. },
  159. ];
  160. /** 注释列表(单独卡片展示) */
  161. const mockCommentaries: ISentence[] = [
  162. {
  163. id: "comm-001",
  164. book: 1,
  165. para: 1,
  166. wordStart: 1,
  167. wordEnd: 5,
  168. content:
  169. "「如是我闻」是结集者阿难在结集时所加的导语,表明以下内容是亲耳所闻。",
  170. contentType: "markdown",
  171. html: "<p>「如是我闻」是结集者阿难在结集时所加的导语,表明以下内容是亲耳所闻。</p>",
  172. editor: mockEditor,
  173. channel: mockCommentaryChannel,
  174. updateAt: "2024-03-03T10:00:00Z",
  175. openInEditMode: false,
  176. },
  177. ];
  178. /** 目录路径 */
  179. const mockPath: ITocPathNode[] = [
  180. { title: "长部", paliTitle: "Dīgha Nikāya", level: 0, book: 1 },
  181. {
  182. title: "梵网经",
  183. paliTitle: "Brahmajāla Sutta",
  184. level: 1,
  185. book: 1,
  186. paragraph: 1,
  187. },
  188. ];
  189. // ─── 基础 props ────────────────────────────────────────────────────
  190. const baseProps: IWidgetSentEditInner = {
  191. id: "sent-demo-1_1_1_5",
  192. book: 1,
  193. para: 1,
  194. wordStart: 1,
  195. wordEnd: 5,
  196. origin: mockOrigin,
  197. translation: mockTranslation,
  198. commentaries: mockCommentaries,
  199. path: mockPath,
  200. tranNum: 2,
  201. nissayaNum: 1,
  202. commNum: 1,
  203. originNum: 1,
  204. simNum: 0,
  205. compact: false,
  206. showWbwProgress: false,
  207. readonly: false,
  208. };
  209. // ─── Demo 页面 ────────────────────────────────────────────────────
  210. const SentEditInnerDemo: React.FC = () => {
  211. const [layout, setLayout] = useState<"column" | "row">("column");
  212. const [compact, setCompact] = useState(false);
  213. const [readonly, setReadonly] = useState(false);
  214. const [showWbwProgress, setShowWbwProgress] = useState(false);
  215. return (
  216. <ConfigProvider>
  217. <div style={{ maxWidth: 1200, margin: "0 auto", padding: "24px 16px" }}>
  218. {/* 页头 */}
  219. <div style={{ marginBottom: 24 }}>
  220. <Title level={3} style={{ margin: 0 }}>
  221. 🧪 SentEditInner — 组件演示
  222. </Title>
  223. <Text type="secondary">
  224. book=1 · para=1 · wordStart=1 · wordEnd=5 · 所有数据为 mock
  225. </Text>
  226. </div>
  227. {/* 控制面板 */}
  228. <div
  229. style={{
  230. display: "flex",
  231. flexWrap: "wrap",
  232. gap: 24,
  233. padding: "16px 20px",
  234. background: "#fafafa",
  235. border: "1px solid #e8e8e8",
  236. borderRadius: 8,
  237. marginBottom: 32,
  238. }}
  239. >
  240. <Space>
  241. <Text>layout</Text>
  242. <Switch
  243. checkedChildren="row"
  244. unCheckedChildren="column"
  245. checked={layout === "row"}
  246. onChange={(v) => setLayout(v ? "row" : "column")}
  247. />
  248. <Tag color={layout === "row" ? "blue" : "green"}>{layout}</Tag>
  249. </Space>
  250. <Space>
  251. <Text>compact</Text>
  252. <Switch checked={compact} onChange={setCompact} />
  253. </Space>
  254. <Space>
  255. <Text>readonly</Text>
  256. <Switch checked={readonly} onChange={setReadonly} />
  257. </Space>
  258. <Space>
  259. <Text>showWbwProgress</Text>
  260. <Switch checked={showWbwProgress} onChange={setShowWbwProgress} />
  261. </Space>
  262. </div>
  263. <Divider>渲染结果</Divider>
  264. {/* 目标组件 */}
  265. <div
  266. style={{
  267. border: "2px dashed #d9d9d9",
  268. borderRadius: 8,
  269. padding: 16,
  270. background: "#fff",
  271. }}
  272. >
  273. <SentEditInner
  274. {...baseProps}
  275. layout={layout}
  276. compact={compact}
  277. readonly={readonly}
  278. showWbwProgress={showWbwProgress}
  279. onTranslationChange={(data) => {
  280. console.log("[Demo] onTranslationChange →", data);
  281. }}
  282. />
  283. </div>
  284. {/* 第二个:无注释、无翻译的极简情形 */}
  285. <Divider style={{ marginTop: 40 }}>极简情形(无翻译 / 无注释)</Divider>
  286. <div
  287. style={{
  288. border: "2px dashed #ffe58f",
  289. borderRadius: 8,
  290. padding: 16,
  291. background: "#fffbe6",
  292. }}
  293. >
  294. <SentEditInner
  295. id="sent-demo-minimal"
  296. book={1}
  297. para={2}
  298. wordStart={1}
  299. wordEnd={3}
  300. origin={[mockOrigin[1]]}
  301. originNum={1}
  302. layout="column"
  303. compact
  304. readonly
  305. />
  306. </div>
  307. {/* Mock 数据预览 */}
  308. <Divider style={{ marginTop: 40 }}>Mock 数据预览</Divider>
  309. <pre
  310. style={{
  311. background: "#282c34",
  312. color: "#abb2bf",
  313. padding: 20,
  314. borderRadius: 8,
  315. fontSize: 12,
  316. overflow: "auto",
  317. maxHeight: 400,
  318. }}
  319. >
  320. {JSON.stringify(
  321. {
  322. origin: mockOrigin,
  323. translation: mockTranslation,
  324. commentaries: mockCommentaries,
  325. path: mockPath,
  326. },
  327. null,
  328. 2
  329. )}
  330. </pre>
  331. </div>
  332. </ConfigProvider>
  333. );
  334. };
  335. export default SentEditInnerDemo;