From ca6aea29022990f139a5acaa475c36a5f4312ee9 Mon Sep 17 00:00:00 2001 From: CrescentLeaf Date: Sat, 30 Aug 2025 15:36:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(wip):=20=E5=89=8D=E7=AB=AF,=20=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E7=B7=A8=E8=AD=AF=E5=89=8D=E7=AB=AF=20TODO:=20?= =?UTF-8?q?=E4=BF=AE=E5=BE=A9=20webpack=20(in=20mian.ts)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/index.html | 108 +++++++++++ client/test.html | 40 ++++ client/ui/App.jsx | 283 ++++++++++++++++++++++++++++ client/ui/Avatar.jsx | 15 ++ client/ui/chat/ChatFragment.jsx | 3 + client/ui/chat/Message.jsx | 83 ++++++++ client/ui/chat/MessageContainer.jsx | 18 ++ client/ui/chat/SystemMessage.jsx | 27 +++ client/ui/main/ContactsListItem.jsx | 16 ++ client/ui/main/RecentsListItem.jsx | 21 +++ client/ui/useEventListener.js | 17 ++ deno.jsonc | 7 +- server/api/ApiManager.ts | 2 +- server/main.ts | 9 +- server/web_packer.ts | 29 +++ webpack_config.ts | 5 - 16 files changed, 674 insertions(+), 9 deletions(-) create mode 100644 client/index.html create mode 100644 client/test.html create mode 100644 client/ui/App.jsx create mode 100644 client/ui/Avatar.jsx create mode 100644 client/ui/chat/ChatFragment.jsx create mode 100644 client/ui/chat/Message.jsx create mode 100644 client/ui/chat/MessageContainer.jsx create mode 100644 client/ui/chat/SystemMessage.jsx create mode 100644 client/ui/main/ContactsListItem.jsx create mode 100644 client/ui/main/RecentsListItem.jsx create mode 100644 client/ui/useEventListener.js create mode 100644 server/web_packer.ts delete mode 100644 webpack_config.ts diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..6515971 --- /dev/null +++ b/client/index.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + TheWhiteSilk + + + + + +
+ + + + + + \ No newline at end of file diff --git a/client/test.html b/client/test.html new file mode 100644 index 0000000..379c99f --- /dev/null +++ b/client/test.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + TheWhiteSilk Debugger + + + + Send + +
+ +
+ + + + \ No newline at end of file diff --git a/client/ui/App.jsx b/client/ui/App.jsx new file mode 100644 index 0000000..25feccb --- /dev/null +++ b/client/ui/App.jsx @@ -0,0 +1,283 @@ +import Message from "./chat/Message.jsx" +import MessageContainer from "./chat/MessageContainer.jsx" +import ContactsListItem from "./main/ContactsListItem.jsx" +import RecentsListItem from "./main/RecentsListItem.jsx" +import useEventListener from './useEventListener.js' + +export default function App() { + const [recentsList, setRecentsList] = React.useState([ + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + content: "成步堂君, 我又坐牢了(" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + content: "我是绫里真宵, 是一名灵媒师~" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + content: "成步堂君, 我又坐牢了(" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + content: "我是绫里真宵, 是一名灵媒师~" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + content: "成步堂君, 我又坐牢了(" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + content: "我是绫里真宵, 是一名灵媒师~" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + content: "成步堂君, 我又坐牢了(" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + content: "我是绫里真宵, 是一名灵媒师~" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + content: "成步堂君, 我又坐牢了(" + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + content: "我是绫里真宵, 是一名灵媒师~" + }, + ]) + const [contactsMap, setContactsMap] = React.useState({ + 默认分组: [ + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + } + ], + 测试分组114514: [ + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "麻油衣酱", + }, + { + userId: 0, + avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png", + nickName: "Maya Fey", + } + ], + }) + const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents') + + const navigationRailRef = React.useRef(null) + useEventListener(navigationRailRef, 'change', (event) => { + setNavigationItemSelected(event.target.value) + }) + + return ( +
+ { + // 移动端用 页面调试 + // 換個地方弄 + // (new URL(location.href).searchParams.get('debug') == 'true') && + } + + + + + + + + + { + // 侧边列表 + } + { + // 最近聊天 + (navigationItemSelected == "Recents") && + + { + recentsList.map((v) => + + ) + } + + } + { + // 联系人列表 + (navigationItemSelected == "Contacts") && + + { + Object.keys(contactsMap).map((v) => + <> + {v} + { + contactsMap[v].map((v2) => + + ) + } + + ) + } + + } + { + // 分割线 + } +
+ +
+ { + // 聊天页面 + } +
+ + + Title + + +
+ + + Test + + + Test + + + Test + + + Test + + + Test + + + Test + + + Test + + + Test + + + { + // 输入框 + } +
+ + + +
+ {/* + + Title +
+ +
*/} +
+
+
+ ) +} \ No newline at end of file diff --git a/client/ui/Avatar.jsx b/client/ui/Avatar.jsx new file mode 100644 index 0000000..619410b --- /dev/null +++ b/client/ui/Avatar.jsx @@ -0,0 +1,15 @@ +export default function Avatar({ src, text, icon = 'person', ...props } = {}) { + return ( + src ? + {'(头像)' + + : ( + text ? + { + text.substring(0, 0) + } + + : + ) + ) +} diff --git a/client/ui/chat/ChatFragment.jsx b/client/ui/chat/ChatFragment.jsx new file mode 100644 index 0000000..e92ea04 --- /dev/null +++ b/client/ui/chat/ChatFragment.jsx @@ -0,0 +1,3 @@ +export default function ChatFragment() { + +} diff --git a/client/ui/chat/Message.jsx b/client/ui/chat/Message.jsx new file mode 100644 index 0000000..05f4615 --- /dev/null +++ b/client/ui/chat/Message.jsx @@ -0,0 +1,83 @@ +import Avatar from "../Avatar.jsx" + +/** + * 一条消息 + * @param { Object } param + * @param { "left" | "right" } [param.direction="left"] 消息方向 + * @param { String } [param.avatar] 头像链接 + * @param { String } [param.nickName] 昵称 + * @returns { React.JSX.Element } + */ +export default function Message({ direction = 'left', avatar, nickName, children, ...props } = {}) { + let isAtRight = direction == 'right' + return ( +
+
+ { + // 发送者昵称(左) + isAtRight && + {nickName} + + } + { + // 发送者头像 + } + + { + // 发送者昵称(右) + !isAtRight && + {nickName} + + } +
+ + + { + // 消息内容 + children + } + + +
+ ) +} diff --git a/client/ui/chat/MessageContainer.jsx b/client/ui/chat/MessageContainer.jsx new file mode 100644 index 0000000..edcc26c --- /dev/null +++ b/client/ui/chat/MessageContainer.jsx @@ -0,0 +1,18 @@ +/** + * 消息容器 + * @returns { React.JSX.Element } + */ +export default function MessageContainer({ children, style, ...props } = {}) { + return ( +
+ {children} +
+ ) +} diff --git a/client/ui/chat/SystemMessage.jsx b/client/ui/chat/SystemMessage.jsx new file mode 100644 index 0000000..c9b9f4b --- /dev/null +++ b/client/ui/chat/SystemMessage.jsx @@ -0,0 +1,27 @@ +/** + * 一条系统提示消息 + * @returns { React.JSX.Element } + */ +export default function SystemMessage({ children } = {}) { + return ( +
+ + {children} + +
+ ) +} diff --git a/client/ui/main/ContactsListItem.jsx b/client/ui/main/ContactsListItem.jsx new file mode 100644 index 0000000..3600ee2 --- /dev/null +++ b/client/ui/main/ContactsListItem.jsx @@ -0,0 +1,16 @@ +import Avatar from "../Avatar.jsx" + +export default function ContactsListItem({ nickName, avatar }) { + return ( + + {nickName} + + + ) +} diff --git a/client/ui/main/RecentsListItem.jsx b/client/ui/main/RecentsListItem.jsx new file mode 100644 index 0000000..e677948 --- /dev/null +++ b/client/ui/main/RecentsListItem.jsx @@ -0,0 +1,21 @@ +import Avatar from "../Avatar.jsx" + +export default function RecentsListItem({ nickName, avatar, content }) { + return ( + + {nickName} + + {content} + + ) +} diff --git a/client/ui/useEventListener.js b/client/ui/useEventListener.js new file mode 100644 index 0000000..1c0f67e --- /dev/null +++ b/client/ui/useEventListener.js @@ -0,0 +1,17 @@ +/** + * @callback callback + * @param { Event } event + */ + +/** + * 绑定事件 + * @param { React.Ref } ref + * @param { String } eventName + * @param { callback } callback + */ +export default function useEventListener(ref, eventName, callback) { + React.useEffect(() => { + ref.current.addEventListener(eventName, callback) + return () => ref.current.removeEventListener(eventName, callback) + }, [ref, eventName, callback]) +} diff --git a/deno.jsonc b/deno.jsonc index 98215d1..339ccd0 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -6,8 +6,11 @@ "imports": { "chalk": "npm:chalk@5.4.1", "file-type": "npm:file-type@21.0.0", - "webpack": "npm:webpack@5.101.2", "express": "npm:express@5.1.0", - "socket.io": "npm:socket.io@4.8.1" + "socket.io": "npm:socket.io@4.8.1", + + "webpack": "npm:webpack@5.101.2", + "@babel/preset-env": "npm:@babel/preset-env@^7.26.9", + "@babel/preset-react": "npm:@babel/preset-react@^7.26.3" } } \ No newline at end of file diff --git a/server/api/ApiManager.ts b/server/api/ApiManager.ts index 86517bd..82a31f0 100644 --- a/server/api/ApiManager.ts +++ b/server/api/ApiManager.ts @@ -1,6 +1,6 @@ import HttpServerLike from '../types/HttpServerLike.ts' import UserApi from "./UserApi.ts" -import SocketIo from "socket.io" +import * as SocketIo from "socket.io" import ApiCallbackMessage from "../types/ApiCallbackMessage.ts" import EventCallbackFunction from "../types/EventCallbackFunction.ts" diff --git a/server/main.ts b/server/main.ts index 6ea0b62..63d8556 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,10 +1,11 @@ import ApiManager from "./api/ApiManager.ts" import express from 'express' -import SocketIo from 'socket.io' +import * as SocketIo from 'socket.io' import HttpServerLike from "./types/HttpServerLike.ts" import config from './config.ts' import http from 'node:http' import https from 'node:https' +import web_packer from './web_packer.ts' const app = express() const httpServer: HttpServerLike = ( @@ -21,3 +22,9 @@ ApiManager.initEvents() ApiManager.initAllApis() httpServer.listen(config.server.listen) + +web_packer?.run((err, status) => { + if (err) throw err + console.log("前端頁面已編譯完成") + web_packer?.close(() => {}) +}) diff --git a/server/web_packer.ts b/server/web_packer.ts new file mode 100644 index 0000000..9b74ae3 --- /dev/null +++ b/server/web_packer.ts @@ -0,0 +1,29 @@ +import webpack from 'webpack' +import config from "./config.ts" +import path from 'node:path' + +export default webpack({ + mode: 'production', + devtool: 'source-map', + entry: './client/ui/App.jsx', + output: { + filename: 'bundle.js', + path: path.resolve(import.meta.dirname as string, config.data_path, 'page_compiled'), + }, + module: { + rules: [ + { + test: /\.(j|t)sx?$/, + use: { + loader: "babel-loader", + options: { + presets: [ + '@babel/preset-env', + '@babel/preset-react', + ], + }, + }, + }, + ], + }, +}) diff --git a/webpack_config.ts b/webpack_config.ts deleted file mode 100644 index c277a33..0000000 --- a/webpack_config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import webpack from 'webpack' - -export default webpack({ - -})