Skip to content

Instantly share code, notes, and snippets.

@Cozy228
Created December 3, 2025 06:30
Show Gist options
  • Select an option

  • Save Cozy228/2bb12c527e61c5344d864e2ade968f3d to your computer and use it in GitHub Desktop.

Select an option

Save Cozy228/2bb12c527e61c5344d864e2ade968f3d to your computer and use it in GitHub Desktop.
prompt
你现在是一个前端/React/TypeScript 代码改造助手,目标很明确:
把“纯 `<pre>` 渲染的 ASCII 文本面板”改造成:
1)支持带颜色的数值变量,
2)仍然完全不破坏 ASCII 布局(边框不位移、对齐不乱),
3)数据和模板分离。
请严格按下面步骤和约束修改代码。
--------------------
【整体目标】
--------------------
- 现状:代码里用 `<pre>{ascii}</pre>` 直接渲染一整段 ASCII 字符串,其中包含边框、标签和“写死的数字”。
- 目标:
1. 把 ASCII 中的动态数值抽出来,变成 “模板 + 数据” 的形式;
2. 渲染时,模板中数值位置输出**固定宽度**的字符串,保证每一行长度恒定,ASCII 边框永远不会被撑歪;
3. 再把最终 ASCII 字符串拆成 `<span>` 网格,给数值字符上不同颜色,但不改变任何字符的位置。
--------------------
【第 1 步:从纯 ASCII 变成“模板 + 数据”】
--------------------
1. 找到当前类似下面的写法(示例):
```tsx
const ascii = `
+--------------+
| CPU: 37 % |
| MEM: 68 % |
| RPS: 1024 |
+--------------+
`.trimEnd()
2. 把“会变化的数字”改成占位符,采用如下语法:
• [KEY:宽度]
• KEY:数据字段名(大写字母/数字/下划线,方便正则解析,例如 CPU、MEM、RPS)
• 宽度:这个位置允许显示的字符数,用于对齐,例如 CPU 是 3 位宽度,就写 :3
示例改造:
const template = `
+--------------+
| CPU: [CPU:3] %|
| MEM: [MEM:3] %|
| RPS: [RPS:5] |
+--------------+
`.trimEnd()
3. 把真实数值放到一个 JS 对象里,不要写在模板里:
const data = {
CPU: 37,
MEM: 68,
RPS: 1024,
}
【第 2 步:实现 renderAscii(template, data) —— 固定宽度字符串】
实现一个函数:输入模板字符串 + 数据对象,输出最终的纯 ASCII 字符串。核心要求:模板中的每一个 [KEY:宽度] 均被替换为 长度恰好等于 宽度 的字符串。
规则:
• 将 value 转为字符串;
• 如果长度大于 宽度,只保留右边 宽度 位(slice(-width)),避免撑破;
• 如果长度小于 宽度,用空格在左侧 padStart 填充到指定宽度;
• 最终输出的字符串长度必须始终等于 宽度。
示例实现(TypeScript/JS):
function formatSlot(value: unknown, width: number) {
const raw = String(value ?? '')
const clipped = raw.slice(-width) // 只保留右侧 width 个字符
const padded = clipped.padStart(width, ' ')// 左侧填充空格
return padded
}
function renderAscii(template: string, data: Record<string, unknown>): string {
return template.replace(
/$begin:math:display$\(\[A\-Z0\-9\_\]\+\)\:\(\\d\+\)$end:math:display$/g,
(_, key, widthStr) => {
const width = parseInt(widthStr, 10)
return formatSlot(data[key], width)
},
)
}
使用方式:
const ascii = renderAscii(template, data)
此时 ascii 是一个纯字符串,复制出来在任何等宽字体下都应保持完美对齐,边框不会错位。
【第 3 步:ColorAscii 组件——拆成字符网格,用 span 上色】
现在要做的是:在不改变 ASCII 布局的前提下,对某些数字或位置进行着色。
要求:
• 保留 <pre> 或 white-space: pre,保证换行与空格原样输出;
• 使用等宽字体(如 font-mono);
• 将 ascii 拆分为「行数组 → 字符数组」;
• 每个字符用 <span> 包裹,便于添加 className 来控制颜色;
• 空格字符用 '\u00A0'(不换行空格)输出,防止因 HTML 的空格折叠导致视觉错位;
• 推荐让 <span> display: inline-block,方便后续扩展(hover、tooltip 等)且更稳定。
示例组件:
type HighlightRule = (params: {
ch: string
x: number
y: number
}) => string | undefined // 返回 className
function ColorAscii({
ascii,
highlight,
}: {
ascii: string
highlight?: HighlightRule
}) {
const lines = ascii.split('\n')
return (
<pre className="font-mono whitespace-pre leading-none">
{lines.map((line, y) => (
<React.Fragment key={y}>
{line.split('').map((ch, x) => {
const cls = highlight?.({ ch, x, y })
return (
<span
key={x}
className={cls}
style={{ display: 'inline-block' }}
>
{ch === ' ' ? '\u00A0' : ch}
</span>
)
})}
{y < lines.length - 1 ? '\n' : null}
</React.Fragment>
))}
</pre>
)
}
使用示例:根据字符是否是数字 + 数值大小上色(只是示例,实际逻辑可以更复杂,比如根据数据结构映射):
function AsciiPanel() {
const template = /* 如上定义的 template */
const data = /* 实际数据 */
const ascii = useMemo(
() => renderAscii(template, data),
[template, data],
)
return (
<ColorAscii
ascii={ascii}
highlight={({ ch }) => {
const n = Number(ch)
if (!Number.isNaN(n) && n >= 7) return 'text-red-400'
if (!Number.isNaN(n)) return 'text-sky-400'
return 'text-slate-300'
}}
/>
)
}
注意:
• 不要在滚动/动画回调中不断 setState 触发此组件重渲染;
• 动画(包括 GSAP auto scroll)只操作容器元素(例如 <pre> 外层)的 transform,不要让 React 每帧重算 ascii 或重新渲染整棵字符树。
【第 4 步:重点约束总结(你在修改代码时务必严格遵守)】
1. 所有动态数值必须从 ASCII 字符串中剥离出来,改为 [KEY:宽度] 的模板占位符;
2. 模板渲染成最终 ASCII 时,每个占位符必须输出固定宽度字符串,超长截断、过短空格填充,行长度不能变化;
3. <pre> + 等宽字体 + white-space: pre 必须保留,以维持 ASCII 网格结构;
4. 每个最终字符用 <span> 包裹,空格用 '\u00A0' 输出,可以通过 className 控制颜色,但不能改动字符本身的位置;
5. 动画层(如 GSAP)只修改 DOM 的 transform 等样式,不通过 React 状态驱动逐帧重渲染;
6. 改造完成后,复制页面中这块 ASCII 文本到纯文本编辑器(等宽字体)时,边框和对齐必须与改造前一致,仅数值和颜色逻辑发生变化。
请在重构代码时完全按照上述步骤和约束进行实现与修改。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment