[mod]
This commit is contained in:
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
frontend/.vscode/extensions.json
vendored
Normal file
3
frontend/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
16
frontend/README.md
Normal file
16
frontend/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
|
||||
|
||||
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
|
||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
33833
frontend/package-lock.json
generated
Normal file
33833
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
frontend/package.json
Normal file
33
frontend/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.22.4",
|
||||
"tsrpc-browser": "^3.3.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.9.7",
|
||||
"vue-tsc": "^0.34.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.5.17",
|
||||
"@vue/cli-plugin-eslint": "^4.5.17",
|
||||
"@vue/cli-plugin-typescript": "^4.5.17",
|
||||
"@vue/cli-service": "^4.5.17",
|
||||
"@vue/compiler-sfc": "^3.2.33",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"less": "^3.13.1",
|
||||
"less-loader": "^5.0.0"
|
||||
}
|
||||
}
|
||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
23
frontend/src/App.vue
Normal file
23
frontend/src/App.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import Chatroom from "./Chatroom.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "app",
|
||||
components: {
|
||||
Chatroom,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Chatroom title="Client #1" />
|
||||
<Chatroom title="Client #2" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
236
frontend/src/Chatroom.vue
Normal file
236
frontend/src/Chatroom.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="Chatroom">
|
||||
<header>{{ title }}</header>
|
||||
<ul class="list" ref="ul">
|
||||
<li v-for="(v, i) in list" :key="i">
|
||||
<div class="content">{{ v.content }}</div>
|
||||
<div class="time">{{ v.time.toLocaleTimeString() }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div> <button @click="connect">Connect</button><button @click="start">Start</button> </div>
|
||||
<div class="send">
|
||||
<input placeholder="Say something..." v-model="input" @keypress.enter="send" />
|
||||
<button @click="send">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick } from "vue";
|
||||
import { getClient } from "./getClient";
|
||||
import { Msgstart } from "./shared/protocols/lobby/Msgstart";
|
||||
import { MsgChat } from "./shared/protocols/MsgChat";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Chatroom",
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input: "",
|
||||
list: [] as MsgChat[],
|
||||
client: getClient(),
|
||||
};
|
||||
},
|
||||
|
||||
mounted(): void {
|
||||
// Listen Msg
|
||||
this.client.listenMsg("Chat", (v: MsgChat) => {
|
||||
console.log("Chat")
|
||||
this.list.push(v);
|
||||
|
||||
// Scroll the list to the bottom
|
||||
nextTick(() => {
|
||||
const ul = this.$refs.ul as HTMLElement;
|
||||
ul.scrollTo(0, ul.scrollHeight);
|
||||
});
|
||||
});
|
||||
|
||||
// Listen Msg
|
||||
this.client.listenMsg("lobby/B_start", (v: Msgstart) => {
|
||||
console.log("lobby/B_start")
|
||||
let listdata: MsgChat = {
|
||||
time: v.time,
|
||||
content: v.data.content,
|
||||
};
|
||||
this.list.push(listdata);
|
||||
|
||||
// Scroll the list to the bottom
|
||||
nextTick(() => {
|
||||
const ul = this.$refs.ul as HTMLElement;
|
||||
ul.scrollTo(0, ul.scrollHeight);
|
||||
});
|
||||
});
|
||||
|
||||
// When disconnected
|
||||
this.client.flows.postDisconnectFlow.push((v) => {
|
||||
// alert("Server disconnected");
|
||||
let listdata: MsgChat = {
|
||||
time: new Date(),
|
||||
content: "Server disconnected",
|
||||
};
|
||||
this.list.push(listdata);
|
||||
return v;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
async connect(): Promise<void> {
|
||||
// this.client = getClient();
|
||||
// let listdata: MsgChat = {
|
||||
// time: new Date(),
|
||||
// content: `connect`,
|
||||
// };
|
||||
// this.list.push(listdata);
|
||||
|
||||
this.client.connect().then((v) => {
|
||||
if (!v.isSucc) {
|
||||
// alert("= Client Connect Error =\n" + v.errMsg);
|
||||
let listdata: MsgChat = {
|
||||
time: new Date(),
|
||||
content: "= Client Connect Error =\n" + v.errMsg,
|
||||
};
|
||||
this.list.push(listdata);
|
||||
} else {
|
||||
// alert("= Client Connect Error =\n" + v.errMsg);
|
||||
let listdata: MsgChat = {
|
||||
time: new Date(),
|
||||
content: "Server Connect",
|
||||
};
|
||||
this.list.push(listdata);
|
||||
}
|
||||
});
|
||||
},
|
||||
async start(): Promise<void> {
|
||||
let ret = await this.client.callApi("lobby/start", {});
|
||||
|
||||
// Error
|
||||
if (!ret.isSucc) {
|
||||
// alert(ret.err.message);
|
||||
let listdata: MsgChat = {
|
||||
time: new Date(),
|
||||
content: ret.err.message,
|
||||
};
|
||||
this.list.push(listdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
this.input = "";
|
||||
},
|
||||
// Send input message
|
||||
async send(): Promise<void> {
|
||||
let ret = await this.client.callApi("Send", {
|
||||
content: this.input,
|
||||
});
|
||||
|
||||
// Error
|
||||
if (!ret.isSucc) {
|
||||
// alert(ret.err.message);
|
||||
let listdata: MsgChat = {
|
||||
time: new Date(),
|
||||
content: ret.err.message,
|
||||
};
|
||||
this.list.push(listdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
this.input = "";
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// .div .Chatroom {
|
||||
// display: inline-block;
|
||||
// }
|
||||
|
||||
.Chatroom {
|
||||
// display: inline-block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 460px;
|
||||
height: 480px;
|
||||
margin: 20px;
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #454545;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
>header {
|
||||
background: #454545;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
>.send {
|
||||
flex: 0 0 40px;
|
||||
display: flex;
|
||||
border-top: 1px solid #454545;
|
||||
|
||||
>* {
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
>input {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
>button {
|
||||
flex: 0 0 100px;
|
||||
background: #215fa4;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #4b80bb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
padding-bottom: 20px;
|
||||
background: #f2f2f2;
|
||||
|
||||
>li {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
line-height: 1.5em;
|
||||
border-radius: 5px;
|
||||
|
||||
>.content {
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
>.time {
|
||||
font-size: 12px;
|
||||
color: #4b80bb;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
frontend/src/assets/index.css
Normal file
100
frontend/src/assets/index.css
Normal file
@@ -0,0 +1,100 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body>h1 {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Chatroom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 460px;
|
||||
height: 480px;
|
||||
margin: 20px;
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #454545;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-room>header {
|
||||
background: #454545;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.send {
|
||||
flex: 0 0 40px;
|
||||
display: flex;
|
||||
border-top: 1px solid #454545;
|
||||
}
|
||||
|
||||
.send>* {
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.send>input {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.send>button {
|
||||
flex: 0 0 100px;
|
||||
background: #215fa4;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send>button:hover {
|
||||
background: #4b80bb;
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
padding-bottom: 20px;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.list>li {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
line-height: 1.5em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.list>li>.content {
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.list>li>.time {
|
||||
font-size: 12px;
|
||||
color: #4b80bb;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.list>li:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
BIN
frontend/src/assets/logo.png
Normal file
BIN
frontend/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
52
frontend/src/components/HelloWorld.vue
Normal file
52
frontend/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
Recommended IDE setup:
|
||||
<a href="https://code.visualstudio.com/" target="_blank">VS Code</a>
|
||||
+
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
</p>
|
||||
|
||||
<p>See <code>README.md</code> for more information.</p>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Docs
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
|
||||
</p>
|
||||
|
||||
<button type="button" @click="count++">count is: {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
color: #304455;
|
||||
}
|
||||
</style>
|
||||
16
frontend/src/env.d.ts
vendored
Normal file
16
frontend/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
// TSRPC would decode ObjectId as string in frontend.
|
||||
declare module 'mongodb' {
|
||||
export type ObjectId = string;
|
||||
export type ObjectID = string;
|
||||
}
|
||||
declare module 'bson' {
|
||||
export type ObjectId = string;
|
||||
export type ObjectID = string;
|
||||
}
|
||||
11
frontend/src/getClient.ts
Normal file
11
frontend/src/getClient.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { WsClient } from "tsrpc-browser";
|
||||
import { serviceProto, ServiceType } from "./shared/protocols/serviceProto";
|
||||
|
||||
export function getClient(): WsClient<ServiceType> {
|
||||
return new WsClient(serviceProto, {
|
||||
server: "ws://127.0.0.1:4000",
|
||||
// Remove this to use binary mode (remove from the server too)
|
||||
json: true,
|
||||
logger: console,
|
||||
})
|
||||
}
|
||||
4
frontend/src/main.ts
Normal file
4
frontend/src/main.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
1
frontend/src/shared
Symbolic link
1
frontend/src/shared
Symbolic link
@@ -0,0 +1 @@
|
||||
D:/Project/Test/Test_NodeJS/TSRPC_Badminton-Scoreboard/backend/src/shared
|
||||
33
frontend/tsconfig.json
Normal file
33
frontend/tsconfig.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
frontend/tsconfig.node.json
Normal file
8
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
11
frontend/vite.config.ts
Normal file
11
frontend/vite.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 5000
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user