Skip to content

Commit c625462

Browse files
committed
feat(ws): added WebSocket examples and service scripts
1 parent b6cea4a commit c625462

File tree

16 files changed

+733
-74
lines changed

16 files changed

+733
-74
lines changed

CHANGELOG.zh_CN.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- 新增图标选择器组件
1111
- 新增修改密码界面
1212
- 新增部门管理示例界面
13+
- 新增 WebSocket 示例和服务脚本
1314

1415
### ⚡ Performance Improvements
1516

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"qrcode": "^1.4.4",
4343
"sortablejs": "^1.13.0",
4444
"vditor": "^3.8.1",
45-
"vue": "3.0.5",
45+
"vue": "^3.0.7",
4646
"vue-i18n": "^9.0.0",
4747
"vue-router": "^4.0.4",
4848
"vue-types": "^3.0.2",
@@ -73,7 +73,7 @@
7373
"@vitejs/plugin-legacy": "^1.3.1",
7474
"@vitejs/plugin-vue": "^1.1.5",
7575
"@vitejs/plugin-vue-jsx": "^1.1.2",
76-
"@vue/compiler-sfc": "3.0.5",
76+
"@vue/compiler-sfc": "^3.0.7",
7777
"autoprefixer": "^10.2.4",
7878
"commitizen": "^4.2.3",
7979
"conventional-changelog-cli": "^2.1.1",

