This commit is contained in:
oneao committed 2026-04-24 17:28:11 +08:00
1 parent 07e0001b87
commit 46cbb2f50d
543 files changed
+32576 -4231

No files matched your search

@@ -0,0 +1,46 @@
import type { CompanyQuery } from '@/types/base'
import type { Area, Industry } from '@/types/company'
import type { PostTag } from '@/types/post'
import { defineStore } from 'pinia'
import { computed, reactive, ref } from 'vue'
export const useCommonStore = defineStore('common', () => {
const areaOptions = reactive<Area[]>([])
const industryOptions = reactive<Industry[]>([])
const postTagOptions = reactive<PostTag[]>([]) // 原始列表,不含“全部”
const companyQuery = ref<CompanyQuery>({
industries: [],
areas: [],
name: '',
})
// 计算属性:带“全部”选项
const postTagOptionsWithAll = computed(() => [
{ id: 0, name: '全部', icon: 'fluent-color:apps-28' },
...postTagOptions,
])
function setAreaOptions(options: Area[]) {
areaOptions.splice(0, areaOptions.length, ...options)
}
function setIndustryOptions(options: Industry[]) {
industryOptions.splice(0, industryOptions.length, ...options)
}
function setPostTagOptions(options: PostTag[]) {
postTagOptions.splice(0, postTagOptions.length, ...options)
}
return {
areaOptions,
industryOptions,
companyQuery,
postTagOptions, // 原始数据
postTagOptionsWithAll, // 带“全部”,响应式
setAreaOptions,
setIndustryOptions,
setPostTagOptions,
}
})
@@ -0,0 +1,25 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCountStore = defineStore('count', () => {
const imCount = ref(0)
function incrementImCount(amount: number = 1) {
imCount.value += amount
}
function decrementImCount(amount: number = 1) {
imCount.value = Math.max(imCount.value - amount, 0)
}
function setImCount(value: number) {
imCount.value = value
}
return {
imCount,
incrementImCount,
decrementImCount,
setImCount,
}
})
@@ -0,0 +1,9 @@
import type { App } from 'vue'
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
export function setupPinia(app: App) {
const store = createPinia()
store.use(createPersistedState())
app.use(store)
}
@@ -0,0 +1,97 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
interface IUser {
id: number
role: number
name: string
position: string
avatar: string
}
interface ICompany {
id: number
logo: string
name: string
}
export const useUserStore = defineStore(
'user',
() => {
const user = ref<IUser>({
id: 0,
role: 0,
name: '',
position: '',
avatar: '',
})
const company = ref<ICompany>({
id: 0,
logo: '',
name: '',
})
const token = ref('')
// ===== setters =====
const setUser = (val: IUser) => {
user.value = val
}
const setCompany = (val: ICompany) => {
company.value = val
}
const setToken = (val: string) => {
token.value = val
}
// 一次性设置(登录时用)
const setLoginInfo = (data: {
user: IUser
company: ICompany
token: string
}) => {
user.value = data.user
company.value = data.company
token.value = data.token
}
const clear = () => {
user.value = {
id: 0,
role: 0,
name: '',
position: '',
avatar: '',
}
company.value = {
id: 0,
logo: '',
name: '',
}
token.value = ''
}
// ===== computed =====
const isLogined = computed(() => {
return !!(token.value && user.value.id && company.value.id)
})
return {
user,
company,
token,
setUser,
setCompany,
setToken,
setLoginInfo,
clear,
isLogined,
}
},
{
persist: true,
},
)
@@ -0,0 +1,292 @@
import type { MessageRequest, MessageResponse } from '@/types/im'
import { readonly, ref } from 'vue'
import { MessageType } from '@/types/im'
import { useUserStore } from './user'
interface UseWebSocketOptions {
url: string
heartbeatInterval?: number
pongTimeout?: number
reconnectInterval?: number
maxReconnectTimes?: number
}
/* ================== 单例 ================== */
let singleton: ReturnType<typeof createWebSocket> | null = null
function createWebSocket(options?: Partial<UseWebSocketOptions>) {
const userStore = useUserStore()
/* ================== 配置 ================== */
const config: Required<UseWebSocketOptions> = {
url:
options?.url
|| import.meta.env.VITE_WEBSOCKET_BASEURL
|| 'ws://127.0.0.1:8090/ws',
heartbeatInterval: options?.heartbeatInterval ?? 15_000,
pongTimeout: options?.pongTimeout ?? 5_000,
reconnectInterval: options?.reconnectInterval ?? 3_000,
maxReconnectTimes: options?.maxReconnectTimes ?? 5,
}
/* ================== 状态 ================== */
let ws: WebSocket | null = null
const connected = ref(false)
const authenticated = ref(false)
let cachedToken: string | null = userStore.token
let heartbeatTimer: number | null = null
let pongTimeoutTimer: number | null = null
let reconnectTimer: number | null = null
let reconnectTimes = 0
let manualClose = false
/* ================== 外部消息回调 ================== */
const messageHandlers: Array<(msg: MessageResponse) => void> = []
/* ================== 连接管理 ================== */
function connect() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
console.log('[WebSocket] 已存在连接或正在连接,跳过')
return
}
console.log('[WebSocket] 开始建立连接')
manualClose = false
ws = new WebSocket(config.url)
ws.onopen = () => {
connected.value = true
reconnectTimes = 0
console.log('[WebSocket] 连接成功')
startHeartbeat()
// 自动重新登录
if (cachedToken) {
console.log('[WebSocket] 自动重新发送 LOGIN')
login(cachedToken)
}
}
ws.onmessage = (evt) => {
const msg: MessageResponse = JSON.parse(evt.data)
console.log('===收到消息===')
console.log(msg)
handleMessage(msg)
}
ws.onclose = () => {
console.warn('[WebSocket] 连接关闭')
connected.value = false
authenticated.value = false
stopHeartbeat()
stopPongTimeout()
ws = null
if (!manualClose) {
tryReconnect()
}
}
ws.onerror = (err) => {
console.error('[WebSocket] 连接错误', err)
}
}
function close() {
console.log('[WebSocket] 主动关闭连接')
manualClose = true
stopHeartbeat()
stopPongTimeout()
clearReconnectTimer()
ws?.close()
ws = null
}
/* ================== 重连 ================== */
function tryReconnect() {
if (reconnectTimer || reconnectTimes >= config.maxReconnectTimes)
return
reconnectTimes++
console.warn(`[WebSocket] 第 ${reconnectTimes} 次重连中...`)
reconnectTimer = window.setTimeout(() => {
reconnectTimer = null
connect()
}, config.reconnectInterval)
}
function clearReconnectTimer() {
if (reconnectTimer) {
clearTimeout(reconnectTimer)
reconnectTimer = null
}
}
/* ================== 心跳 ================== */
function startHeartbeat() {
stopHeartbeat()
heartbeatTimer = window.setTimeout(() => {
sendRaw(MessageType.PING)
startPongTimeout()
startHeartbeat()
}, config.heartbeatInterval)
}
function stopHeartbeat() {
if (heartbeatTimer) {
clearTimeout(heartbeatTimer)
heartbeatTimer = null
}
}
function startPongTimeout() {
stopPongTimeout()
pongTimeoutTimer = window.setTimeout(() => {
console.error('[WebSocket] 心跳超时,断开连接')
ws?.close()
}, config.pongTimeout)
}
function stopPongTimeout() {
if (pongTimeoutTimer) {
clearTimeout(pongTimeoutTimer)
pongTimeoutTimer = null
}
}
/* ================== 发送 ================== */
function sendRaw(type: MessageType, data?: any, to?: any) {
if (!ws || ws.readyState !== WebSocket.OPEN) {
console.warn('[WebSocket] 发送失败,未连接')
return
}
const msg: MessageRequest = {
type,
from: userStore.user.id,
to: to ?? null,
timestamp: Date.now(),
data: data ?? null,
}
console.log('===发送消息===')
console.log(msg)
ws.send(JSON.stringify(msg))
}
function send(type: MessageType, data?: any, to?: any) {
if (!authenticated.value && type !== MessageType.LOGIN) {
console.warn('[WebSocket] 未 LOGIN,禁止发送业务消息')
return
}
sendRaw(type, data, to)
}
/* ================== 登录相关 ================== */
function login(token: string) {
console.log('触发login:', authenticated.value)
if (!connected.value || authenticated.value)
return
cachedToken = token
sendRaw(MessageType.LOGIN, { token })
}
function logout() {
cachedToken = null
authenticated.value = false
sendRaw(MessageType.LOGOUT)
}
/* ================== 消息处理(核心修改点) ================== */
function handleMessage(msg: MessageResponse) {
switch (msg.type) {
case MessageType.PONG:
stopPongTimeout()
return
case MessageType.LOGIN:
authenticated.value = true
console.log('[WebSocket] LOGIN 成功')
return
case MessageType.LOGOUT:
authenticated.value = false
console.log('[WebSocket] 已 LOGOUT')
return
default:
// 所有业务消息交给外部
messageHandlers.forEach(cb => cb(msg))
}
}
/* ================== 对外订阅 ================== */
function onMessage(handler: (msg: MessageResponse) => void) {
messageHandlers.push(handler)
}
/* ================== 业务 API ================== */
function sendPrivate(toUserId: number, content: any) {
send(MessageType.PRIVATE, content, toUserId)
}
function sendGroup(groupId: number, content: any) {
send(MessageType.GROUP, content, groupId)
}
/* ================== 初始化 ================== */
connect()
return {
connect,
close,
login,
logout,
sendPrivate,
sendGroup,
onMessage,
connected: readonly(connected),
authenticated: readonly(authenticated),
}
}
/* ================== 对外 ================== */
export function useWebSocket(options?: Partial<UseWebSocketOptions>) {
if (!singleton) {
singleton = createWebSocket(options)
}
return singleton
}