From 91ca9904f2b67aa6791ef410f06760f4156aac09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BA=9A=E6=98=95?= <1549469775@qq.com> Date: Sat, 11 Oct 2025 16:18:22 +0800 Subject: [PATCH] feat: Enhance chat functionality and UI components - Added ChatBox component for improved chat interface. - Introduced Msg and Node components for rendering messages with markdown support. - Implemented useScroll composable for automatic scrolling behavior in chat. - Updated auto-imports to include useScroll globally. - Added loading spinner in index.html for better user experience during loading. - Removed deprecated theme-helpers.scss file and refactored global styles. - Updated package.json to include new dependencies for markdown rendering and DOM manipulation. --- bun.lockb | Bin 168453 -> 203607 bytes packages/client/auto-imports.d.ts | 2 + packages/client/components.d.ts | 3 + packages/client/index.html | 57 +++++++ packages/client/package.json | 2 + .../client/src/assets/styles/scss/_global.scss | 184 +------------------- .../src/assets/styles/scss/_theme-helpers.scss | 190 --------------------- packages/client/src/assets/styles/scss/common.scss | 1 + packages/client/src/components/AiDemo/_/sseData.ts | 119 ++++++++++--- packages/client/src/components/ChatBox/_/Msg.vue | 64 +++++++ packages/client/src/components/ChatBox/_/Node.vue | 61 +++++++ .../client/src/components/ChatBox/_/sseData.ts | 102 +++++++++++ packages/client/src/components/ChatBox/index.vue | 153 +++++++++++++++++ packages/client/src/composables/useScroll/index.ts | 90 ++++++++++ packages/client/src/pages/index.vue | 38 ++--- 15 files changed, 643 insertions(+), 423 deletions(-) delete mode 100644 packages/client/src/assets/styles/scss/_theme-helpers.scss create mode 100644 packages/client/src/components/ChatBox/_/Msg.vue create mode 100644 packages/client/src/components/ChatBox/_/Node.vue create mode 100644 packages/client/src/components/ChatBox/_/sseData.ts create mode 100644 packages/client/src/components/ChatBox/index.vue create mode 100644 packages/client/src/composables/useScroll/index.ts diff --git a/bun.lockb b/bun.lockb index 0b44cbcd6e578af4adb84b9f5cb4d581c59a4a47..c4db671d659096c4155240238a7e8298b8e78497 100644 GIT binary patch delta 31743 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 delta 9717 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 readonly useRoute: UnwrapRef readonly useRouter: UnwrapRef + readonly useScroll: UnwrapRef readonly useSeoMeta: UnwrapRef readonly useServerHead: UnwrapRef readonly useServerHeadSafe: UnwrapRef diff --git a/packages/client/components.d.ts b/packages/client/components.d.ts index 613e14f..34cb5de 100644 --- a/packages/client/components.d.ts +++ b/packages/client/components.d.ts @@ -9,10 +9,13 @@ export {} declare module 'vue' { export interface GlobalComponents { AiDemo: typeof import('./src/components/AiDemo/index.vue')['default'] + ChatBox: typeof import('./src/components/ChatBox/index.vue')['default'] ClientOnly: typeof import('./../../internal/x/components/ClientOnly.vue')['default'] CookieDemo: typeof import('./src/components/CookieDemo.vue')['default'] DataFetch: typeof import('./src/components/DataFetch.vue')['default'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] + Msg: typeof import('./src/components/ChatBox/_/Msg.vue')['default'] + Node: typeof import('./src/components/ChatBox/_/Node.vue')['default'] QuillEditor: typeof import('./src/components/QuillEditor/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/packages/client/index.html b/packages/client/index.html index 789957a..16d0a38 100644 --- a/packages/client/index.html +++ b/packages/client/index.html @@ -4,6 +4,63 @@ +
+
+ + +
diff --git a/packages/client/package.json b/packages/client/package.json index aae169d..d98baf8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -8,6 +8,7 @@ "check": "vue-tsc" }, "devDependencies": { + "@types/jsdom": "^27.0.0", "sass-embedded": "^1.93.2", "unplugin-vue-components": "^29.1.0", "vue-tsc": "^3.1.0" @@ -20,6 +21,7 @@ "ant-design-x-vue": "^1.3.2", "dompurify": "^3.2.7", "htmlparser2": "^10.0.0", + "jsdom": "^27.0.0", "marked": "^16.3.0", "maz-ui": "^4.1.6", "ofetch": "^1.4.1", diff --git a/packages/client/src/assets/styles/scss/_global.scss b/packages/client/src/assets/styles/scss/_global.scss index 5b4dac0..fe31c38 100644 --- a/packages/client/src/assets/styles/scss/_global.scss +++ b/packages/client/src/assets/styles/scss/_global.scss @@ -1,26 +1,11 @@ -/* - theme-helpers.scss - 为主题开发提供的 SCSS 帮助函数 / mixin / 示例 - 说明(中文注释): - - 提供把 SCSS map 转换为 CSS 自定义属性(variables)的 mixin - - 提供生成主题(light/dark)选择器的 mixin 示例 - - 提供读取 CSS 变量的辅助函数 `css-var()` - - 提供颜色可读性判断函数 `readable-text()`(基于简单亮度公式) - - 提供颜色微调辅助 `tone()` - - 使用方式:在你的主样式中导入此文件并调用 mixin/map。示例在文件底部有注释。 -*/ - -// ----------------------------- -// CSS 变量相关帮助 -// ----------------------------- +@use "sass:string"; // 返回一个 var(...) 字符串,方便在 SCSS 中使用 CSS 变量 @function css-var($name, $fallback: null) { @if $fallback == null { - @return unquote("var(--#{$name})"); + @return string.unquote("var(--#{$name})"); } @else { - @return unquote("var(--#{$name}, #{$fallback})"); + @return string.unquote("var(--#{$name}, #{$fallback})"); } } @@ -41,166 +26,3 @@ @include declare-theme-variables($map); } } - - -// ----------------------------- -// 颜色工具函数 -// ----------------------------- - -// 计算近似亮度(0-255)用于对比判定(基于 ITU BT.601 近似) -@function _luma($color) { - // 期望 $color 为 color 类型 - $r: red($color); - $g: green($color); - $b: blue($color); - @return ($r * 0.299) + ($g * 0.587) + ($b * 0.114); -} - -// 根据背景色返回可读的文字颜色(#000 或 #fff) -// 示例: color: readable-text(#0d1117); -@function readable-text($bg, $light: #ffffff, $dark: #000000) { - // 如果传入的不是 color 类型,尝试转换(如果是变量字符串则无法计算) - @if type-of($bg) != 'color' { - // 无法在构建时计算 CSS 变量的对比,默认返回白色以便在暗色环境下可读 - @return $light; - } - @if _luma($bg) > 186 { - @return $dark; - } - @return $light; -} - -// 基于 lighten/darken 的简单色调微调函数(正值变亮,负值变暗) -@function tone($color, $percent) { - @if type-of($color) != 'color' { - @warn "tone(): first argument is not a color; returned value will be unchanged."; - @return $color; - } - @if $percent == 0 { - @return $color; - } - @if $percent > 0 { - @return lighten($color, $percent); - } @else { - @return darken($color, abs($percent)); - } -} - -// 使颜色变浅的辅助函数 -// 用法: -// lighten-by(#0d1117, 20) -> 以 20% 变亮 -// lighten-by(#0d1117, 20%) -> 以 20% 变亮 -// lighten-by(#0d1117, 0.2) -> 以 20% 变亮(小数形式) -@function lighten-by($color, $amount) { - @if type-of($color) != 'color' { - @warn "lighten-by(): first argument is not a color; returned value will be unchanged."; - @return $color; - } - @if type-of($amount) != 'number' { - @warn "lighten-by(): amount must be a number (e.g. 20, 20% or 0.2). Returning original color."; - @return $color; - } - - // 规范化为百分比单位(Sass 的 percent 类型) - $pct: $amount; - @if unit($amount) != '%' { - // 无单位数字:如果在 (0,1] 范围内,视为小数比例;否则当作百分比数值 - @if $amount > 0 and $amount <= 1 { - $pct: $amount * 100%; - } @else { - $pct: $amount * 1%; - } - } - - @return lighten($color, $pct); -} - -// ----------------------------- -// 常用组件/场景 mixin -// ----------------------------- - -// 简单的背景/文字组合,接收背景颜色或变量名 -// 用法:@include bg-fg('color-canvas-default'); // 传入变量名 -// @include bg-fg(#0d1117); // 传入 color 类型 -@mixin bg-fg($bg, $fg: null) { - @if type-of($bg) == 'string' { - // 假定传入的是变量名,使用 css-var - background: css-var($bg); - @if $fg == null { - // 无法静态计算对比,留空或用户自行指定 - color: inherit; - } else { - color: css-var($fg); - } - } @else if type-of($bg) == 'color' { - background: $bg; - @if $fg == null { - color: readable-text($bg); - } @else if type-of($fg) == 'color' { - color: $fg; - } @else { - color: css-var($fg); - } - } @else { - @warn "bg-fg(): unsupported bg type"; - } -} - -// 一个可重用的按钮样式 mixin,支持传入变量名或颜色 -// 用法: -// .btn { @include theme-button('color-accent-emphasis'); } -@mixin theme-button($bg, $fg: null, $radius: 6px, $pad-y: 8px, $pad-x: 12px) { - display: inline-flex; - align-items: center; - justify-content: center; - padding: $pad-y $pad-x; - border-radius: $radius; - border: none; - cursor: pointer; - @if type-of($bg) == 'string' { - background: css-var($bg); - @if $fg == null { color: css-var('color-fg-default'); } @else { color: css-var($fg); } - } @else if type-of($bg) == 'color' { - background: $bg; - @if $fg == null { color: readable-text($bg); } @else if type-of($fg) == 'color' { color: $fg; } @else { color: css-var($fg); } - } - // 微交互 - &:hover { filter: brightness(0.95); } - &:active { transform: translateY(1px); } -} - - -// ----------------------------- -// 示例(注释掉,直接拷贝到你的样式里使用) -// ----------------------------- - -// 示例主题 maps:键名与全局 CSS 变量中的命名保持一致(但不包含前缀 --) -// $theme-light: ( -// 'color-fg-default': #24292f, -// 'color-fg-muted': #57606a, -// 'color-canvas-default': #ffffff, -// 'color-border-default': #d0d7de, -// 'color-accent-fg': #0969da, -// ); -// -// $theme-dark: ( -// 'color-fg-default': #c9d1d9, -// 'color-fg-muted': #8b949e, -// 'color-canvas-default': #0d1117, -// 'color-border-default': #30363d, -// 'color-accent-fg': #58a6ff, -// ); -// -// 生成到 :root 和手动切换器: -// @include generate-theme(':root', $theme-light); -// @include generate-theme(':root[data-theme="dark"]', $theme-dark); -// -// 使用 CSS 变量: -// .markdown-body { color: css-var('color-fg-default'); background: css-var('color-canvas-default'); } -// -// 使用 mixin 快速为按钮应用主题颜色: -// .btn { @include theme-button('color-accent-emphasis'); } - -// ----------------------------- -// 结束 -// ----------------------------- diff --git a/packages/client/src/assets/styles/scss/_theme-helpers.scss b/packages/client/src/assets/styles/scss/_theme-helpers.scss deleted file mode 100644 index 19e3dcf..0000000 --- a/packages/client/src/assets/styles/scss/_theme-helpers.scss +++ /dev/null @@ -1,190 +0,0 @@ - -// 返回一个 var(...) 字符串,方便在 SCSS 中使用 CSS 变量 -@function css-var($name, $fallback: null) { - @if $fallback == null { - @return unquote("var(--#{$name})"); - } @else { - @return unquote("var(--#{$name}, #{$fallback})"); - } -} - -// 将一个 map 转换为 CSS 变量声明,需在选择器块内使用 -// 用法: -// :root { @include declare-theme-variables($my-theme-map); } -@mixin declare-theme-variables($map) { - @each $token, $val in $map { - // 允许传入颜色、字符串或数字 - --#{$token}: #{$val}; - } -} - -// 生成主题选择器(selector 可以是 ":root"、":root[data-theme=\"dark\"]" 或 ".theme-dark") -// 用法:@include generate-theme(':root', $theme-light); -@mixin generate-theme($selector, $map) { - #{$selector} { - @include declare-theme-variables($map); - } -} - - -// ----------------------------- -// 颜色工具函数 -// ----------------------------- - -// 计算近似亮度(0-255)用于对比判定(基于 ITU BT.601 近似) -@function _luma($color) { - // 期望 $color 为 color 类型 - $r: red($color); - $g: green($color); - $b: blue($color); - @return ($r * 0.299) + ($g * 0.587) + ($b * 0.114); -} - -// 根据背景色返回可读的文字颜色(#000 或 #fff) -// 示例: color: readable-text(#0d1117); -@function readable-text($bg, $light: #ffffff, $dark: #000000) { - // 如果传入的不是 color 类型,尝试转换(如果是变量字符串则无法计算) - @if type-of($bg) != 'color' { - // 无法在构建时计算 CSS 变量的对比,默认返回白色以便在暗色环境下可读 - @return $light; - } - @if _luma($bg) > 186 { - @return $dark; - } - @return $light; -} - -// 基于 lighten/darken 的简单色调微调函数(正值变亮,负值变暗) -@function tone($color, $percent) { - @if type-of($color) != 'color' { - @warn "tone(): first argument is not a color; returned value will be unchanged."; - @return $color; - } - @if $percent == 0 { - @return $color; - } - @if $percent > 0 { - @return lighten($color, $percent); - } @else { - @return darken($color, abs($percent)); - } -} - -// 使颜色变浅的辅助函数 -// 用法: -// lighten-by(#0d1117, 20) -> 以 20% 变亮 -// lighten-by(#0d1117, 20%) -> 以 20% 变亮 -// lighten-by(#0d1117, 0.2) -> 以 20% 变亮(小数形式) -@function lighten-by($color, $amount) { - @if type-of($color) != 'color' { - @warn "lighten-by(): first argument is not a color; returned value will be unchanged."; - @return $color; - } - @if type-of($amount) != 'number' { - @warn "lighten-by(): amount must be a number (e.g. 20, 20% or 0.2). Returning original color."; - @return $color; - } - - // 规范化为百分比单位(Sass 的 percent 类型) - $pct: $amount; - @if unit($amount) != '%' { - // 无单位数字:如果在 (0,1] 范围内,视为小数比例;否则当作百分比数值 - @if $amount > 0 and $amount <= 1 { - $pct: $amount * 100%; - } @else { - $pct: $amount * 1%; - } - } - - @return lighten($color, $pct); -} - -// ----------------------------- -// 常用组件/场景 mixin -// ----------------------------- - -// 简单的背景/文字组合,接收背景颜色或变量名 -// 用法:@include bg-fg('color-canvas-default'); // 传入变量名 -// @include bg-fg(#0d1117); // 传入 color 类型 -@mixin bg-fg($bg, $fg: null) { - @if type-of($bg) == 'string' { - // 假定传入的是变量名,使用 css-var - background: css-var($bg); - @if $fg == null { - // 无法静态计算对比,留空或用户自行指定 - color: inherit; - } else { - color: css-var($fg); - } - } @else if type-of($bg) == 'color' { - background: $bg; - @if $fg == null { - color: readable-text($bg); - } @else if type-of($fg) == 'color' { - color: $fg; - } @else { - color: css-var($fg); - } - } @else { - @warn "bg-fg(): unsupported bg type"; - } -} - -// 一个可重用的按钮样式 mixin,支持传入变量名或颜色 -// 用法: -// .btn { @include theme-button('color-accent-emphasis'); } -@mixin theme-button($bg, $fg: null, $radius: 6px, $pad-y: 8px, $pad-x: 12px) { - display: inline-flex; - align-items: center; - justify-content: center; - padding: $pad-y $pad-x; - border-radius: $radius; - border: none; - cursor: pointer; - @if type-of($bg) == 'string' { - background: css-var($bg); - @if $fg == null { color: css-var('color-fg-default'); } @else { color: css-var($fg); } - } @else if type-of($bg) == 'color' { - background: $bg; - @if $fg == null { color: readable-text($bg); } @else if type-of($fg) == 'color' { color: $fg; } @else { color: css-var($fg); } - } - // 微交互 - &:hover { filter: brightness(0.95); } - &:active { transform: translateY(1px); } -} - - -// ----------------------------- -// 示例(注释掉,直接拷贝到你的样式里使用) -// ----------------------------- - -// 示例主题 maps:键名与全局 CSS 变量中的命名保持一致(但不包含前缀 --) -// $theme-light: ( -// 'color-fg-default': #24292f, -// 'color-fg-muted': #57606a, -// 'color-canvas-default': #ffffff, -// 'color-border-default': #d0d7de, -// 'color-accent-fg': #0969da, -// ); -// -// $theme-dark: ( -// 'color-fg-default': #c9d1d9, -// 'color-fg-muted': #8b949e, -// 'color-canvas-default': #0d1117, -// 'color-border-default': #30363d, -// 'color-accent-fg': #58a6ff, -// ); -// -// 生成到 :root 和手动切换器: -// @include generate-theme(':root', $theme-light); -// @include generate-theme(':root[data-theme="dark"]', $theme-dark); -// -// 使用 CSS 变量: -// .markdown-body { color: css-var('color-fg-default'); background: css-var('color-canvas-default'); } -// -// 使用 mixin 快速为按钮应用主题颜色: -// .btn { @include theme-button('color-accent-emphasis'); } - -// ----------------------------- -// 结束 -// ----------------------------- diff --git a/packages/client/src/assets/styles/scss/common.scss b/packages/client/src/assets/styles/scss/common.scss index c09dff8..8241350 100644 --- a/packages/client/src/assets/styles/scss/common.scss +++ b/packages/client/src/assets/styles/scss/common.scss @@ -50,6 +50,7 @@ $theme-dark: ( // 手动主题切换支持:data-theme 或 class @include generate-theme(':root[data-theme="dark"]', $theme-dark); +@include generate-theme(".theme-light", $theme-light); @include generate-theme(".theme-dark", $theme-dark); #app { diff --git a/packages/client/src/components/AiDemo/_/sseData.ts b/packages/client/src/components/AiDemo/_/sseData.ts index 5ec3ce3..e61a4c2 100644 --- a/packages/client/src/components/AiDemo/_/sseData.ts +++ b/packages/client/src/components/AiDemo/_/sseData.ts @@ -1,31 +1,102 @@ export default [ - { event: "message", answer: "## asdas\n" }, + { + event: "message", + answer: + "## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n", + }, { event: "message", answer: "**asasa**\n" }, - { event: "message", answer: "![啊啊啊](https://ts1.tc.mm.bing.net/th/id/R-C.823270fc68b9c58f0d9b3feb92b7b172?rik=aubbEBMSC86e%2bw&riu=http%3a%2f%2fimg95.699pic.com%2fphoto%2f50038%2f1181.jpg_wh860.jpg&ehk=iQboj4JMLLfDitOL7VJtSktED0AE%2f7Fyxfik0GTJkyQ%3d&risl=&pid=ImgRaw&r=0)asd\n" }, + { + event: "message", + answer: + "![啊啊啊](https://ts1.tc.mm.bing.net/th/id/R-C.823270fc68b9c58f0d9b3feb92b7b172?rik=aubbEBMSC86e%2bw&riu=http%3a%2f%2fimg95.699pic.com%2fphoto%2f50038%2f1181.jpg_wh860.jpg&ehk=iQboj4JMLLfDitOL7VJtSktED0AE%2f7Fyxfik0GTJkyQ%3d&risl=&pid=ImgRaw&r=0)asd\n", + }, { event: "message", answer: "```\nasdaaaasasaasaas\n" }, { event: "message", answer: "asdsaa\n" }, { event: "message", answer: "console.log(as)\n" }, { event: "message", answer: "asa\n```\n\n" }, - { event: "message", answer: "\n\n" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, - { event: "message", answer: "## asdas" }, + { event: "message", answer: '\n\n' }, + { event: "message", answer: "qweqen" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, { event: "message_end", answer: "## asdas" }, -] \ No newline at end of file +]; diff --git a/packages/client/src/components/ChatBox/_/Msg.vue b/packages/client/src/components/ChatBox/_/Msg.vue new file mode 100644 index 0000000..89d2d6d --- /dev/null +++ b/packages/client/src/components/ChatBox/_/Msg.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/packages/client/src/components/ChatBox/_/Node.vue b/packages/client/src/components/ChatBox/_/Node.vue new file mode 100644 index 0000000..dbca24f --- /dev/null +++ b/packages/client/src/components/ChatBox/_/Node.vue @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/packages/client/src/components/ChatBox/_/sseData.ts b/packages/client/src/components/ChatBox/_/sseData.ts new file mode 100644 index 0000000..e61a4c2 --- /dev/null +++ b/packages/client/src/components/ChatBox/_/sseData.ts @@ -0,0 +1,102 @@ +export default [ + { + event: "message", + answer: + "## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n## asdas\n", + }, + { event: "message", answer: "**asasa**\n" }, + { + event: "message", + answer: + "![啊啊啊](https://ts1.tc.mm.bing.net/th/id/R-C.823270fc68b9c58f0d9b3feb92b7b172?rik=aubbEBMSC86e%2bw&riu=http%3a%2f%2fimg95.699pic.com%2fphoto%2f50038%2f1181.jpg_wh860.jpg&ehk=iQboj4JMLLfDitOL7VJtSktED0AE%2f7Fyxfik0GTJkyQ%3d&risl=&pid=ImgRaw&r=0)asd\n", + }, + { event: "message", answer: "```\nasdaaaasasaasaas\n" }, + { event: "message", answer: "asdsaa\n" }, + { event: "message", answer: "console.log(as)\n" }, + { event: "message", answer: "asa\n```\n\n" }, + { event: "message", answer: '\n\n' }, + { event: "message", answer: "qweqen" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "qweqeq" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message", answer: "## asdas\n" }, + { event: "message_end", answer: "## asdas" }, +]; diff --git a/packages/client/src/components/ChatBox/index.vue b/packages/client/src/components/ChatBox/index.vue new file mode 100644 index 0000000..43b75c6 --- /dev/null +++ b/packages/client/src/components/ChatBox/index.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/packages/client/src/composables/useScroll/index.ts b/packages/client/src/composables/useScroll/index.ts new file mode 100644 index 0000000..ffbea6f --- /dev/null +++ b/packages/client/src/composables/useScroll/index.ts @@ -0,0 +1,90 @@ +interface IOption { + containerEl: Readonly>; + contentEl: Readonly>; + // 是否首次滚动到底部 + firstToBottom?: boolean; +} + +const defaultOption: Partial = { + firstToBottom: true, +}; + +export function useScroll(option: IOption) { + if (import.meta.env.SSR) { + return; + } + const { containerEl, contentEl, firstToBottom } = { + ...defaultOption, + ...option, + }; + const isAtBottom = ref(false); + const isAtTop = ref(false); + let _firstToBottom = firstToBottom; + let resizeObserver: ResizeObserver | null = null; + nextTick(() => { + const targetElement = containerEl.value!; + const talkContent = contentEl.value!; + function handleScroll() { + if (targetElement.scrollHeight === targetElement.clientHeight) { + isAtBottom.value = false; + isAtTop.value = false; + return; + } + if ( + Math.abs( + targetElement.scrollHeight - + targetElement.clientHeight - + targetElement.scrollTop + ) <= 50 + ) { + isAtBottom.value = true; + } else { + isAtBottom.value = false; + } + if (targetElement.scrollTop <= 50) { + isAtTop.value = true; + } else { + isAtTop.value = false; + } + } + targetElement.onscroll = handleScroll; + resizeObserver = new ResizeObserver(() => { + // 当内容高度发生变化时,如果当前不在底部,则滚动到底部 + if (targetElement.scrollHeight === targetElement.clientHeight) { + return; + } + if (isAtBottom.value || _firstToBottom) { + _firstToBottom = false; + scrollToBottom(); + } + }); + resizeObserver.observe(talkContent); + + handleScroll(); + }); + onScopeDispose(clear); + function scrollToBottom() { + const container = containerEl.value!; + const scrollTop = container.scrollHeight - container.clientHeight; + container.scrollTop = scrollTop; + // container.scrollTo({ top: scrollTop, behavior: "smooth" }); + } + function scrollToTop() { + containerEl.value!.scrollTop = 0; + } + function clear() { + if (containerEl.value) { + containerEl.value.onscroll = null; + } + if (resizeObserver) { + contentEl.value && resizeObserver.unobserve(contentEl.value); + resizeObserver.disconnect(); + } + } + return { + isAtBottom, + scrollToTop, + scrollToBottom, + clear, + }; +} diff --git a/packages/client/src/pages/index.vue b/packages/client/src/pages/index.vue index f7ce484..f8a38e7 100644 --- a/packages/client/src/pages/index.vue +++ b/packages/client/src/pages/index.vue @@ -9,41 +9,23 @@ definePage({ defineOptions({ name: "home", }); - -// import { useModal } from "vue-final-modal"; -// import ModalConfirmPlainCss from "./_M.vue"; - -// const { open, close } = useModal({ -// component: ModalConfirmPlainCss, -// attrs: { -// title: "Hello World!", -// onConfirm() { -// close(); -// }, -// }, -// slots: { -// default: "

The content of the modal

", -// }, -// });