src/components/SimpleMenu/src/index.less

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
&-tag {
3737
position: absolute;
38-
top: calc(50% - 10px);
38+
top: calc(50% - 8px);
3939
right: 30px;
4040
display: inline-block;
4141
padding: 2px 3px;

src/locales/lang/en/routes/demo/feat.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export default {
1616
tab1: 'Tab with parameter 1',
1717
tab2: 'Tab with parameter 2',
1818

19+
ws: 'Websocket test',
20+
1921
breadcrumb: 'Breadcrumbs',
2022
breadcrumbFlat: 'Flat Mode',
2123
breadcrumbFlatDetail: 'Flat mode details',

src/locales/lang/zh_CN/routes/demo/feat.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export default {
1616
tab1: 'Tab带参1',
1717
tab2: 'Tab带参2',
1818

19+
ws: 'websocket测试',
20+
1921
breadcrumb: '面包屑导航',
2022
breadcrumbFlat: '平级模式',
2123
breadcrumbFlatDetail: '平级详情',

src/router/menus/modules/demo/feat.ts

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ const menu: MenuModule = {
1818
content: 'new',
1919
},
2020
},
21+
{
22+
path: 'ws',
23+
name: t('routes.demo.feat.ws'),
24+
tag: {
25+
content: 'new',
26+
},
27+
},
2128
{
2229
path: 'tabs',
2330
name: t('routes.demo.feat.tabs'),

src/router/routes/modules/demo/feat.ts

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ const feat: AppRouteModule = {
2121
title: t('routes.demo.feat.icon'),
2222
},
2323
},
24+
{
25+
path: 'ws',
26+
name: 'WebSocket',
27+
component: () => import('/@/views/demo/feat/ws/index.vue'),
28+
meta: {
29+
title: t('routes.demo.feat.ws'),
30+
},
31+
},
2432
{
2533
path: 'tabs',
2634
name: 'TabsDemo',

src/views/demo/feat/ws/index.vue

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<template>
2+
<PageWrapper title="WebSocket 示例">
3+
<div class="flex">
4+
<div class="w-1/3 bg-white p-4">
5+
<div class="flex items-center">
6+
<span class="text-lg font-medium mr-4"> 连接状态: </span>
7+
<Tag :color="getTagColor">{{ status }}</Tag>
8+
</div>
9+
<hr class="my-4" />
10+
11+
<div class="flex">
12+
<a-input v-model:value="server" disabled>
13+
<template #addonBefore> 服务地址 </template>
14+
</a-input>
15+
<a-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle">
16+
{{ getIsOpen ? '关闭连接' : '开启连接' }}
17+
</a-button>
18+
</div>
19+
<p class="text-lg font-medium mt-4">设置</p>
20+
<hr class="my-4" />
21+
22+
<InputTextArea
23+
placeholder="需要发送到服务器的内容"
24+
:disabled="!getIsOpen"
25+
v-model:value="sendValue"
26+
allowClear
27+
/>
28+
29+
<a-button type="primary" block class="mt-4" :disabled="!getIsOpen" @click="handlerSend">
30+
发送
31+
</a-button>
32+
</div>
33+
34+
<div class="w-2/3 bg-white ml-4 p-4">
35+
<span class="text-lg font-medium mr-4"> 消息记录: </span>
36+
<hr class="my-4" />
37+
38+
<div class="max-h-80 overflow-auto">
39+
<ul>
40+
<li v-for="item in getList" class="border-b-1 mt-2" :key="item.time">
41+
<div class="flex items-center">
42+
<span class="mr-2 text-primary font-medium">收到消息:</span>
43+
<span>{{ formatToDateTime(item.time) }}</span>
44+
</div>
45+
<div>
46+
{{ item.res }}
47+
</div>
48+
</li>
49+
</ul>
50+
</div>
51+
</div>
52+
</div>
53+
</PageWrapper>
54+
</template>
55+
<script lang="ts">
56+
import { defineComponent, reactive, watchEffect, computed, toRefs } from 'vue';
57+
import { Alert, Tag, Input } from 'ant-design-vue';
58+
59+
import { PageWrapper } from '/@/components/Page';
60+
61+
import { useWebSocket } from '@vueuse/core';
62+
63+
import { formatToDateTime } from '/@/utils/dateUtil';
64+
export default defineComponent({
65+
components: {
66+
PageWrapper,
67+
[Input.name]: Input,
68+
InputTextArea: Input.TextArea,
69+
Alert,
70+
Tag,
71+
},
72+
setup() {
73+
const state = reactive({
74+
server: 'ws://localhost:3380/test',
75+
sendValue: '',
76+
recordList: [] as { id: number; time: number; res: string }[],
77+
});
78+
79+
const { status, data, send, close, open } = useWebSocket(state.server, {
80+
autoReconnect: true,
81+
heartbeat: true,
82+
});
83+
84+
watchEffect(() => {
85+
if (data.value) {
86+
try {
87+
const res = JSON.parse(data.value);
88+
state.recordList.push(res);
89+
} catch (error) {
90+
state.recordList.push({
91+
res: data.value,
92+
id: Math.ceil(Math.random() * 1000),
93+
time: new Date().getTime(),
94+
});
95+
}
96+
}
97+
});
98+
99+
const getIsOpen = computed(() => status.value === 'OPEN');
100+
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red'));
101+
102+
const getList = computed(() => {
103+
return [...state.recordList].reverse();
104+
});
105+
106+
function handlerSend() {
107+
send(state.sendValue);
108+
state.sendValue = '';
109+
}
110+
111+
function toggle() {
112+
if (getIsOpen.value) {
113+
close();
114+
} else {
115+
open();
116+
}
117+
}
118+
return {
119+
status,
120+
formatToDateTime,
121+
...toRefs(state),
122+
handlerSend,
123+
getList,
124+
toggle,
125+
getIsOpen,
126+
getTagColor,
127+
};
128+
},
129+
});
130+
</script>

test/upload-server/README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ Simple file upload service for testing file upload components.
88

99
cd ./test/upload-server
1010

11-
// upload dir
12-
mkdir static
13-
1411
yarn install
1512

16-
node app.js
13+
yarn start
1714

1815
```

test/upload-server/app.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
const Koa = require('koa');
2-
const fs = require('fs');
32
const path = require('path');
43
const router = require('koa-router')();
54
const koaBody = require('koa-body');
65
const static = require('koa-static');
76
const cors = require('koa2-cors');
7+
const fs = require('fs-extra');
88
const app = new Koa();
99

10+
const uploadUrl = 'http://localhost:3001/static/upload';
11+
12+
fs.ensureDir(path.join(__dirname, 'static/upload'));
13+
1014
app.use(cors());
1115

1216
app.use(
@@ -19,8 +23,6 @@ app.use(
1923
})
2024
);
2125

22-
const uploadUrl = 'http://localhost:3001/static/upload';
23-
2426
router.get('/', (ctx) => {
2527
ctx.type = 'html';
2628
const pathUrl = path.join(__dirname, '/static/upload.html');
@@ -61,7 +63,6 @@ const uploadFilePublic = function (ctx, files, flag) {
6163
}
6264
};
6365
if (flag) {
64-
// 多个文件上传
6566
for (let i = 0; i < files.length; i++) {
6667
const f1 = files[i];
6768
fileFunc(f1);

test/upload-server/package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
{
2-
"name": "server",
2+
"name": "upload-server",
33
"version": "1.0.0",
4-
"main": "index.js",
4+
"main": "app.js",
55
"license": "MIT",
6+
"scripts": {
7+
"start": "node app.js"
8+
},
69
"dependencies": {
7-
"koa": "^2.13.0",
10+
"fs-extra": "^9.1.0",
11+
"koa": "^2.13.1",
812
"koa-body": "^4.2.0",
913
"koa-router": "^10.0.0",
1014
"koa-static": "^5.0.0",

test/upload-server/yarn.lock

+35-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ any-promise@^1.1.0:
2727
resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
2828
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
2929

30+
at-least-node@^1.0.0:
31+
version "1.0.0"
32+
resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
33+
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
34+
3035
bytes@3.1.0:
3136
version "3.1.0"
3237
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -146,6 +151,21 @@ fresh@~0.5.2:
146151
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
147152
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
148153

154+
fs-extra@^9.1.0:
155+
version "9.1.0"
156+
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
157+
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
158+
dependencies:
159+
at-least-node "^1.0.0"
160+
graceful-fs "^4.2.0"
161+
jsonfile "^6.0.1"
162+
universalify "^2.0.0"
163+
164+
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
165+
version "4.2.6"
166+
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
167+
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
168+
149169
http-assert@^1.3.0:
150170
version "1.4.1"
151171
resolved "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878"
@@ -213,6 +233,15 @@ is-generator-function@^1.0.7:
213233
resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b"
214234
integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==
215235

236+
jsonfile@^6.0.1:
237+
version "6.1.0"
238+
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
239+
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
240+
dependencies:
241+
universalify "^2.0.0"
242+
optionalDependencies:
243+
graceful-fs "^4.1.6"
244+
216245
keygrip@~1.1.0:
217246
version "1.1.0"
218247
resolved "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
@@ -282,7 +311,7 @@ koa2-cors@^2.0.6:
282311
resolved "https://registry.npmjs.org/koa2-cors/-/koa2-cors-2.0.6.tgz#9ad23df3a0b9bb84530b46f5944f3fb576086554"
283312
integrity sha512-JRCcSM4lamM+8kvKGDKlesYk2ASrmSTczDtGUnIadqMgnHU4Ct5Gw7Bxt3w3m6d6dy3WN0PU4oMP43HbddDEWg==
284313

285-
koa@^2.13.0:
314+
koa@^2.13.1:
286315
version "2.13.1"
287316
resolved "https://registry.npmjs.org/koa/-/koa-2.13.1.tgz#6275172875b27bcfe1d454356a5b6b9f5a9b1051"
288317
integrity sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==
@@ -451,6 +480,11 @@ type-is@^1.6.14, type-is@^1.6.16:
451480
media-typer "0.3.0"
452481
mime-types "~2.1.24"
453482

483+
universalify@^2.0.0:
484+
version "2.0.0"
485+
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
486+
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
487+
454488
unpipe@1.0.0:
455489
version "1.0.0"
456490
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"

test/websocket-server/app.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const Koa = require('koa');
2+
const route = require('koa-route');
3+
const websockify = require('koa-websocket');
4+
5+
const app = websockify(new Koa());
6+
7+
app.ws.use(function (ctx, next) {
8+
ctx.websocket.send('connection succeeded!');
9+
return next(ctx);
10+
});
11+
12+
app.ws.use(
13+
route.all('/test', function (ctx) {
14+
// ctx.websocket.send('Hello World');
15+
ctx.websocket.on('message', function (message) {
16+
// do something with the message from client
17+
18+
if (message !== 'ping') {
19+
let data = JSON.stringify({
20+
id: Math.ceil(Math.random() * 1000),
21+
time: new Date().getTime(),
22+
res: `${message}`,
23+
});
24+
ctx.websocket.send(data);
25+
}
26+
console.log(message);
27+
});
28+
})
29+
);
30+
31+
app.listen(3380, () => {
32+
console.log('websocket server is listen in: ' + 3380);
33+
});

test/websocket-server/package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "websocket-server",
3+
"version": "1.0.0",
4+
"main": "app.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "node app.js"
8+
},
9+
"dependencies": {
10+
"fs-extra": "^9.1.0",
11+
"koa": "^2.13.1",
12+
"koa-route": "^3.2.0",
13+
"koa-websocket": "^6.0.0"
14+
}
15+
}

0 commit comments

Comments
 (0)