Refactor frontend workspaces and centralize API contracts

Organize domain APIs and types under common/apis, shared components under named folders, and private workspace logic alongside each page.

Use Bearer sessions, explicit response envelopes, session-aware routing and streams, and configurable login captcha presentation for the deployed backend contract.

Preserve news, economic calendar, crawler, market, chat, Skills, settings, and demo trading workflows with responsive layouts and explicit configuration saves.

Validate with frontend lint, type checking, 22 test groups, production builds, and deployed desktop/mobile page checks.
This commit is contained in:
eezd committed 2026-10-26 11:39:52 +08:00
1 parent 77b33e5e77
commit bc480fb806
102 files changed
+4716 -1707

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
import MarkdownIt from "markdown-it"
// html: false —— 模型输出和 skill 内容里的 HTML 一律按文本显示,不执行
const md = new MarkdownIt({ html: false, linkify: true, breaks: true })
// 链接一律新窗口打开
const defaultLinkOpen = md.renderer.rules.link_open ?? ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options))
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
tokens[idx].attrSet("target", "_blank")
tokens[idx].attrSet("rel", "noopener noreferrer")
return defaultLinkOpen(tokens, idx, options, env, self)
}
export function renderMarkdown(source: string): string {
return md.render(source)
}
+10
View File
@@ -0,0 +1,10 @@
import type { Ref } from "vue"
/** 持久化到 localStorage 的 ref;值需可 JSON 序列化 */
export function usePersisted<T>(key: string, initial: T): Ref<T> {
const fullKey = `alphapulse.${key}`
const raw = localStorage.getItem(fullKey)
const state = ref(raw === null ? initial : JSON.parse(raw)) as Ref<T>
watch(state, value => localStorage.setItem(fullKey, JSON.stringify(value)), { deep: true })
return state
}
+45
View File
@@ -0,0 +1,45 @@
import dayjs from "dayjs"
export function formatTime(value: string | null | undefined, pattern = "MM-DD HH:mm:ss") {
return value ? dayjs(value).format(pattern) : "-"
}
/** HTML 转纯文本行;DOMParser 解析的文档不会执行脚本 */
export function htmlToLines(html: string): string[] {
const marked = html.replace(/<br\s*\/?>|<\/(?:p|div|li|h\d)>/gi, "\n")
const text = new DOMParser().parseFromString(marked, "text/html").body.textContent ?? ""
return text.split("\n").map(line => line.trim()).filter(Boolean)
}
export interface DiffLine {
type: "same" | "add" | "del"
text: string
}
/** 基于最长公共子序列的逐行对比 */
export function diffLines(before: string[], after: string[]): DiffLine[] {
const n = before.length
const m = after.length
const lcs = Array.from({ length: n + 1 }, () => Array.from<number>({ length: m + 1 }).fill(0))
for (let i = n - 1; i >= 0; i--) {
for (let j = m - 1; j >= 0; j--)
lcs[i][j] = before[i] === after[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1])
}
const result: DiffLine[] = []
let i = 0
let j = 0
while (i < n && j < m) {
if (before[i] === after[j]) {
result.push({ type: "same", text: before[i] })
i++
j++
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
result.push({ type: "del", text: before[i++] })
} else {
result.push({ type: "add", text: after[j++] })
}
}
while (i < n) result.push({ type: "del", text: before[i++] })
while (j < m) result.push({ type: "add", text: after[j++] })
return result
}