28 changed files with 5459 additions and 10 deletions
-
3.env
-
35.gitignore
-
32README.md
-
1env.d.ts
-
13index.html
-
4876package-lock.json
-
41package.json
-
8postcss.config.js
-
BINpublic/favicon.ico
-
51src/App.vue
-
31src/assets/base.css
-
1src/assets/logo.svg
-
7src/assets/main.css
-
46src/components/HelloWorld.vue
-
20src/main.ts
-
23src/router/index.ts
-
58src/services/axios.ts
-
108src/services/socket.ts
-
12src/stores/counter.ts
-
3src/style.css
-
9src/utils/index.ts
-
9src/views/AboutView.vue
-
9src/views/HomeView.vue
-
12tailwind.config.js
-
12tsconfig.app.json
-
11tsconfig.json
-
18tsconfig.node.json
-
20vite.config.ts
@ -0,0 +1,3 @@ |
|||
VITE_API_HOST=window.location.hostname |
|||
VITE_API_PORT=80 |
|||
VITE_WS_PATH=/api/v1/app/ws/state |
@ -1,11 +1,30 @@ |
|||
# ---> Vue |
|||
# gitignore template for Vue.js projects |
|||
# |
|||
# Recommended template: Node.gitignore |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
pnpm-debug.log* |
|||
lerna-debug.log* |
|||
|
|||
# TODO: where does this rule come from? |
|||
docs/_book |
|||
node_modules |
|||
.DS_Store |
|||
dist |
|||
dist-ssr |
|||
coverage |
|||
*.local |
|||
|
|||
# TODO: where does this rule come from? |
|||
test/ |
|||
/cypress/videos/ |
|||
/cypress/screenshots/ |
|||
|
|||
# Editor directories and files |
|||
.vscode/* |
|||
!.vscode/extensions.json |
|||
.idea |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw? |
|||
|
|||
*.tsbuildinfo |
@ -1,3 +1,31 @@ |
|||
# auxiliary_sample |
|||
# A1800 |
|||
|
|||
辅助加样 |
|||
## Project Setup |
|||
|
|||
```sh |
|||
npm install |
|||
``` |
|||
|
|||
### Compile and Hot-Reload for Development |
|||
|
|||
```sh |
|||
npm run dev |
|||
``` |
|||
|
|||
### Type-Check, Compile and Minify for Production |
|||
|
|||
```sh |
|||
npm run build |
|||
``` |
|||
|
|||
## 约定 |
|||
|
|||
目录: |
|||
views: 主要页面 |
|||
components: 子组件 |
|||
services: API接口 |
|||
assets: 资源(图片) |
|||
utils: 实用工具 |
|||
|
|||
.env 环境变量(如IP,Port) |
|||
.env.local 本地环境变量 |
@ -0,0 +1 @@ |
|||
/// <reference types="vite/client" />
|
@ -0,0 +1,13 @@ |
|||
<!DOCTYPE html> |
|||
<html lang=""> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<link rel="icon" href="/favicon.ico"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
|||
<title>Vite App</title> |
|||
</head> |
|||
<body> |
|||
<div id="app"></div> |
|||
<script type="module" src="/src/main.ts"></script> |
|||
</body> |
|||
</html> |
4876
package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,41 @@ |
|||
{ |
|||
"name": "a1800", |
|||
"version": "0.0.0", |
|||
"private": true, |
|||
"type": "module", |
|||
"scripts": { |
|||
"dev": "vite", |
|||
"build": "run-p type-check \"build-only {@}\" --", |
|||
"preview": "vite preview", |
|||
"build-only": "vite build", |
|||
"type-check": "vue-tsc --build" |
|||
}, |
|||
"dependencies": { |
|||
"axios": "^1.7.9", |
|||
"dayjs": "^1.11.13", |
|||
"pinia": "^2.3.0", |
|||
"ramda": "^0.30.1", |
|||
"rxjs": "^7.8.1", |
|||
"vant": "^4.9.16", |
|||
"vue": "^3.5.13", |
|||
"vue-router": "^4.5.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@tsconfig/node22": "^22.0.0", |
|||
"@types/node": "^22.10.2", |
|||
"@types/ramda": "^0.30.2", |
|||
"@types/rx": "^4.1.4", |
|||
"@vitejs/plugin-vue": "^5.2.1", |
|||
"@vitejs/plugin-vue-jsx": "^4.1.1", |
|||
"@vue/tsconfig": "^0.7.0", |
|||
"autoprefixer": "^10.4.20", |
|||
"npm-run-all2": "^7.0.2", |
|||
"postcss": "^8.5.1", |
|||
"postcss-import": "^16.1.0", |
|||
"tailwindcss": "^3.4.17", |
|||
"typescript": "~5.6.3", |
|||
"vite": "^6.0.5", |
|||
"vite-plugin-vue-devtools": "^7.6.8", |
|||
"vue-tsc": "^2.1.10" |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
export default { |
|||
plugins: { |
|||
'postcss-import': {}, |
|||
'tailwindcss/nesting': {}, |
|||
tailwindcss: {}, |
|||
autoprefixer: {}, |
|||
}, |
|||
} |
@ -0,0 +1,51 @@ |
|||
<script setup lang="ts"> |
|||
import { RouterLink, RouterView } from "vue-router"; |
|||
import HelloWorld from "./components/HelloWorld.vue"; |
|||
</script> |
|||
|
|||
<template> |
|||
<header> |
|||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" /> |
|||
|
|||
<div class="wrapper"> |
|||
<HelloWorld msg="You did it!" /> |
|||
|
|||
<h1 class="text-3xl font-bold underline">Hello world!</h1> |
|||
|
|||
<nav> |
|||
<RouterLink to="/">Home</RouterLink> |
|||
<RouterLink to="/about">About</RouterLink> |
|||
</nav> |
|||
</div> |
|||
</header> |
|||
|
|||
<RouterView /> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
header { |
|||
line-height: 1.5; |
|||
max-height: 100vh; |
|||
.logo { |
|||
display: block; |
|||
margin: 0 auto 2rem; |
|||
} |
|||
nav { |
|||
width: 100%; |
|||
font-size: 12px; |
|||
text-align: center; |
|||
margin-top: 2rem; |
|||
a { |
|||
display: inline-block; |
|||
padding: 0 1rem; |
|||
border-left: 1px solid var(--color-border); |
|||
&:first-of-type { |
|||
border: 0; |
|||
} |
|||
&.router-link-exact-active { |
|||
color: green; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,31 @@ |
|||
|
|||
*, |
|||
*::before, |
|||
*::after { |
|||
box-sizing: border-box; |
|||
margin: 0; |
|||
padding: 0; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
body { |
|||
min-height: 100vh; |
|||
font-family: |
|||
Inter, |
|||
-apple-system, |
|||
BlinkMacSystemFont, |
|||
'Segoe UI', |
|||
Roboto, |
|||
Oxygen, |
|||
Ubuntu, |
|||
Cantarell, |
|||
'Fira Sans', |
|||
'Droid Sans', |
|||
'Helvetica Neue', |
|||
sans-serif; |
|||
font-size: 16px; |
|||
text-rendering: optimizeLegibility; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
user-select: none; |
|||
} |
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg> |
@ -0,0 +1,7 @@ |
|||
@import './base.css'; |
|||
|
|||
#app { |
|||
max-width: 1280px; |
|||
max-height: 800px; |
|||
overflow: hidden; |
|||
} |
@ -0,0 +1,46 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from "vue"; |
|||
import { showToast } from "vant"; |
|||
|
|||
defineProps<{ |
|||
msg: string; |
|||
}>(); |
|||
const value = ref<number>(50); |
|||
const onChange = (value: number) => showToast("当前值:" + value); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="greetings"> |
|||
<h1 class="green">{{ msg }}</h1> |
|||
<h3> |
|||
You’ve successfully created a project with |
|||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> + |
|||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next? |
|||
</h3> |
|||
<span class="text-lg font-medium">{{ value }}</span> |
|||
<van-slider class="my-8" :step="0.5" v-model="value" @change="onChange" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.greetings { |
|||
--van-slider-bar-height: 6px; |
|||
--van-slider-button-width: 32px; |
|||
--van-slider-button-height: 32px; |
|||
} |
|||
h1 { |
|||
font-weight: 500; |
|||
font-size: 2.6rem; |
|||
position: relative; |
|||
top: -10px; |
|||
} |
|||
|
|||
h3 { |
|||
font-size: 1.2rem; |
|||
} |
|||
|
|||
.greetings h1, |
|||
.greetings h3 { |
|||
text-align: center; |
|||
} |
|||
</style> |
@ -0,0 +1,20 @@ |
|||
import "./style.css"; |
|||
import "./assets/main.css"; |
|||
|
|||
import { createApp } from "vue"; |
|||
import { createPinia } from "pinia"; |
|||
import Vant from "vant"; |
|||
import App from "./App.vue"; |
|||
import router from "./router"; |
|||
import "vant/lib/index.css"; |
|||
|
|||
const app = createApp(App); |
|||
|
|||
app.use(createPinia()); |
|||
app.use(router); |
|||
app.use(Vant); |
|||
|
|||
app.mount("#app"); |
|||
|
|||
console.log("API HOST:", import.meta.env.VITE_API_HOST); |
|||
console.log("API PORT:", import.meta.env.VITE_API_PORT); |
@ -0,0 +1,23 @@ |
|||
import { createRouter, createWebHistory } from 'vue-router' |
|||
import HomeView from '../views/HomeView.vue' |
|||
|
|||
const router = createRouter({ |
|||
history: createWebHistory(import.meta.env.BASE_URL), |
|||
routes: [ |
|||
{ |
|||
path: '/', |
|||
name: 'home', |
|||
component: HomeView, |
|||
}, |
|||
{ |
|||
path: '/about', |
|||
name: 'about', |
|||
// route level code-splitting
|
|||
// this generates a separate chunk (About.[hash].js) for this route
|
|||
// which is lazy-loaded when the route is visited.
|
|||
component: () => import('../views/AboutView.vue'), |
|||
}, |
|||
], |
|||
}) |
|||
|
|||
export default router |
@ -0,0 +1,58 @@ |
|||
import axios from "axios"; |
|||
|
|||
const url = `${window.location.protocol}://${import.meta.env.VITE_API_HOST}:${import.meta.env.VITE_API_PORT}`; |
|||
|
|||
const apiClient = axios.create({ |
|||
baseURL: url, // 设置请求的根路径
|
|||
timeout: 60000, |
|||
headers: { |
|||
"Content-Type": "application/json", |
|||
}, |
|||
}); |
|||
|
|||
// 请求拦截器
|
|||
// apiClient.interceptors.request.use(
|
|||
// (config) => {
|
|||
// const token = sessionStorage.getItem("token");
|
|||
// if (!config.headers) {
|
|||
// config.headers = AxiosHeaders.from({}); // 确保 config.headers 是 AxiosHeaders 类型
|
|||
// }
|
|||
|
|||
// if (token) {
|
|||
// config.headers.set("Authorization", `Bearer ${encodeURIComponent(token)}`); // 使用 set 方法设置 Authorization
|
|||
// }
|
|||
|
|||
// return config;
|
|||
// },
|
|||
// error => {
|
|||
// return Promise.reject(error);
|
|||
// }
|
|||
// );
|
|||
|
|||
// 响应拦截器
|
|||
apiClient.interceptors.response.use( |
|||
(response) => { |
|||
if (response.data && response.data.dataType === "ZAppPromopt") { |
|||
if (response.data.ecode === "USR_NOT_EXIT") { |
|||
return Promise.resolve(response.data); |
|||
} else if (response.data.ecode === "USR_PASSWORD_ERROR") { |
|||
return Promise.resolve(response.data); |
|||
} else { |
|||
console.log("接口出错", response.data); |
|||
// eventBus.emit("show-error-modal", response.data.data);
|
|||
return Promise.reject(response.data); |
|||
} |
|||
} |
|||
return response; |
|||
}, |
|||
error => { |
|||
// eventBus.emit("show-error-modal", {
|
|||
// messageLevel: "Error",
|
|||
// title: "网络请求失败",
|
|||
// info: error.message,
|
|||
// });
|
|||
return Promise.reject(error); |
|||
} |
|||
); |
|||
|
|||
export default apiClient; |
@ -0,0 +1,108 @@ |
|||
import { Subject } from "rxjs"; |
|||
|
|||
export type SocketState = 'open' | 'close' | 'error' |
|||
|
|||
class WebSocketClient { |
|||
private ws: WebSocket | null = null; |
|||
private url: string; |
|||
private reconnectAttempts: number = -1; |
|||
private maxReconnectAttempts: number = 5; |
|||
private reconnectInterval: number = 3000; |
|||
|
|||
readonly dataOb = new Subject() |
|||
readonly stateOb = new Subject<SocketState>() |
|||
|
|||
constructor(url: string) { |
|||
this.url = url; |
|||
} |
|||
|
|||
// 连接 WebSocket
|
|||
connect(): void { |
|||
try { |
|||
// WebSocket.CONNECTING (0) WebSocket.OPEN (1)
|
|||
if (this.ws && this.ws.readyState <= 1) { |
|||
// 已连接
|
|||
console.log(`${this.url} 正在连接或已连接,无需重复连接`); |
|||
} else { |
|||
this.ws = new WebSocket(this.url); |
|||
this.bindEvents(); |
|||
} |
|||
} catch (error) { |
|||
console.error("WebSocket 连接失败:", error); |
|||
this.reconnect(); |
|||
} |
|||
} |
|||
|
|||
// 绑定事件
|
|||
private bindEvents(): void { |
|||
if (!this.ws) return; |
|||
|
|||
// 连接建立时的处理
|
|||
this.ws.onopen = () => { |
|||
console.log("WebSocket 连接已建立"); |
|||
this.reconnectAttempts = -1; // 重置重连次数
|
|||
this.stateOb.next('open') |
|||
}; |
|||
|
|||
// 接收消息的处理
|
|||
this.ws.onmessage = (event: MessageEvent) => { |
|||
try { |
|||
const data = JSON.parse(event.data); |
|||
// console.log('🚀 ~ WebSocketClient ~ bindEvents ~ data:', data)
|
|||
this.dataOb.next(data) |
|||
} catch (error) { |
|||
console.error("消息解析错误:", error); |
|||
} |
|||
}; |
|||
|
|||
this.ws.onclose = () => { |
|||
this.stateOb.next('close') |
|||
console.log("WebSocket 连接已关闭"); |
|||
this.reconnect(); |
|||
}; |
|||
|
|||
this.ws.onerror = error => { |
|||
this.stateOb.next('error') |
|||
console.error("WebSocket 错误:", error); |
|||
}; |
|||
} |
|||
|
|||
// 重连机制
|
|||
private reconnect(): void { |
|||
if (this.reconnectAttempts === -1) { |
|||
this.reconnectAttempts = 0; |
|||
} |
|||
if (this.reconnectAttempts >= this.maxReconnectAttempts) { |
|||
console.log("达到最大重连次数,停止重连"); |
|||
this.reconnectAttempts = -1; |
|||
return; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`); |
|||
this.reconnectAttempts++; |
|||
this.connect(); |
|||
}, this.reconnectInterval); |
|||
} |
|||
|
|||
// 关闭连接
|
|||
disconnect(): void { |
|||
if (this.ws) { |
|||
this.ws.close(); |
|||
this.ws = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
const urlSocketMap = new Map<string, WebSocketClient>(); |
|||
|
|||
// 导出 WebSocket 客户端
|
|||
export const createWebSocket = (url: string): WebSocketClient => { |
|||
if (urlSocketMap.has(url)) { |
|||
return urlSocketMap.get(url)!; |
|||
} else { |
|||
const client = new WebSocketClient(url); |
|||
urlSocketMap.set(url, client); |
|||
return client; |
|||
} |
|||
}; |
@ -0,0 +1,12 @@ |
|||
import { ref, computed } from 'vue' |
|||
import { defineStore } from 'pinia' |
|||
|
|||
export const useCounterStore = defineStore('counter', () => { |
|||
const count = ref(0) |
|||
const doubleCount = computed(() => count.value * 2) |
|||
function increment() { |
|||
count.value++ |
|||
} |
|||
|
|||
return { count, doubleCount, increment } |
|||
}) |
@ -0,0 +1,3 @@ |
|||
@tailwind base; |
|||
@tailwind components; |
|||
@tailwind utilities; |
@ -0,0 +1,9 @@ |
|||
export function isValidIPv4(ip: string) { |
|||
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; |
|||
return ipv4Regex.test(ip); |
|||
} |
|||
export function formatRemainTime(seconds: number) { |
|||
const min = Math.floor(seconds / 60).toFixed(); |
|||
const sec = (seconds % 60).toFixed(); |
|||
return min.padStart(2, "0") + ":" + sec.padStart(2, "0"); |
|||
} |
@ -0,0 +1,9 @@ |
|||
<template> |
|||
<div class="about"> |
|||
<h1>This is an about page</h1> |
|||
</div> |
|||
</template> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,9 @@ |
|||
<script setup lang="ts"> |
|||
|
|||
</script> |
|||
|
|||
<template> |
|||
<main> |
|||
<div>Home Sub View</div> |
|||
</main> |
|||
</template> |
@ -0,0 +1,12 @@ |
|||
/** @type {import('tailwindcss').Config} */ |
|||
export default { |
|||
content: [ |
|||
"./index.html", |
|||
"./src/**/*.{vue,js,ts,jsx,tsx}", |
|||
], |
|||
theme: { |
|||
extend: {}, |
|||
}, |
|||
plugins: [], |
|||
} |
|||
|
@ -0,0 +1,12 @@ |
|||
{ |
|||
"extends": "@vue/tsconfig/tsconfig.dom.json", |
|||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], |
|||
"exclude": ["src/**/__tests__/*"], |
|||
"compilerOptions": { |
|||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
|||
|
|||
"paths": { |
|||
"@/*": ["./src/*"] |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"files": [], |
|||
"references": [ |
|||
{ |
|||
"path": "./tsconfig.node.json" |
|||
}, |
|||
{ |
|||
"path": "./tsconfig.app.json" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,18 @@ |
|||
{ |
|||
"extends": "@tsconfig/node22/tsconfig.json", |
|||
"include": [ |
|||
"vite.config.*", |
|||
"vitest.config.*", |
|||
"cypress.config.*", |
|||
"nightwatch.conf.*", |
|||
"playwright.config.*" |
|||
], |
|||
"compilerOptions": { |
|||
"noEmit": true, |
|||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
|||
|
|||
"module": "ESNext", |
|||
"moduleResolution": "Bundler", |
|||
"types": ["node"] |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
import { fileURLToPath, URL } from "node:url"; |
|||
|
|||
import { defineConfig } from "vite"; |
|||
import vue from "@vitejs/plugin-vue"; |
|||
import vueJsx from "@vitejs/plugin-vue-jsx"; |
|||
import vueDevTools from "vite-plugin-vue-devtools"; |
|||
|
|||
// https://vite.dev/config/
|
|||
export default defineConfig({ |
|||
plugins: [vue(), vueJsx(), vueDevTools()], |
|||
resolve: { |
|||
alias: { |
|||
"@": fileURLToPath(new URL("./src", import.meta.url)), |
|||
}, |
|||
}, |
|||
server: { |
|||
host: "0.0.0.0", |
|||
port: 5174, |
|||
}, |
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue