11 Commits
v0.2.1 ... dev

Author SHA1 Message Date
9fb248dff0 wip: v0.2.8 dialog 美化 2025-06-18 23:01:08 +08:00
1b4ba1cb61 wip: v0.2.7(发送消息) 2025-06-17 22:45:22 +08:00
e5f7b5e6dc wip: 0.2.8
发送成功
2025-05-30 16:24:42 +08:00
21287e0874 wip: 0.2.7
实现了 rtc 握手和打开数据通道
2025-05-26 17:40:06 +08:00
5bc695bde3 wip: v0.2.6 2025-05-26 09:37:23 +08:00
b8645a68ed wip: 0.2.5
还未实现 rtc 握手
2025-05-23 18:01:16 +08:00
013670b78f wip: 0.2.4
还未实现 rtc 握手
2025-05-22 17:57:36 +08:00
16e9d663f4 wip: 0.2.3
1. websocket hook
  2. rtc init ok
2025-05-20 18:04:37 +08:00
becbc137c5 Merge tag 'v0.1.4' 2025-05-16 16:52:17 +08:00
d41d516f19 fix: 0.1.4
All checks were successful
/ build ushare (push) Successful in 36s
/ clean (push) Successful in 0s
1. meta clean goroutine walk error
  2. clean interval to args(--clean)
2025-05-16 16:50:22 +08:00
0a24393dcb fix: 0.1.4
All checks were successful
/ build ushare (push) Successful in 51s
/ clean (push) Successful in 0s
1. meta clean goroutine walk error
  2. clean interval to args(--clean)
2025-05-16 16:48:28 +08:00
16 changed files with 145 additions and 254 deletions

View File

@ -25,7 +25,8 @@ COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
COPY --from=backend-builder /app/ushare /usr/local/bin/ushare
# 配置 Nginx
COPY deployment/nginx.conf /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf
COPY deployment/nginx.conf /etc/nginx/conf.d
COPY deployment/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

View File

