展示

Code Block

通用代码块显示组件:基于 prism-react-renderer 的语法高亮,7 套内置配色主题(浅/深色变体)+ 自定义主题支持,支持折叠、滚动、自适应高度三种显示模式,行号 sticky 固定,自带完整样式。含 CodeBlockPanel 外层容器面板(仿 Tailwind CSS 官网风格),支持文件名标签与复制按钮。

codesyntax-highlightingprismthemecollapsescrollcode-blockpanelcopy

快速预览

查看 Code Block 组件的基本效果

counter.tsx
1import { useState } from "react";
2
3export function Counter({ initial = 0 }) {
4 const [count, setCount] = useState(initial);
5
6 return (
7 <div className="flex items-center gap-4">
8 <button onClick={() => setCount(c => c - 1)}>-</button>
9 <span className="text-xl font-bold">{count}</span>
10 <button onClick={() => setCount(c => c + 1)}>+</button>
11 </div>
12 );
13}

基本用法

使用 Code Block 组件的基础示例

导入组件

1import { CodeBlock, CodeBlockPanel } from "@/components/qiuye-ui/code-block";

使用组件

1<CodeBlockPanel filename="app.ts" code={code}>
2 <CodeBlock language="typescript" isDark>
3 {code}
4 </CodeBlock>
5</CodeBlockPanel>

组件演示

查看组件的各种使用方式和效果

基础用法

最简单的代码块展示,自动语法高亮

TypeScript
1import React, { useState } from "react";
2
3export function Counter({ initial = 0 }) {
4 const [count, setCount] = useState(initial);
5
6 return (
7 <div className="flex items-center gap-4">
8 <button onClick={() => setCount(c => c - 1)}>-</button>
9 <span className="text-xl font-bold">{count}</span>
10 <button onClick={() => setCount(c => c + 1)}>+</button>
11 </div>
12 );
13}

面板容器

仿 Tailwind CSS 官网风格的深色面板外壳,支持文件名标签与复制按钮

