111
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace Application.Web.Controllers.Login
|
namespace Application.Web.Controllers.Login
|
||||||
{
|
{
|
||||||
@@ -11,7 +12,11 @@ namespace Application.Web.Controllers.Login
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class LoginController : ControllerBase
|
public class LoginController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly IHubContext<ChatHub> _hubContext;
|
||||||
|
public LoginController(IHubContext<ChatHub> hubContext)
|
||||||
|
{
|
||||||
|
_hubContext = hubContext;
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 登录接口
|
/// 登录接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -26,6 +31,7 @@ namespace Application.Web.Controllers.Login
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IPoAction> Test(string name)
|
public async Task<IPoAction> Test(string name)
|
||||||
{
|
{
|
||||||
|
await _hubContext.Clients.All.SendAsync("ReceiveMessage", "系统");
|
||||||
return PoAction.Ok(name);
|
return PoAction.Ok(name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
Service/Application.Web/Plug/ChatHub/ChatHub.cs
Normal file
26
Service/Application.Web/Plug/ChatHub/ChatHub.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
namespace Application.Web
|
||||||
|
{
|
||||||
|
public class ChatHub:Hub
|
||||||
|
{
|
||||||
|
// 客户端调用:发送消息给所有人
|
||||||
|
public async Task SendMessage(string user, string message)
|
||||||
|
{
|
||||||
|
// 推送给所有客户端(Clients.All)
|
||||||
|
await Clients.All.SendAsync("ReceiveMessage", user, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:连接/断开事件
|
||||||
|
public override async Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
|
||||||
|
await base.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||||
|
{
|
||||||
|
await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
|
||||||
|
await base.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,6 +70,8 @@ builder.Services.AddCors(options =>
|
|||||||
|
|
||||||
#endregion 配置跨域处理,允许所有来源
|
#endregion 配置跨域处理,允许所有来源
|
||||||
|
|
||||||
|
builder.Services.AddSignalR();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.Services.UseInject();//启用框架
|
app.Services.UseInject();//启用框架
|
||||||
@@ -98,8 +100,9 @@ app.UseHttpsRedirection();
|
|||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapControllers();
|
app.MapHub<ChatHub>("/chatHub");
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "default",
|
name: "default",
|
||||||
pattern: "{controller=Home}/{action=Index}/{id?}")
|
pattern: "{controller=Home}/{action=Index}/{id?}")
|
||||||
|
|||||||
133
Web/package-lock.json
generated
133
Web/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": "^10.0.0",
|
||||||
"@vant/nuxt": "^1.0.7",
|
"@vant/nuxt": "^1.0.7",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
@@ -1222,6 +1223,40 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@microsoft/signalr": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"eventsource": "^2.0.2",
|
||||||
|
"fetch-cookie": "^2.0.3",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
|
"ws": "^7.5.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@microsoft/signalr/node_modules/ws": {
|
||||||
|
"version": "7.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||||
|
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
|
||||||
@@ -4297,7 +4332,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"event-target-shim": "^5.0.0"
|
"event-target-shim": "^5.0.0"
|
||||||
@@ -5889,7 +5923,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -5915,6 +5948,15 @@
|
|||||||
"bare-events": "^2.7.0"
|
"bare-events": "^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventsource": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||||
@@ -6033,6 +6075,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fetch-cookie": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"dependencies": {
|
||||||
|
"set-cookie-parser": "^2.4.8",
|
||||||
|
"tough-cookie": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/file-uri-to-path": {
|
"node_modules/file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
@@ -7403,7 +7455,6 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": "^5.0.0"
|
"whatwg-url": "^5.0.0"
|
||||||
@@ -8798,6 +8849,27 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/psl": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/lupomontero"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/quansync": {
|
"node_modules/quansync": {
|
||||||
"version": "0.2.11",
|
"version": "0.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||||
@@ -8815,6 +8887,12 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/querystringify": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@@ -8966,6 +9044,12 @@
|
|||||||
"regexp-tree": "bin/regexp-tree"
|
"regexp-tree": "bin/regexp-tree"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
@@ -9260,6 +9344,12 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
@@ -9897,11 +9987,25 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "4.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||||
|
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"psl": "^1.1.33",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
|
"universalify": "^0.2.0",
|
||||||
|
"url-parse": "^1.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
@@ -10084,6 +10188,15 @@
|
|||||||
"@types/estree": "^1.0.0"
|
"@types/estree": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/universalify": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unplugin": {
|
"node_modules/unplugin": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
|
||||||
@@ -10344,6 +10457,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/url-parse": {
|
||||||
|
"version": "1.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||||
|
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"querystringify": "^2.1.1",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@@ -10868,7 +10991,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/webpack-virtual-modules": {
|
"node_modules/webpack-virtual-modules": {
|
||||||
@@ -10881,7 +11003,6 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": "^10.0.0",
|
||||||
"@vant/nuxt": "^1.0.7",
|
"@vant/nuxt": "^1.0.7",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
|||||||
588
Web/src/pages/home/im.vue
Normal file
588
Web/src/pages/home/im.vue
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
<template>
|
||||||
|
<div class="im-page">
|
||||||
|
<section class="hero-card">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">SignalR IM Demo</p>
|
||||||
|
<h1>即时通讯测试页</h1>
|
||||||
|
<p class="hero-text">
|
||||||
|
当前已对接服务端 `ChatHub`,默认调用 `SendMessage(user, message)`,监听 `ReceiveMessage`、
|
||||||
|
`UserConnected` 和 `UserDisconnected`。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="status-box">
|
||||||
|
<span class="status-label">连接状态</span>
|
||||||
|
<strong :class="['status-pill', `is-${status}`]">{{ statusText }}</strong>
|
||||||
|
<span v-if="connectionId" class="connection-id">连接ID:{{ connectionId }}</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-title">连接配置</div>
|
||||||
|
<div class="field-grid">
|
||||||
|
<label class="field">
|
||||||
|
<span>Hub 地址</span>
|
||||||
|
<input v-model.trim="hubUrl" type="text" placeholder="https://localhost:7198/chatHub">
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>当前昵称</span>
|
||||||
|
<input v-model.trim="nickname" type="text" placeholder="请输入发送昵称">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="field">
|
||||||
|
<span>Bearer Token</span>
|
||||||
|
<textarea
|
||||||
|
v-model.trim="accessToken"
|
||||||
|
rows="3"
|
||||||
|
placeholder="如后端开启鉴权,可在这里粘贴 token;为空则匿名连接。"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="primary-btn" :disabled="isConnecting || isConnected" @click="connectHub">连接</button>
|
||||||
|
<button class="ghost-btn" :disabled="!isConnected" @click="disconnectHub">断开</button>
|
||||||
|
<button class="ghost-btn" @click="resetToken">读取本地 Token</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-title">消息面板</div>
|
||||||
|
<div ref="messageListRef" class="message-list">
|
||||||
|
<div v-if="!messages.length" class="empty-state">还没有消息,先连接然后发送一条试试。</div>
|
||||||
|
<article
|
||||||
|
v-for="item in messages"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['message-item', item.type === 'system' ? 'is-system' : item.user === nickname ? 'is-self' : '']"
|
||||||
|
>
|
||||||
|
<div class="message-meta">
|
||||||
|
<strong>{{ item.user }}</strong>
|
||||||
|
<span>{{ item.time }}</span>
|
||||||
|
</div>
|
||||||
|
<p>{{ item.message }}</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="field">
|
||||||
|
<span>发送内容</span>
|
||||||
|
<textarea
|
||||||
|
v-model.trim="draftMessage"
|
||||||
|
rows="3"
|
||||||
|
placeholder="输入消息后点击发送"
|
||||||
|
@keydown.ctrl.enter.exact.prevent="sendMessage"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="primary-btn" :disabled="!isConnected || !draftMessage" @click="sendMessage">发送消息</button>
|
||||||
|
<button class="ghost-btn" @click="clearMessages">清空消息</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<div class="panel-title">运行日志</div>
|
||||||
|
<div class="log-list">
|
||||||
|
<p v-if="!logs.length" class="empty-state">暂无日志。</p>
|
||||||
|
<p v-for="item in logs" :key="item.id" class="log-item">{{ item.text }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HubConnection } from '@microsoft/signalr'
|
||||||
|
import { BaseConfig } from '@/config/BaseConfig'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: layout.default
|
||||||
|
})
|
||||||
|
|
||||||
|
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
||||||
|
type MessageType = 'chat' | 'system'
|
||||||
|
|
||||||
|
interface ChatMessage {
|
||||||
|
id: string
|
||||||
|
user: string
|
||||||
|
message: string
|
||||||
|
time: string
|
||||||
|
type: MessageType
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LogItem {
|
||||||
|
id: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const defaultHubUrl = `${BaseConfig.BaseUrl.replace(/\/$/, '')}/chatHub`
|
||||||
|
|
||||||
|
const hubUrl = ref(defaultHubUrl)
|
||||||
|
const nickname = ref('')
|
||||||
|
const accessToken = ref('')
|
||||||
|
const draftMessage = ref('')
|
||||||
|
const status = ref<ConnectionStatus>('disconnected')
|
||||||
|
const connectionId = ref('')
|
||||||
|
const messages = ref<ChatMessage[]>([])
|
||||||
|
const logs = ref<LogItem[]>([])
|
||||||
|
const connection = shallowRef<HubConnection | null>(null)
|
||||||
|
const messageListRef = useTemplateRef<HTMLDivElement>('messageListRef')
|
||||||
|
|
||||||
|
const isConnected = computed(() => status.value === 'connected')
|
||||||
|
const isConnecting = computed(() => status.value === 'connecting')
|
||||||
|
const statusText = computed(() => {
|
||||||
|
switch (status.value) {
|
||||||
|
case 'connecting':
|
||||||
|
return '连接中'
|
||||||
|
case 'connected':
|
||||||
|
return '已连接'
|
||||||
|
case 'error':
|
||||||
|
return '连接异常'
|
||||||
|
default:
|
||||||
|
return '未连接'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||||
|
|
||||||
|
const getNowText = () =>
|
||||||
|
new Date().toLocaleTimeString('zh-CN', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
|
||||||
|
const addLog = (text: string) => {
|
||||||
|
logs.value.unshift({
|
||||||
|
id: buildId(),
|
||||||
|
text: `[${getNowText()}] ${text}`
|
||||||
|
})
|
||||||
|
|
||||||
|
logs.value = logs.value.slice(0, 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMessage = (user: string, message: string, type: MessageType = 'chat') => {
|
||||||
|
messages.value.push({
|
||||||
|
id: buildId(),
|
||||||
|
user,
|
||||||
|
message,
|
||||||
|
time: getNowText(),
|
||||||
|
type
|
||||||
|
})
|
||||||
|
|
||||||
|
void nextTick(() => {
|
||||||
|
const target = messageListRef.value
|
||||||
|
if (target) {
|
||||||
|
target.scrollTop = target.scrollHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveHubUrl = (value: string) => {
|
||||||
|
const finalValue = value.trim() || defaultHubUrl
|
||||||
|
|
||||||
|
if (/^https?:\/\//i.test(finalValue)) {
|
||||||
|
return finalValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.client && finalValue.startsWith('/')) {
|
||||||
|
return `${window.location.origin}${finalValue}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerConnectionEvents = (hub: HubConnection) => {
|
||||||
|
hub.on('ReceiveMessage', (user: string, message: string) => {
|
||||||
|
addMessage(user || '匿名用户', message || '')
|
||||||
|
})
|
||||||
|
|
||||||
|
hub.on('UserConnected', (id: string) => {
|
||||||
|
addMessage('系统', `用户已连接:${id}`, 'system')
|
||||||
|
addLog(`收到 UserConnected 事件:${id}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
hub.on('UserDisconnected', (id: string) => {
|
||||||
|
addMessage('系统', `用户已断开:${id}`, 'system')
|
||||||
|
addLog(`收到 UserDisconnected 事件:${id}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
hub.onreconnecting((error) => {
|
||||||
|
status.value = 'connecting'
|
||||||
|
addLog(`连接重试中:${error?.message || '网络波动'}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
hub.onreconnected((id) => {
|
||||||
|
status.value = 'connected'
|
||||||
|
connectionId.value = id || hub.connectionId || ''
|
||||||
|
addLog(`重连成功:${connectionId.value || '无连接ID'}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
hub.onclose((error) => {
|
||||||
|
status.value = error ? 'error' : 'disconnected'
|
||||||
|
connectionId.value = ''
|
||||||
|
addLog(`连接关闭:${error?.message || '已主动断开'}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createConnection = async () => {
|
||||||
|
const signalR = await import('@microsoft/signalr')
|
||||||
|
|
||||||
|
const hub = new signalR.HubConnectionBuilder()
|
||||||
|
.withUrl(resolveHubUrl(hubUrl.value), {
|
||||||
|
withCredentials: false,
|
||||||
|
accessTokenFactory: () => accessToken.value.trim()
|
||||||
|
})
|
||||||
|
.withAutomaticReconnect()
|
||||||
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
registerConnectionEvents(hub)
|
||||||
|
return hub
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectHub = async () => {
|
||||||
|
if (isConnecting.value || isConnected.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nickname.value.trim()) {
|
||||||
|
nickname.value = `游客${Math.floor(Math.random() * 1000)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.value = 'connecting'
|
||||||
|
addLog(`开始连接:${resolveHubUrl(hubUrl.value)}`)
|
||||||
|
|
||||||
|
if (connection.value) {
|
||||||
|
await connection.value.stop()
|
||||||
|
connection.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hub = await createConnection()
|
||||||
|
await hub.start()
|
||||||
|
|
||||||
|
connection.value = hub
|
||||||
|
connectionId.value = hub.connectionId || ''
|
||||||
|
status.value = 'connected'
|
||||||
|
|
||||||
|
addLog(`连接成功:${connectionId.value || '未返回连接ID'}`)
|
||||||
|
addMessage('系统', 'SignalR 已连接,可以开始发送消息。', 'system')
|
||||||
|
} catch (error) {
|
||||||
|
status.value = 'error'
|
||||||
|
connectionId.value = ''
|
||||||
|
connection.value = null
|
||||||
|
|
||||||
|
const message = error instanceof Error ? error.message : '未知错误'
|
||||||
|
addLog(`连接失败:${message}`)
|
||||||
|
addMessage('系统', `连接失败:${message}`, 'system')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disconnectHub = async () => {
|
||||||
|
if (!connection.value) {
|
||||||
|
status.value = 'disconnected'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await connection.value.stop()
|
||||||
|
connection.value = null
|
||||||
|
status.value = 'disconnected'
|
||||||
|
connectionId.value = ''
|
||||||
|
addMessage('系统', '已主动断开连接。', 'system')
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendMessage = async () => {
|
||||||
|
const message = draftMessage.value.trim()
|
||||||
|
if (!connection.value || !isConnected.value || !message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.value.invoke('SendMessage', nickname.value.trim(), message)
|
||||||
|
addLog(`发送成功:${message}`)
|
||||||
|
draftMessage.value = ''
|
||||||
|
} catch (error) {
|
||||||
|
const text = error instanceof Error ? error.message : '未知错误'
|
||||||
|
addLog(`发送失败:${text}`)
|
||||||
|
addMessage('系统', `发送失败:${text}`, 'system')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearMessages = () => {
|
||||||
|
messages.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetToken = () => {
|
||||||
|
if (!import.meta.client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken.value = userStore.token || localStorage.getItem('token') || ''
|
||||||
|
addLog(accessToken.value ? '已读取本地 token。' : '未读取到本地 token。')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nickname.value = userStore.userNickname || `游客${Math.floor(Math.random() * 1000)}`
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (connection.value) {
|
||||||
|
void connection.value.stop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.im-page,
|
||||||
|
.im-page div,
|
||||||
|
.im-page section,
|
||||||
|
.im-page article,
|
||||||
|
.im-page p,
|
||||||
|
.im-page h1,
|
||||||
|
.im-page span,
|
||||||
|
.im-page strong {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.im-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px 16px 40px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(217, 83, 79, 0.14), transparent 32%),
|
||||||
|
linear-gradient(180deg, #f7efe4 0%, #f4f0ea 100%);
|
||||||
|
color: #2a221d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card,
|
||||||
|
.panel {
|
||||||
|
width: min(960px, 100%);
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
border: 1px solid rgba(71, 50, 38, 0.14);
|
||||||
|
border-radius: 20px;
|
||||||
|
background: rgba(255, 250, 244, 0.92);
|
||||||
|
box-shadow: 0 16px 36px rgba(54, 38, 29, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #8d5d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-text {
|
||||||
|
margin-top: 10px;
|
||||||
|
max-width: 640px;
|
||||||
|
color: #665246;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-box {
|
||||||
|
display: flex;
|
||||||
|
min-width: 180px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label,
|
||||||
|
.connection-id,
|
||||||
|
.field span,
|
||||||
|
.panel-title {
|
||||||
|
color: #7d6557;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 84px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pill.is-disconnected,
|
||||||
|
.status-pill.is-error {
|
||||||
|
background: rgba(217, 83, 79, 0.14);
|
||||||
|
color: #b53d38;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pill.is-connecting {
|
||||||
|
background: rgba(254, 94, 8, 0.14);
|
||||||
|
color: #d46514;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pill.is-connected {
|
||||||
|
background: rgba(25, 135, 84, 0.14);
|
||||||
|
color: #198754;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input,
|
||||||
|
.field textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(98, 77, 65, 0.18);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: #fffdfa;
|
||||||
|
color: #2a221d;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field input:focus,
|
||||||
|
.field textarea:focus {
|
||||||
|
border-color: #d46514;
|
||||||
|
box-shadow: 0 0 0 3px rgba(254, 94, 8, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn,
|
||||||
|
.ghost-btn {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, opacity 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
background: linear-gradient(135deg, #fe5e08 0%, #d9534f 100%);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghost-btn {
|
||||||
|
background: rgba(89, 70, 59, 0.08);
|
||||||
|
color: #4a3a31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn:hover,
|
||||||
|
.ghost-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn:disabled,
|
||||||
|
.ghost-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.45;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list,
|
||||||
|
.log-list {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid rgba(98, 77, 65, 0.14);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.76);
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(98, 77, 65, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item + .message-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item.is-self {
|
||||||
|
border-color: rgba(25, 135, 84, 0.22);
|
||||||
|
background: rgba(25, 135, 84, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item.is-system {
|
||||||
|
border-style: dashed;
|
||||||
|
background: rgba(254, 94, 8, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #7d6557;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item p,
|
||||||
|
.log-item,
|
||||||
|
.empty-state {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #45352d;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item + .log-item {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-card,
|
||||||
|
.field-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-box {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,19 +1,88 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-home"></div>
|
<div class="page-home">
|
||||||
</template>
|
<section class="home-card">
|
||||||
|
<p class="page-tag">Home</p>
|
||||||
|
<h1>前端调试入口</h1>
|
||||||
|
<p class="page-desc">
|
||||||
|
这里先放一个即时通讯测试页入口,方便直接验证 SignalR 连接、收发消息和连接状态。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<NuxtLink class="entry-link" to="/home/im">
|
||||||
|
进入 IM Demo
|
||||||
|
</NuxtLink>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: layout.default
|
layout: layout.default
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.page-home,
|
||||||
|
.page-home div,
|
||||||
|
.page-home section,
|
||||||
|
.page-home p,
|
||||||
|
.page-home h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.page-home {
|
.page-home {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
padding: 24px 16px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(254, 94, 8, 0.1), transparent 38%),
|
||||||
|
linear-gradient(180deg, #f5f1ea 0%, #f8f6f2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-card {
|
||||||
|
width: min(720px, 100%);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: rgba(255, 252, 247, 0.95);
|
||||||
|
border: 1px solid rgba(70, 54, 45, 0.12);
|
||||||
|
box-shadow: 0 16px 40px rgba(48, 40, 40, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tag {
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #8d5d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #2a221d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #665246;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 140px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: linear-gradient(135deg, #fe5e08 0%, #d9534f 100%);
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-link:hover,
|
||||||
|
.entry-link:focus,
|
||||||
|
.entry-link:active {
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #db5208 0%, #bf4744 100%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
8
Web/src/types/user.ts
Normal file
8
Web/src/types/user.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface IUserInfo {
|
||||||
|
id?: number
|
||||||
|
username?: string
|
||||||
|
nickname?: string
|
||||||
|
avatar?: string
|
||||||
|
role?: string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user