@ -1,50 +1,23 @@
user root;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
server {
listen 80;
events {
worker_connections 1024;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
location /api {
proxy_pass http://localhost:9119;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:9119;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /ushare {
proxy_pass http://localhost:9119;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_max_temp_file_size 0;
proxy_request_buffering off;
proxy_buffering off;
client_max_body_size 5M;
}
location /ushare {
proxy_pass http://localhost:9119;
const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]})
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -16,6 +16,7 @@ export const useFileUpload = () => {
setProgress(0);
try {
console.log(`[D] api.Upload: upload file = ${file.name}, size = ${file.size}`, file);
const url = `/api/ushare/${file.name}`;
// 1. 初始化上传
@ -25,7 +26,7 @@ export const useFileUpload = () => {
});
if (!res1.ok) {
console.log(`[W] upload: put file not ok, status = ${res1.status}, res = ${await res1.text()}`)
console.log(`[D] upload: put file not ok, status = ${res1.status}, res = ${await res1.text()}`)
if (res1.status === 401) {
window.location.href = "/login?next=/share"
return ""

View File

@ -17,8 +17,7 @@ const useClass = createUseStyles({
background: "rgba(0, 0, 0, 0.5)",
backdropFilter: "blur(2px)"
},
background: "rgba(212,212,212,0.85)",
backdropFilter: "blur(8px)",
background: "rgba(255, 255, 255, 0.4)",
},
dialog_content: {
padding: "1.5rem",

View File

@ -50,6 +50,7 @@ export const Drawer: React.FC<DrawerProps> = ({
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
console.log('[D] escape close:', close)
close()
}
};

View File

@ -1,87 +1,6 @@
import React, {useState} from 'react';
import {Dialog} from "../../../component/dialog/dialog.tsx";
import {ReceivedMessage} from "./types.ts";
import {createUseStyles} from "react-jss";
const useStyles = createUseStyles({
root: {
marginBottom: '1rem',
},
sender: {
margin: '0 0 0.5rem 0',
fontSize: '0.9rem',
color: '#666',
},
msgBox: {
background: 'rgba(255,255,255,0.96)',
color: '#222',
padding: '1rem',
borderRadius: '4px',
border: '1px solid #ddd',
maxHeight: '400px',
minHeight: '80px',
overflowY: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
},
downloadLink: {
color: '#007aff',
textDecoration: 'underline',
},
btnRow: {
display: 'flex',
flexDirection: 'row',
gap: '0.5rem',
justifyContent: 'flex-end',
alignItems: 'center',
marginTop: '8px',
},
copySuccess: {
color: '#28a745',
fontSize: '0.9rem',
marginRight: '0.5rem',
animation: 'fadeIn 0.3s ease-in',
},
copyBtn: {
padding: '8px 16px',
background: '#007aff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s',
'&:hover': {
background: '#0056b3',
},
},
copyBtnSuccess: {
background: '#28a745',
},
closeBtn: {
padding: '8px 16px',
background: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s',
},
progressBar: {
width: '100%',
height: 8,
background: '#eee',
borderRadius: 4,
margin: '12px 0',
overflow: 'hidden',
},
progressInner: {
height: '100%',
background: '#4dabf7',
transition: 'width 0.3s',
},
});
interface MessageDialogProps {
open: boolean;
@ -91,14 +10,16 @@ interface MessageDialogProps {
export const MessageDialog: React.FC<MessageDialogProps> = ({open, message, onClose}) => {
const [copySuccess, setCopySuccess] = useState(false);
const classes = useStyles();
const handleCopyMessage = () => {
if (message) {
navigator.clipboard.writeText(message.text || '').then(() => {
console.log('消息已复制到剪贴板');
setCopySuccess(true);
// 2秒后隐藏成功提示
setTimeout(() => setCopySuccess(false), 2000);
}).catch(() => {
}).catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动复制');
});
}
@ -111,49 +32,82 @@ export const MessageDialog: React.FC<MessageDialogProps> = ({open, message, onCl
onClose={onClose}
footer={false}
>
<div className={classes.root}>
<p className={classes.sender}>
<div style={{ marginBottom: '1rem' }}>
<p style={{ margin: '0 0 0.5rem 0', fontSize: '0.9rem', color: '#666' }}>
: {message?.sender}
</p>
<div className={classes.msgBox}>
<div style={{
background: '#f5f5f5',
padding: '1rem',
borderRadius: '4px',
border: '1px solid #ddd',
maxHeight: '200px',
overflowY: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
}}>
{message?.isFile ? (
<>
<div>📎 : {message.fileName} ({message.fileSize ? (message.fileSize/1024).toFixed(1) : ''} KB)</div>
{message.receiving ? (
<>
<div style={{fontSize: '0.95em', color: '#888'}}>... {Math.round((message.progress||0)*100)}%</div>
<div className={classes.progressBar}>
<div className={classes.progressInner} style={{width: `${Math.round((message.progress||0)*100)}%`}} />
</div>
</>
) : (
<a href={message.fileBlobUrl} download={message.fileName} className={classes.downloadLink}>
</a>
)}
<a href={message.fileBlobUrl} download={message.fileName} style={{color:'#007aff',textDecoration:'underline'}}>
</a>
</>
) : (
message?.text
)}
</div>
</div>
<div className={classes.btnRow}>
<div style={{ display: 'flex', flexDirection: 'row', gap: '0.5rem', justifyContent: 'flex-end', alignItems: 'center', marginTop: '8px' }}>
{copySuccess && (
<span className={classes.copySuccess}>
<span style={{
color: '#28a745',
fontSize: '0.9rem',
marginRight: '0.5rem',
animation: 'fadeIn 0.3s ease-in'
}}>
</span>
)}
{!message?.isFile && (
<button
onClick={handleCopyMessage}
className={copySuccess ? `${classes.copyBtn} ${classes.copyBtnSuccess}` : classes.copyBtn}
style={{
padding: '8px 16px',
background: copySuccess ? '#28a745' : '#007aff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
if (!copySuccess) {
e.currentTarget.style.backgroundColor = '#0056b3';
}
}}
onMouseLeave={(e) => {
if (!copySuccess) {
e.currentTarget.style.backgroundColor = '#007aff';
}
}}
>
{copySuccess ? '已复制' : '复制消息'}
</button>
)}
<button
onClick={onClose}
className={classes.closeBtn}
style={{
padding: '8px 16px',
background: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
transition: 'all 0.2s'
}}
>
</button>

View File

@ -1,4 +1,5 @@
import { WSMessage, ReceivedMessage} from "./types.ts";
import {Resp} from "../../../interface/response.ts";
import {Client, WSMessage, ReceivedMessage} from "./types.ts";
import {useLocalStore} from "../../../store/local.ts";
// 文件接收缓存
@ -8,7 +9,7 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
if (chunk.type === 'file') {
// 文件元信息,初始化缓存
fileReceiveCache[chunk.name + '_' + chunk.timestamp] = {
chunks: new Array(chunk.totalChunks),
chunks: [],
total: chunk.totalChunks,
received: 0,
name: chunk.name,
@ -16,16 +17,6 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
sender: chunk.sender,
timestamp: chunk.timestamp
};
// 首次弹出进度
onFileReceived({
sender: chunk.sender,
timestamp: chunk.timestamp,
fileName: chunk.name,
fileSize: chunk.size,
isFile: true,
progress: 0,
receiving: true
});
} else if (chunk.type === 'file-chunk') {
const key = chunk.name + '_' + chunk.timestamp;
const cache = fileReceiveCache[key];
@ -34,49 +25,21 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
const uint8 = new Uint8Array(chunk.data);
cache.chunks[chunk.chunkIndex] = uint8.buffer;
cache.received++;
// 实时回调进度
if (cache.received < cache.total) {
// 全部收到
if (cache.received === cache.total) {
const blob = new Blob(cache.chunks);
const url = URL.createObjectURL(blob);
onFileReceived({
sender: cache.sender,
timestamp: cache.timestamp,
fileName: cache.name,
fileSize: cache.size,
isFile: true,
progress: cache.received / cache.total,
receiving: true
fileBlobUrl: url,
isFile: true
});
delete fileReceiveCache[key];
}
}
} else if (chunk.type === 'file-end') {
const key = chunk.name + '_' + chunk.timestamp;
const cache = fileReceiveCache[key];
if (cache && cache.received === cache.total) {
const blob = new Blob(cache.chunks);
const url = URL.createObjectURL(blob);
onFileReceived({
sender: cache.sender,
timestamp: cache.timestamp,
fileName: cache.name,
fileSize: cache.size,
fileBlobUrl: url,
isFile: true,
progress: 1,
receiving: false
});
delete fileReceiveCache[key];
} else if (cache) {
// 分块未齐全,提示异常
onFileReceived({
sender: cache.sender,
timestamp: cache.timestamp,
fileName: cache.name,
fileSize: cache.size,
isFile: true,
progress: cache.received / cache.total,
receiving: true,
text: '文件分块未齐全,接收失败'
});
}
}
};
@ -97,18 +60,21 @@ export class RTCHandler {
// 更新回调函数的方法
updateCallbacks = (newCallbacks: RTCHandlerCallbacks) => {
console.log('[D] Updating RTC handler callbacks');
this.callbacks = newCallbacks;
};
setupDataChannel = async (ch: RTCDataChannel, type: 'sender' | 'receiver') => {
console.log(`[D] Setting up data channel for type: ${type}`);
ch.onopen = () => {
console.log(`[D] 通道已打开!类型: ${type}`);
console.log('[D] Calling onChannelOpen callback with type:', type);
this.callbacks.onChannelOpen(type);
useLocalStore.getState().setChannel(ch);
};
ch.onmessage = (e) => {
// console.log('[D] Received message:', e.data);
console.log('[D] Received message:', e.data);
try {
const data = JSON.parse(e.data);
if (data.type === 'message') {
@ -118,9 +84,10 @@ export class RTCHandler {
timestamp: Date.now(),
sender: data.sender || '未知用户'
};
console.log('[D] Calling onMessageReceived callback with message:', message);
this.callbacks.onMessageReceived(message);
} else if (data.type === 'file' || data.type === 'file-chunk' || data.type === 'file-end') {
// 处理文件相关消息
} else if (data.type === 'file') {
// 处理文件消息
handleFileChunk(data, this.callbacks.onMessageReceived);
}
} catch (error) {
@ -130,23 +97,27 @@ export class RTCHandler {
timestamp: Date.now(),
sender: '未知用户'
};
console.log('[D] Calling onMessageReceived callback with plain text message:', message);
this.callbacks.onMessageReceived(message);
}
};
ch.onclose = () => {
console.log('[D] 通道关闭');
console.log('[D] Calling onChannelClose callback');
this.callbacks.onChannelClose();
useLocalStore.getState().setChannel();
};
};
handleBubbleClick = async (bubbleId: string, currentUserId: string) => {
console.log(`[D] click id = ${bubbleId}`);
const current_rtc = this.rtcRef.current;
if (!current_rtc) return;
current_rtc.onnegotiationneeded = async () => {
const offer = await current_rtc.createOffer();
console.log('[D] offer created', offer);
await current_rtc.setLocalDescription(offer);
const data = {
id: bubbleId,
@ -161,6 +132,7 @@ export class RTCHandler {
};
current_rtc.onicecandidate = async (e) => {
console.log('[D] on candidate ', e);
await fetch('/api/ulocal/candidate', {
method: 'POST',
headers: {"Content-Type": "application/json"},
@ -176,7 +148,7 @@ export class RTCHandler {
let current_id: string;
let current_rtc: RTCPeerConnection | null;
const msg = JSON.parse(e.data) as WSMessage;
// console.log('[D] ws event msg =', msg);
console.log('[D] ws event msg =', msg);
switch (msg.type) {
case "enter":
@ -241,6 +213,7 @@ export class RTCHandler {
return;
}
if (!candidate_data.candidate) {
console.log('[W] candidate data null');
return;
}
await current_rtc.addIceCandidate(candidate_data.candidate);
@ -250,8 +223,10 @@ export class RTCHandler {
sendMessage = (msg: string, files: File[], senderName: string) => {
const ch = useLocalStore.getState().channel;
console.log('[D] ready to send:', msg, files, ch);
const CHUNK_SIZE = 64 * 1024; // 64KB
const BUFFERED_AMOUNT_THRESHOLD = 1 * 1024 * 1024; // 1MB
if (ch && ch.readyState === 'open') {
if (msg.trim()) {
// 发送文本消息
@ -279,28 +254,23 @@ export class RTCHandler {
};
ch.send(JSON.stringify(fileData));
ch.bufferedAmountLowThreshold = BUFFERED_AMOUNT_THRESHOLD;
let offset = 0;
let chunkIndex = 0;
const reader = new FileReader();
const sendNextChunk = () => {
if (offset >= file.size) {
// 分块全部发送完毕,发送 file-end
ch.send(JSON.stringify({
type: 'file-end',
name: file.name,
timestamp,
}));
return;
}
if (ch.bufferedAmount > BUFFERED_AMOUNT_THRESHOLD) {
function sendNextChunk() {
if (offset >= file.size) return;
if (ch && ch.bufferedAmount && ch.bufferedAmount > BUFFERED_AMOUNT_THRESHOLD) {
// 等待缓冲区变低
ch.addEventListener('bufferedamountlow', sendNextChunk, { once: true });
return;
}
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.onload = (e) => {
if (e.target?.result) {
ch.send(JSON.stringify({
ch?.send(JSON.stringify({
type: 'file-chunk',
name: file.name,
timestamp,
@ -314,7 +284,7 @@ export class RTCHandler {
}
};
reader.readAsArrayBuffer(slice);
};
}
sendNextChunk();
});
}

View File

@ -6,12 +6,11 @@ interface SendDialogProps {
open: boolean;
onSend: (msg: string, files: File[]) => void;
onClose: () => void;
name: string;
}
export const SendDialog: React.FC<SendDialogProps> = ({open, onSend, onClose, name}) => {
export const SendDialog: React.FC<SendDialogProps> = ({open, onSend, onClose}) => {
return (
<Dialog open={open} title={`发送消息给${name}`} onClose={onClose}>
<Dialog open={open} title="发送消息" onClose={onClose}>
<Sender onSend={onSend} />
</Dialog>
);

View File

@ -150,10 +150,17 @@ export const Sender: React.FC<SenderProps> = ({onSend}) => {
</div>
<div className={classes.buttons}>
<input type="file" ref={fileInputRef} hidden onChange={handleFileSelect}/>
<button className={classes.action_btn} onClick={() => {
<button
className={classes.action_btn}
style={{ opacity: 0.5, cursor: 'not-allowed' }}
onClick={() => { message.warning('暂不支持文件发送') }}
>
📁
</button>
{/* <button className={classes.action_btn} onClick={() => {
fileInputRef.current && fileInputRef.current.click()
}}>📁 选择文件
</button>
</button> */}
<button className={classes.action_btn} onClick={handleSubmit}> </button>
</div>
</div>

View File

@ -33,6 +33,4 @@ export interface ReceivedMessage {
fileSize?: number;
fileBlobUrl?: string;
isFile?: boolean;
progress?: number; // 0-1
receiving?: boolean; // 是否正在接收
}

View File

@ -55,15 +55,14 @@ const useClass = createUseStyles({
export const LocalSharing: React.FC = () => {
const classes = useClass();
const {id, name, set, } = useLocalStore();
const [_rtc, setRTC] = useState<RTCPeerConnection>();
const {id, name, set, setChannel} = useLocalStore();
const [rtc, setRTC] = useState<RTCPeerConnection>();
const rtcRef = useRef<RTCPeerConnection | null>(null);
const [clients, setClients] = useState<Client[]>([]);
const {connect, close} = useWebsocket({});
const [open, setOpen] = useState<{ send: boolean; receive: boolean }>({send: false, receive: false});
const [receivedMessage, setReceivedMessage] = useState<ReceivedMessage | null>(null);
const [showMessageDialog, setShowMessageDialog] = useState(false);
const [receivingFile, setReceivingFile] = useState<ReceivedMessage | null>(null);
// RTC处理器的回调函数 - 使用useCallback确保稳定性
const onChannelOpen = useCallback((type: 'sender' | 'receiver') => {
@ -72,16 +71,9 @@ export const LocalSharing: React.FC = () => {
}, []);
const onMessageReceived = useCallback((message: ReceivedMessage) => {
if (message.isFile && message.receiving) {
setReceivingFile(message);
} else if (message.isFile && !message.receiving) {
setReceivingFile(null);
setReceivedMessage(message);
setShowMessageDialog(true);
} else {
setReceivedMessage(message);
setShowMessageDialog(true);
}
console.log('[D] Message received:', message);
setReceivedMessage(message);
setShowMessageDialog(true);
}, []);
const onChannelClose = useCallback(() => {
@ -101,6 +93,7 @@ export const LocalSharing: React.FC = () => {
// 更新RTC处理器的回调函数
useEffect(() => {
if (rtcHandlerRef.current) {
console.log('[D] Updating RTC handler callbacks');
rtcHandlerRef.current.updateCallbacks(rtcCallbacks);
}
}, [rtcCallbacks]);
@ -115,6 +108,7 @@ export const LocalSharing: React.FC = () => {
const handleWSEvent = async (e: MessageEvent) => {
const msgData = JSON.parse(e.data);
console.log('[D] ws event msg =', msgData);
if (msgData.type === "enter" || msgData.type === "leave") {
await updateClients();
@ -135,6 +129,7 @@ export const LocalSharing: React.FC = () => {
const handleBubbleClick = async (bubble: any) => {
setOpen({send: true, receive: false});
console.log('[D] Bubble clicked:', bubble.id, 'Current RTC handler:', rtcHandlerRef.current);
if (rtcHandlerRef.current) {
try {
await rtcHandlerRef.current.handleBubbleClick(bubble.id, id);
@ -170,6 +165,7 @@ export const LocalSharing: React.FC = () => {
const response = await fetch('/api/ulocal/register', {method: 'POST'});
const data = ((await response.json()) as Resp<{ id: string; name: string }>).data;
set(data.id, data.name);
console.log(`[D] register id = ${data.id}`);
connect(`/api/ulocal/ws?id=${data.id}`, {fn: handleWSEvent});
await updateClients();
@ -178,6 +174,7 @@ export const LocalSharing: React.FC = () => {
setRTC(_rtc);
// 在RTC连接创建后立即创建处理器实例
console.log('[D] Creating RTC handler after connection setup');
rtcHandlerRef.current = new RTCHandler(rtcRef, rtcCallbacks);
return () => {
@ -192,14 +189,6 @@ export const LocalSharing: React.FC = () => {
const bubbles = generateBubbles(clients, id);
useEffect(() => {
if (receivingFile && receivingFile.isFile && !receivingFile.receiving) {
setReceivingFile(null);
setReceivedMessage(receivingFile);
setShowMessageDialog(true);
}
}, [receivingFile]);
return (
<div className={classes.container}>
<CloudBackground/>
@ -220,17 +209,8 @@ export const LocalSharing: React.FC = () => {
open={open.send}
onSend={handleSend}
onClose={() => setOpen({send: false, receive: false})}
name={name}
/>
{/* 文件接收进度弹窗 */}
{receivingFile && (
<MessageDialog
open={true}
message={receivingFile}
onClose={() => setReceivingFile(null)}
/>
)}
<MessageDialog
open={showMessageDialog}

View File

@ -136,6 +136,7 @@ const PanelLeftUpload: React.FC<{ set_code: (code:string) => void }> = ({set_cod
}
function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log('[D] onFileChange: e =', e)
setFile(e.currentTarget.files ? e.currentTarget.files[0] : null)
}

View File

@ -42,6 +42,7 @@ export const PanelRight = () => {
async function onFetchFile() {
const url = `/ushare/${code}`
console.log('[D] onFetchFile: url =', url)
const link = document.createElement('a');
link.href = url;
document.body.appendChild(link);

View File

@ -1,12 +1,13 @@
import {useState} from "react";
import {createUseStyles} from "react-jss";
import {Dialog} from "../../component/dialog/dialog.tsx";
const useClass = createUseStyles({
container: {}
})
export const TestPage = () => {
const classes = useClass()
const [_open, setOpen] = useState<boolean>(false)
const [open, setOpen] = useState<boolean>(false)
const handleOpen = () => {
setOpen(true)
@ -14,5 +15,8 @@ export const TestPage = () => {
return <div className={classes.container}>
<button onClick={handleOpen}>open</button>
<Dialog open={open} title="hello world" >
<input />
</Dialog>
</div>
}

View File

@ -8,7 +8,7 @@ export interface LocalStore {
setChannel: (chan?: RTCDataChannel) => void;
}
export const useLocalStore = create<LocalStore>()((_set, _get) => ({
export const useLocalStore = create<LocalStore>()((_set, get) => ({
id: '',
name: '',
set: (id: string, name: string) => {

View File

@ -167,6 +167,8 @@
const text = inputBox.value;
const files = fileInput.files;
console.log('文本内容:', text);
console.log('发送文件:', Array.from(files));
// 清空内容
inputBox.value = '';