CodeBlockPanel
hooks/use-list-manager.ts
1import { useState, useEffect, useCallback, useMemo } from "react";
2
3interface User {
4 id: number;
5 name: string;
6 email: string;
7 role: "admin" | "user" | "moderator";
8 createdAt: Date;
9}
10
11type FilterFn<T> = (item: T) => boolean;
12
13/**
14 * 通用列表管理 Hook
15 * 支持过滤、排序、分页
16 */
17export function useListManager<T extends { id: number }>(
18 initialItems: T[],
19 pageSize: number = 10,
20) {
21 const [items, setItems] = useState<T[]>(initialItems);
22 const [filter, setFilter] = useState<FilterFn<T> | null>(null);
23 const [page, setPage] = useState(0);
24 const [sortKey, setSortKey] = useState<keyof T | null>(null);
25 const [sortDesc, setSortDesc] = useState(false);

不传 filename 时自动显示语言类型标签:

TSX
1import React, { useState } from "react";
2
3export function Counter({ initial = 0 }) {
4 const [count, setCount] = useState(initial);
5
6 return (
7 <div className="flex items-center gap-4">
8 <button onClick={() => setCount(c => c - 1)}>-</button>
9 <span className="text-xl font-bold">{count}</span>
10 <button onClick={() => setCount(c => c + 1)}>+</button>
11 </div>
12 );
13}

搭配 displayMode="scroll" 使用:

api_client.py
1from dataclasses import dataclass, field
2from typing import Optional, List
3import asyncio
4import aiohttp
5
6
7@dataclass
8class Config:
9 """应用配置"""
10 base_url: str = "https://api.example.com"
11 timeout: int = 30
12 max_retries: int = 3
13 headers: dict = field(default_factory=lambda: {
14 "Content-Type": "application/json",
15 "Accept": "application/json",
16 })
17
18
19class APIClient:
20 """异步 API 客户端"""
21
22 def __init__(self, config: Optional[Config] = None):
23 self.config = config or Config()
24 self._session: Optional[aiohttp.ClientSession] = None
25
26 async def __aenter__(self):
27 self._session = aiohttp.ClientSession(
28 headers=self.config.headers,
29 timeout=aiohttp.ClientTimeout(total=self.config.timeout),
30 )
31 return self
32
33 async def __aexit__(self, *args):
34 if self._session:
35 await self._session.close()
36
37 async def get(self, endpoint: str, **params) -> dict:
38 """发送 GET 请求"""
39 url = f"{self.config.base_url}/{endpoint}"
40 for attempt in range(self.config.max_retries):
41 try:
42 async with self._session.get(url, params=params) as resp:
43 resp.raise_for_status()
44 return await resp.json()
45 except aiohttp.ClientError as e:
46 if attempt == self.config.max_retries - 1:
47 raise
48 await asyncio.sleep(2 ** attempt)
49
50
51async def main():
52 async with APIClient() as client:
53 users = await client.get("users", page=1, limit=10)
54 print(f"获取到 {len(users)} 个用户")
55
56
57if __name__ == "__main__":
58 asyncio.run(main())

配色主题

7 套内置主题,自动适配浅色 / 深色模式

浅色
example.ts
1import { useState, useEffect, useCallback, useMemo } from "react";
2
3interface User {
4 id: number;
5 name: string;
6 email: string;
7 role: "admin" | "user" | "moderator";
8 createdAt: Date;
9}
10
11type FilterFn<T> = (item: T) => boolean;
12
13/**
14 * 通用列表管理 Hook
15 * 支持过滤、排序、分页
16 */
17export function useListManager<T extends { id: number }>(
18 initialItems: T[],
19 pageSize: number = 10,
20) {
21 const [items, setItems] = useState<T[]>(initialItems);
22 const [filter, setFilter] = useState<FilterFn<T> | null>(null);
23 const [page, setPage] = useState(0);
24 const [sortKey, setSortKey] = useState<keyof T | null>(null);
25 const [sortDesc, setSortDesc] = useState(false);
26
27 // 过滤 + 排序
28 const processed = useMemo(() => {
29 let result = filter ? items.filter(filter) : [...items];
30
31 if (sortKey) {
32 result.sort((a, b) => {
33 const va = a[sortKey];
34 const vb = b[sortKey];
35 if (va < vb) return sortDesc ? 1 : -1;
36 if (va > vb) return sortDesc ? -1 : 1;
37 return 0;
38 });
39 }
40
41 return result;
42 }, [items, filter, sortKey, sortDesc]);

折叠模式

超出行数自动折叠,渐变遮罩 + 展开/收起按钮

displayMode="collapse"

滚动模式

设置最大高度,超出部分纵向滚动,带滚动阴影指示器

displayMode="scroll"
1from dataclasses import dataclass, field
2from typing import Optional, List
3import asyncio
4import aiohttp
5
6
7@dataclass
8class Config:
9 """应用配置"""
10 base_url: str = "https://api.example.com"
11 timeout: int = 30
12 max_retries: int = 3
13 headers: dict = field(default_factory=lambda: {
14 "Content-Type": "application/json",
15 "Accept": "application/json",
16 })
17
18
19class APIClient:
20 """异步 API 客户端"""
21
22 def __init__(self, config: Optional[Config] = None):
23 self.config = config or Config()
24 self._session: Optional[aiohttp.ClientSession] = None
25
26 async def __aenter__(self):
27 self._session = aiohttp.ClientSession(
28 headers=self.config.headers,
29 timeout=aiohttp.ClientTimeout(total=self.config.timeout),
30 )
31 return self
32
33 async def __aexit__(self, *args):
34 if self._session:
35 await self._session.close()
36
37 async def get(self, endpoint: str, **params) -> dict:
38 """发送 GET 请求"""
39 url = f"{self.config.base_url}/{endpoint}"
40 for attempt in range(self.config.max_retries):
41 try:
42 async with self._session.get(url, params=params) as resp:
43 resp.raise_for_status()
44 return await resp.json()
45 except aiohttp.ClientError as e:
46 if attempt == self.config.max_retries - 1:
47 raise
48 await asyncio.sleep(2 ** attempt)
49
50
51async def main():
52 async with APIClient() as client:
53 users = await client.get("users", page=1, limit=10)
54 print(f"获取到 {len(users)} 个用户")
55
56
57if __name__ == "__main__":
58 asyncio.run(main())

自适应高度模式

高度自动适配父容器(如 Dialog),无需手动设置 maxHeight,适合弹窗 / Flex 布局场景

displayMode="auto-height"

多语言支持

支持 TypeScript、Python、JSX/TSX 等多种编程语言

Python
1from dataclasses import dataclass, field
2from typing import Optional, List
3import asyncio
4import aiohttp
5
6
7@dataclass
8class Config:
9 """应用配置"""
10 base_url: str = "https://api.example.com"
11 timeout: int = 30
12 max_retries: int = 3
13 headers: dict = field(default_factory=lambda: {
14 "Content-Type": "application/json",
15 "Accept": "application/json",
16 })

无阴影模式

通过 noShadow 属性移除代码块容器的 box-shadow,适合嵌入卡片等已有阴影的容器中

noShadow

默认(有阴影)

1const a = "有阴影";
2console.log(a);

noShadow(无阴影)

1const b = "无阴影";
2console.log(b);

行号显示控制

通过 showLineNumbers 控制行号显示策略:始终显示、始终隐藏、或按行数阈值自动切换

showLineNumbers

showLineNumbers={false} — 始终隐藏行号

1import React, { useState } from "react";
2
3export function Counter({ initial = 0 }) {
4 const [count, setCount] = useState(initial);
5
6 return (
7 <div className="flex items-center gap-4">
8 <button onClick={() => setCount(c => c - 1)}>-</button>
9 <span className="text-xl font-bold">{count}</span>
10 <button onClick={() => setCount(c => c + 1)}>+</button>
11 </div>
12 );
13}

showLineNumbers={15} — 代码不足 15 行,自动隐藏

1import React, { useState } from "react";
2
3export function Counter({ initial = 0 }) {
4 const [count, setCount] = useState(initial);
5
6 return (
7 <div className="flex items-center gap-4">
8 <button onClick={() => setCount(c => c - 1)}>-</button>
9 <span className="text-xl font-bold">{count}</span>
10 <button onClick={() => setCount(c => c + 1)}>+</button>
11 </div>
12 );
13}

showLineNumbers={15} — 代码超过 15 行,自动显示

1import { useState, useEffect, useCallback, useMemo } from "react";
2
3interface User {
4 id: number;
5 name: string;
6 email: string;
7 role: "admin" | "user" | "moderator";
8 createdAt: Date;
9}
10
11type FilterFn<T> = (item: T) => boolean;
12
13/**
14 * 通用列表管理 Hook
15 * 支持过滤、排序、分页
16 */
17export function useListManager<T extends { id: number }>(
18 initialItems: T[],
19 pageSize: number = 10,
20) {
21 const [items, setItems] = useState<T[]>(initialItems);
22 const [filter, setFilter] = useState<FilterFn<T> | null>(null);
23 const [page, setPage] = useState(0);
24 const [sortKey, setSortKey] = useState<keyof T | null>(null);
25 const [sortDesc, setSortDesc] = useState(false);
26
27 // 过滤 + 排序
28 const processed = useMemo(() => {
29 let result = filter ? items.filter(filter) : [...items];
30
31 if (sortKey) {
32 result.sort((a, b) => {
33 const va = a[sortKey];
34 const vb = b[sortKey];
35 if (va < vb) return sortDesc ? 1 : -1;
36 if (va > vb) return sortDesc ? -1 : 1;
37 return 0;
38 });
39 }
40
41 return result;
42 }, [items, filter, sortKey, sortDesc]);

面板 + 折叠模式组合

CodeBlockPanel 搭配 displayMode="collapse" 使用,面板自动重置内部样式

组合用法
hooks/use-list-manager.ts