Refactor code structure and remove redundant changes
This commit is contained in:
133
utils/RefreshToken.ts
Normal file
133
utils/RefreshToken.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import axios, { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'
|
||||
import type { AppMemberUserInfoRespVO } from '@/api/common/types'
|
||||
export type onRefreshTokenResponse = (data: AxiosResponse | boolean) => { token: string; refreshToken: string } | false
|
||||
|
||||
export type TRefreshTokenConstructorConfig = {
|
||||
refreshTokenApiUrl: string
|
||||
refreshTokenKey?: string
|
||||
tokenKey?: string
|
||||
axiosInstance: AxiosInstance
|
||||
onRefreshTokenResponse: onRefreshTokenResponse
|
||||
onRefreshTokenResquest?: () => {
|
||||
config?: AxiosRequestConfig
|
||||
data?: any
|
||||
}
|
||||
}
|
||||
export default class RefreshToken {
|
||||
static instance: RefreshToken
|
||||
static SOTRAGE_REFRESH_TOKEN_KEY = 'kl-tk'
|
||||
static SOTRAGE_TOKENKEY = 'kl-rfk'
|
||||
static SOTRAGE_USERID = 'kl-userId'
|
||||
static SOTRAGE_USERNAME = 'kl-userName'
|
||||
static SOTRAGE_USERINFO = 'kl-userInfo'
|
||||
private REFRESH_TOKEN_API_URL: string
|
||||
public pending = false
|
||||
private axios: AxiosInstance
|
||||
public callbacks: any[] = []
|
||||
private onRefreshTokenResponse: onRefreshTokenResponse
|
||||
private onRefreshTokenResquest?: () => {
|
||||
config?: AxiosRequestConfig
|
||||
data?: any
|
||||
}
|
||||
|
||||
protected constructor(config: TRefreshTokenConstructorConfig) {
|
||||
RefreshToken.SOTRAGE_REFRESH_TOKEN_KEY = config?.refreshTokenKey || 'kl-tk'
|
||||
RefreshToken.SOTRAGE_TOKENKEY = config?.refreshTokenKey || 'kl-rfk'
|
||||
RefreshToken.SOTRAGE_USERID = config?.refreshTokenKey || 'kl-userId'
|
||||
this.REFRESH_TOKEN_API_URL = config?.refreshTokenApiUrl || ''
|
||||
this.axios = config.axiosInstance
|
||||
this.onRefreshTokenResponse = config.onRefreshTokenResponse
|
||||
this.onRefreshTokenResquest = config.onRefreshTokenResquest
|
||||
}
|
||||
|
||||
static create(config: TRefreshTokenConstructorConfig) {
|
||||
if (!RefreshToken.instance) {
|
||||
RefreshToken.instance = new RefreshToken(config)
|
||||
}
|
||||
return RefreshToken.instance
|
||||
}
|
||||
|
||||
// 设置token
|
||||
setToken(token: string, refreshToken: string) {
|
||||
window.localStorage.setItem(RefreshToken.SOTRAGE_TOKENKEY, token)
|
||||
window.localStorage.setItem(RefreshToken.SOTRAGE_REFRESH_TOKEN_KEY, refreshToken)
|
||||
}
|
||||
|
||||
// 清除token
|
||||
removeToken() {
|
||||
window.localStorage.removeItem(RefreshToken.SOTRAGE_TOKENKEY)
|
||||
window.localStorage.removeItem(RefreshToken.SOTRAGE_REFRESH_TOKEN_KEY)
|
||||
//清除token,一起把userID也清除了
|
||||
window.localStorage.removeItem(RefreshToken.SOTRAGE_USERID)
|
||||
window.localStorage.removeItem(RefreshToken.SOTRAGE_USERNAME)
|
||||
window.localStorage.removeItem(RefreshToken.SOTRAGE_USERINFO)
|
||||
}
|
||||
//设置userID
|
||||
setUserId(userId: string) {
|
||||
window.localStorage.setItem(RefreshToken.SOTRAGE_USERID, userId)
|
||||
}
|
||||
setUserName(userName: string) {
|
||||
window.localStorage.setItem(RefreshToken.SOTRAGE_USERNAME, userName)
|
||||
}
|
||||
setUserInfo(userInfo: AppMemberUserInfoRespVO) {
|
||||
window.localStorage.setItem(RefreshToken.SOTRAGE_USERINFO, JSON.stringify(userInfo))
|
||||
}
|
||||
// 获取token
|
||||
getToken() {
|
||||
const token = window.localStorage.getItem(RefreshToken.SOTRAGE_TOKENKEY) || ''
|
||||
const refreshToken = window.localStorage.getItem(RefreshToken.SOTRAGE_REFRESH_TOKEN_KEY) || ''
|
||||
const userId = window.localStorage.getItem(RefreshToken.SOTRAGE_USERID) || ''
|
||||
const userName = window.localStorage.getItem(RefreshToken.SOTRAGE_USERNAME) || ''
|
||||
const userInfo = window.localStorage.getItem(RefreshToken.SOTRAGE_USERINFO) || ''
|
||||
return {
|
||||
token,
|
||||
refreshToken,
|
||||
userId,
|
||||
userName,
|
||||
userInfo: userInfo ? JSON.parse(userInfo) : {},
|
||||
}
|
||||
}
|
||||
// 刷新token
|
||||
refresh() {
|
||||
return new Promise<{ token: string; refreshToken: string } | boolean>((resolve) => {
|
||||
this.pending = true
|
||||
const config = (this.onRefreshTokenResquest && this.onRefreshTokenResquest()) || {}
|
||||
axios
|
||||
.post(this.REFRESH_TOKEN_API_URL, config.data || {}, config.config)
|
||||
.then((res) => {
|
||||
const ac = this.onRefreshTokenResponse(res)
|
||||
if (ac) {
|
||||
// 设置新的token
|
||||
this.setToken(ac.token, ac.refreshToken)
|
||||
resolve(ac)
|
||||
return
|
||||
}
|
||||
resolve(false)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
this.onRefreshTokenResponse(false)
|
||||
resolve(false)
|
||||
})
|
||||
.finally(() => {
|
||||
this.pending = false
|
||||
})
|
||||
})
|
||||
}
|
||||
// 推入刷新token成功 回调队列
|
||||
pushCallbacks(config: AxiosRequestConfig) {
|
||||
return new Promise((resolve) => {
|
||||
this.callbacks.push(() => {
|
||||
resolve(this.axios(config))
|
||||
})
|
||||
})
|
||||
}
|
||||
// 推出队列回调
|
||||
releaseQueue() {
|
||||
// const tokens = this.getToken()
|
||||
this.callbacks.forEach((item) => {
|
||||
item()
|
||||
})
|
||||
this.callbacks = []
|
||||
}
|
||||
}
|
||||
126
utils/WebSocketWithHeartbeatAndReconnect.ts
Normal file
126
utils/WebSocketWithHeartbeatAndReconnect.ts
Normal file
@ -0,0 +1,126 @@
|
||||
export default class WebSocketWithHeartbeatAndReconnect {
|
||||
private url: string
|
||||
private socket: WebSocket | null
|
||||
private heartbeatInterval: number
|
||||
private pingMessage: string
|
||||
private pongMessage: string
|
||||
private reconnectInterval: number
|
||||
private maxReconnectAttempts: number
|
||||
private reconnectAttempts: number
|
||||
private heartbeatTimer: any
|
||||
private reconnectTimer: any
|
||||
|
||||
constructor(url: string, options = {} as any) {
|
||||
this.url = url
|
||||
this.socket = null
|
||||
this.heartbeatInterval = options.heartbeatInterval || 30000 // 默认每 30 秒发送一次心跳
|
||||
this.pingMessage = options.pingMessage || 'ping' // 默认 ping 消息
|
||||
this.pongMessage = options.pongMessage || 'pong' // 默认 pong 消息
|
||||
this.reconnectInterval = options.reconnectInterval || 5000 // 默认每 5 秒尝试重连
|
||||
this.maxReconnectAttempts = options.maxReconnectAttempts || 5 // 最大重连次数
|
||||
this.reconnectAttempts = 0 // 当前已尝试的重连次数
|
||||
this.heartbeatTimer = null // 存储心跳定时器
|
||||
this.reconnectTimer = null // 存储重连定时器
|
||||
}
|
||||
|
||||
// 初始化 WebSocket 连接
|
||||
connect() {
|
||||
if (this.socket) {
|
||||
this.socket.close() // 关闭现有连接
|
||||
}
|
||||
|
||||
this.socket = new WebSocket(this.url)
|
||||
|
||||
// 连接成功时启动心跳
|
||||
this.socket.onopen = () => {
|
||||
console.log('WebSocket connected')
|
||||
this.reconnectAttempts = 0 // 重置重连次数
|
||||
this.startHeartbeat() // 启动心跳机制
|
||||
}
|
||||
|
||||
// 接收到消息时
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log('Received message:', event.data)
|
||||
|
||||
// 如果接收到 pong 消息,说明服务器回应了心跳
|
||||
if (event.data === this.pongMessage) {
|
||||
console.log('Pong received')
|
||||
this.resetHeartbeat() // 重置心跳定时器
|
||||
} else {
|
||||
this.getMsg(event.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 连接关闭时
|
||||
this.socket.onclose = (event) => {
|
||||
console.log('WebSocket closed:', event)
|
||||
this.stopHeartbeat() // 停止心跳机制
|
||||
this.handleReconnect() // 尝试重连
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
this.socket.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
this.socket?.close() // 在发生错误时关闭连接
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳机制,定时发送 ping 消息
|
||||
startHeartbeat() {
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
console.log('Sending ping...')
|
||||
this.socket.send(this.pingMessage) // 发送 ping 消息
|
||||
}
|
||||
}, this.heartbeatInterval)
|
||||
}
|
||||
|
||||
// 重置心跳机制
|
||||
resetHeartbeat() {
|
||||
clearInterval(this.heartbeatTimer) // 清除之前的心跳定时器
|
||||
this.startHeartbeat() // 重新启动心跳定时器
|
||||
}
|
||||
|
||||
// 停止心跳机制
|
||||
stopHeartbeat() {
|
||||
clearInterval(this.heartbeatTimer) // 清除心跳定时器
|
||||
clearTimeout(this.reconnectTimer) // 清除重连定时器
|
||||
}
|
||||
|
||||
// 处理重连机制
|
||||
handleReconnect() {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++
|
||||
console.log(`Attempting to reconnect... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
|
||||
|
||||
// 重连时延
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.connect() // 尝试重新连接
|
||||
}, this.reconnectInterval)
|
||||
} else {
|
||||
console.log('Max reconnect attempts reached. Giving up.')
|
||||
}
|
||||
}
|
||||
|
||||
// 发送数据
|
||||
send(message: string) {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(message)
|
||||
console.log('Message sent:', message)
|
||||
} else {
|
||||
console.log('WebSocket is not open. Cannot send message.')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
close() {
|
||||
if (this.socket) {
|
||||
this.socket.close()
|
||||
console.log('WebSocket closed manually.')
|
||||
}
|
||||
}
|
||||
// 接受到的数据
|
||||
getMsg(val: string) {
|
||||
console.log(val)
|
||||
}
|
||||
}
|
||||
151
utils/axios.ts
Normal file
151
utils/axios.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import qs from 'qs'
|
||||
import useUserStore from '@/store/user'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import RefreshToken from './RefreshToken'
|
||||
// 允许跨域
|
||||
axios.defaults.headers.post['Access-Control-Allow-Origin-Type'] = '*'
|
||||
const axiosInstance: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 1200000,
|
||||
})
|
||||
|
||||
// 自动刷新 token 实现
|
||||
export const refreshToken = RefreshToken.create({
|
||||
axiosInstance,
|
||||
refreshTokenApiUrl: '',
|
||||
onRefreshTokenResponse: (res: any) => {
|
||||
if (res.data && (res?.data?.code === 0 || res?.data?.statusCode === '00000') && res.data.data) {
|
||||
return {
|
||||
token: res?.data?.data?.access_token || '',
|
||||
refreshToken: res?.data?.data?.refresh_token || '',
|
||||
}
|
||||
}
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
return false
|
||||
},
|
||||
onRefreshTokenResquest() {
|
||||
const tokens = refreshToken.getToken()
|
||||
return {
|
||||
data: {
|
||||
refreshToken: tokens.refreshToken,
|
||||
},
|
||||
config: {
|
||||
headers: {
|
||||
refreshToken: tokens.refreshToken,
|
||||
},
|
||||
data: {
|
||||
refreshToken: tokens.refreshToken,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
// axios实例拦截响应
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
//因为后端的有些接口没有statusCode这个状态码,先这样判断吧
|
||||
if (response.data.statusCode === '00000' || response.data.code === 0) {
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage.error(response.data.msg)
|
||||
return response.data
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
let message = '系统异常'
|
||||
const { status, config } = error.response
|
||||
if (status === 401) {
|
||||
if (refreshToken.pending) {
|
||||
return refreshToken.pushCallbacks(config)
|
||||
} else {
|
||||
// 刷新token
|
||||
refreshToken.refresh().then((refreshSuccess) => {
|
||||
// 刷新成功 回调错误队列
|
||||
if (refreshSuccess) {
|
||||
refreshToken.releaseQueue()
|
||||
} else {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
})
|
||||
// 推入第一个401请求
|
||||
return refreshToken.pushCallbacks(config)
|
||||
}
|
||||
} else if (status === 404) {
|
||||
message = '接口地址有误'
|
||||
} else if (status === 400) {
|
||||
message = '参数有误,请确认参数是否正确'
|
||||
}
|
||||
ElMessage.error(message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
// axios实例拦截请求
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
config.headers['Accept-Language'] = 'zh-CN'
|
||||
const tokens = refreshToken.getToken()
|
||||
if (tokens !== null) {
|
||||
config.headers['authorization'] = `Bearer ${tokens.token}`
|
||||
config.headers['Refreshtoken'] = tokens.refreshToken
|
||||
}
|
||||
|
||||
// 防止get请求缓存
|
||||
if (config.method === 'get') {
|
||||
config.params = {
|
||||
...config.params,
|
||||
...{ _t: new Date().getTime() },
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlainObject(config.data)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.data = {
|
||||
...config.data,
|
||||
}
|
||||
if (config.headers?.['content-type']) {
|
||||
const contentType: string = config.headers['content-type'] + ''
|
||||
if (/^application\/x-www-form-urlencoded/.test(contentType)) {
|
||||
// form形式编码
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
const request = <T = any>(config: AxiosRequestConfig): Promise<T> => {
|
||||
const conf = config
|
||||
return new Promise((resolve, reject) => {
|
||||
axiosInstance
|
||||
.request<any, AxiosResponse<IResponse>>(conf)
|
||||
.then((res: AxiosResponse<IResponse>) => {
|
||||
const data: any = res
|
||||
resolve(data as T)
|
||||
})
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
}
|
||||
// 封装get
|
||||
export function get<T = any>(config: AxiosRequestConfig): Promise<T> {
|
||||
return request({ ...config, method: 'GET' })
|
||||
}
|
||||
// 封装delete
|
||||
export function Delete<T = any>(config: AxiosRequestConfig): Promise<T> {
|
||||
return request({ ...config, method: 'delete' })
|
||||
}
|
||||
// 封装put
|
||||
export function put<T = any>(config: AxiosRequestConfig): Promise<T> {
|
||||
return request({ ...config, method: 'put' })
|
||||
}
|
||||
// 封装post
|
||||
export function post<T = any>(config: AxiosRequestConfig): Promise<T> {
|
||||
return request({ ...config, method: 'POST' })
|
||||
}
|
||||
|
||||
export default axiosInstance
|
||||
export type { AxiosInstance, AxiosResponse }
|
||||
24
utils/className.ts
Normal file
24
utils/className.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 检查元素是否存在cls这个名字的class
|
||||
* @private
|
||||
*/
|
||||
export function hasClass(obj: HTMLElement, cls: string) {
|
||||
return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
|
||||
}
|
||||
/**
|
||||
* 为元素添加class
|
||||
* @private
|
||||
*/
|
||||
export function addClass(obj: HTMLElement, cls: string) {
|
||||
if (!hasClass(obj, cls)) obj.className += ' ' + cls
|
||||
}
|
||||
/**
|
||||
* 删除元素中的class
|
||||
* @private
|
||||
*/
|
||||
export function removeClass(obj: HTMLElement, cls: string) {
|
||||
if (hasClass(obj, cls)) {
|
||||
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
|
||||
obj.className = obj.className.replace(reg, ' ')
|
||||
}
|
||||
}
|
||||
31
utils/login.ts
Normal file
31
utils/login.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export const QQ_APP_ID = '102796005' // QQ appid
|
||||
export const WECHAT_APP_ID = 'wxc2fa7aafa1649f7a' // 微信 appid
|
||||
|
||||
// 生成随机字符串
|
||||
export const generateRandomString = (length: number) => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// QQ登录
|
||||
export const handleLoginQQ = () => {
|
||||
const appId = QQ_APP_ID // APP ID
|
||||
const redirectUri = encodeURIComponent('https://tuxixi.net/index?type=35') // 回调地址
|
||||
const state = generateRandomString(16) // 生成随机state
|
||||
// 存储state用于后续验证
|
||||
localStorage.setItem('qq_login_state', state)
|
||||
window.location.href = `https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=${appId}&redirect_uri=${redirectUri}&state=${state}`
|
||||
}
|
||||
|
||||
export const handleLoginWechat = () => {
|
||||
const appId = WECHAT_APP_ID // APP ID
|
||||
const redirectUri = encodeURIComponent('https://tuxixi.net?type=32') // 回调地址
|
||||
const state = generateRandomString(16) // 生成随机state
|
||||
// 存储state用于后续验证
|
||||
localStorage.setItem('wechat_login_state', state)
|
||||
window.location.href = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_login&state=${state}`
|
||||
}
|
||||
101
utils/mqttClient.ts
Normal file
101
utils/mqttClient.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import mqtt, { MqttClient, IClientOptions } from 'mqtt'
|
||||
|
||||
// MQTT 配置
|
||||
const defaultOptions: IClientOptions = {
|
||||
clean: true,
|
||||
connectTimeout: 4000,
|
||||
clientId: 'vue3-client-' + Math.random().toString(16).substr(2, 8),
|
||||
username: 'zbjk',
|
||||
password: 'zbjk@123456',
|
||||
// 移除 port 配置
|
||||
// port: 8083,
|
||||
reconnectPeriod: 1000, // 添加重连间隔
|
||||
}
|
||||
|
||||
// MQTT 代理地址(示例使用公共测试服务器)
|
||||
const brokerUrl = 'wss://www.tuxixi.net/mqtt'
|
||||
|
||||
export default class MQTTClient {
|
||||
private client: MqttClient | null = null
|
||||
private url: string
|
||||
private options: IClientOptions
|
||||
|
||||
constructor(url: string, options = {} as any) {
|
||||
this.client = null
|
||||
this.url = url || brokerUrl
|
||||
this.options = { ...defaultOptions, ...options }
|
||||
}
|
||||
|
||||
connect(): void {
|
||||
this.client = mqtt.connect(this.url, this.options)
|
||||
|
||||
// 连接成功
|
||||
this.client.on('connect', () => {
|
||||
console.log('MQTT Connected successfully')
|
||||
})
|
||||
|
||||
// 连接关闭
|
||||
this.client.on('close', () => {
|
||||
console.log('MQTT Connection closed')
|
||||
})
|
||||
|
||||
// 重连
|
||||
this.client.on('reconnect', () => {
|
||||
console.log('MQTT Attempting to reconnect...')
|
||||
})
|
||||
|
||||
// 错误处理
|
||||
this.client.on('error', (error: Error) => {
|
||||
console.error('MQTT Error:', error)
|
||||
console.error('Error details:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
url: this.url,
|
||||
options: this.options,
|
||||
})
|
||||
})
|
||||
|
||||
// 连接结束
|
||||
this.client.on('end', () => {
|
||||
console.log('MQTT Connection ended')
|
||||
})
|
||||
}
|
||||
|
||||
subscribe(topic: string): void {
|
||||
if (!this.client) return
|
||||
|
||||
this.client.subscribe(topic, { qos: 0 }, (err?: any) => {
|
||||
if (!err) console.log(`Subscribed to ${topic}`)
|
||||
})
|
||||
}
|
||||
|
||||
unsubscribe(topic: string): void {
|
||||
if (!this.client) return
|
||||
|
||||
this.client.unsubscribe(topic, (err?: any) => {
|
||||
if (!err) console.log(`Unsubscribed from ${topic}`)
|
||||
})
|
||||
}
|
||||
|
||||
// 接收消息处理
|
||||
onMessage(callback: (topic: string, message: string) => void): void {
|
||||
if (!this.client) return
|
||||
|
||||
this.client.on('message', (topic: string, message: Buffer) => {
|
||||
console.log('接收消息---------', topic, message.toString())
|
||||
callback(topic, message.toString())
|
||||
})
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
publish(topic: string, message: string): void {
|
||||
if (!this.client) return
|
||||
|
||||
console.log('发送消息---------', topic, message)
|
||||
this.client.publish(topic, message, { qos: 0 })
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.client?.end()
|
||||
}
|
||||
}
|
||||
11
utils/rem.ts
Normal file
11
utils/rem.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// 设置 rem 函数
|
||||
function setRem() {
|
||||
// 1920 默认大小16px; 1920px = 120rem ;每个元素px基础上/16
|
||||
const screenWidth = 1920
|
||||
const scale = screenWidth / 16
|
||||
const htmlWidth = window.screen.width
|
||||
// 得到html的Dom元素
|
||||
const htmlDom = document.getElementsByTagName('html')[0]
|
||||
// 设置根元素字体大小
|
||||
htmlDom.style.fontSize = htmlWidth / scale + 'px'
|
||||
}
|
||||
81
utils/useMessage.ts
Normal file
81
utils/useMessage.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||
export const useMessage = () => {
|
||||
return {
|
||||
// 消息提示
|
||||
info(content: string) {
|
||||
ElMessage.info(content)
|
||||
},
|
||||
// 错误消息
|
||||
error(content: string) {
|
||||
ElMessage.error(content)
|
||||
},
|
||||
// 成功消息
|
||||
success(content: string) {
|
||||
ElMessage.success(content)
|
||||
},
|
||||
// 警告消息
|
||||
warning(content: string) {
|
||||
ElMessage.warning(content)
|
||||
},
|
||||
// 弹出提示
|
||||
alert(content: string, title: string) {
|
||||
ElMessageBox.alert(content, title)
|
||||
},
|
||||
// 错误提示
|
||||
alertError(content: string, title: string) {
|
||||
ElMessageBox.alert(content, title, { type: 'error' })
|
||||
},
|
||||
// 成功提示
|
||||
alertSuccess(content: string, title: string) {
|
||||
ElMessageBox.alert(content, title, { type: 'success' })
|
||||
},
|
||||
// 警告提示
|
||||
alertWarning(content: string, title: string) {
|
||||
ElMessageBox.alert(content, title, { type: 'warning' })
|
||||
},
|
||||
// 通知提示
|
||||
notify(content: string) {
|
||||
ElNotification.info(content)
|
||||
},
|
||||
// 错误通知
|
||||
notifyError(content: string) {
|
||||
ElNotification.error(content)
|
||||
},
|
||||
// 成功通知
|
||||
notifySuccess(content: string) {
|
||||
ElNotification.success(content)
|
||||
},
|
||||
// 警告通知
|
||||
notifyWarning(content: string) {
|
||||
ElNotification.warning(content)
|
||||
},
|
||||
// 确认窗体
|
||||
confirm(content: string, title?: string, options?: any) {
|
||||
return ElMessageBox.confirm(content, title, {
|
||||
type: 'warning',
|
||||
...options,
|
||||
})
|
||||
},
|
||||
// 删除窗体
|
||||
delConfirm(content?: string, title?: string, options?: any) {
|
||||
return ElMessageBox.confirm(content, title, {
|
||||
type: 'warning',
|
||||
...options,
|
||||
})
|
||||
},
|
||||
// 导出窗体
|
||||
exportConfirm(content?: string, title?: string, options?: any) {
|
||||
return ElMessageBox.confirm(content, title, {
|
||||
type: 'warning',
|
||||
...options,
|
||||
})
|
||||
},
|
||||
// 提交内容
|
||||
prompt(content: string, title: string, options?: any) {
|
||||
return ElMessageBox.prompt(content, title, {
|
||||
type: 'warning',
|
||||
...options,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
98
utils/utils.ts
Normal file
98
utils/utils.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import Decimal from 'decimal.js'
|
||||
/**
|
||||
* 加法计算
|
||||
* @param param1 当前值number或string
|
||||
* @param param2 当前值number或string
|
||||
* @param num 保留小数位数,默认小数2位
|
||||
* @param mode 舍入模式,1 向上取,2 向下取,3 四舍五入,默认3
|
||||
* @returns 返回处理后的数据
|
||||
*/
|
||||
export function accAdd(param1: number | string, param2: number | string, num = 2, mode = 3) {
|
||||
if (param1 === null || param1 === undefined || param1 === '') return param1
|
||||
if (param2 === null || param2 === undefined || param2 === '') return param2
|
||||
|
||||
const temp = new Decimal(param1).add(new Decimal(param2))
|
||||
if (mode === 1) return temp.toFixed(num, Decimal.ROUND_UP)
|
||||
else if (mode === 2) return temp.toFixed(num, Decimal.ROUND_DOWN)
|
||||
else return temp.toFixed(num, Decimal.ROUND_HALF_UP)
|
||||
}
|
||||
/**
|
||||
* 减法计算
|
||||
* @param param1 当前值number或string
|
||||
* @param param2 当前值number或string
|
||||
* @param num 保留小数位数,默认小数2位
|
||||
* @param mode 舍入模式,1 向上取,2 向下取,3 四舍五入,默认3
|
||||
* @returns 返回处理后的数据
|
||||
*/
|
||||
export function accSub(param1: number | string, param2: number | string, num = 2, mode = 3) {
|
||||
if (param1 === null || param1 === undefined || param1 === '') return param1
|
||||
if (param2 === null || param2 === undefined || param2 === '') return param2
|
||||
|
||||
const temp = new Decimal(param1).sub(new Decimal(param2))
|
||||
if (mode === 1) return temp.toFixed(num, Decimal.ROUND_UP)
|
||||
else if (mode === 2) return temp.toFixed(num, Decimal.ROUND_DOWN)
|
||||
else return temp.toFixed(num, Decimal.ROUND_HALF_UP)
|
||||
}
|
||||
/**
|
||||
* 乘法计算
|
||||
* @param param1 当前值number或string
|
||||
* @param param2 当前值number或string
|
||||
* @param num 保留小数位数,默认小数2位
|
||||
* @param mode 舍入模式,1 向上取,2 向下取,3 四舍五入,默认3
|
||||
* @returns 返回处理后的数据
|
||||
*/
|
||||
export function accMul(param1: number | string, param2: number | string, num = 2, mode = 3) {
|
||||
if (param1 === null || param1 === undefined || param1 === '') return param1
|
||||
if (param2 === null || param2 === undefined || param2 === '') return param2
|
||||
|
||||
const temp = new Decimal(param1).mul(new Decimal(param2))
|
||||
if (mode === 1) return temp.toFixed(num, Decimal.ROUND_UP)
|
||||
else if (mode === 2) return temp.toFixed(num, Decimal.ROUND_DOWN)
|
||||
else return temp.toFixed(num, Decimal.ROUND_HALF_UP)
|
||||
}
|
||||
/**
|
||||
* 除法计算
|
||||
* @param param1 当前值number或string
|
||||
* @param param2 当前值number或string
|
||||
* @param num 保留小数位数,默认小数2位
|
||||
* @param mode 舍入模式,1 向上取,2 向下取,3 四舍五入,默认3
|
||||
* @returns 返回处理后的数据
|
||||
*/
|
||||
export function accDiv(param1: number | string, param2: number | string, num = 2, mode = 3) {
|
||||
if (param1 === null || param1 === undefined || param1 === '') return param1
|
||||
if (param2 === null || param2 === undefined || param2 === '' || param2.toString() === '0') return param2
|
||||
|
||||
const temp = new Decimal(param1).div(new Decimal(param2))
|
||||
if (mode === 1) return temp.toFixed(num, Decimal.ROUND_UP)
|
||||
else if (mode === 2) return temp.toFixed(num, Decimal.ROUND_DOWN)
|
||||
else return temp.toFixed(num, Decimal.ROUND_HALF_UP)
|
||||
}
|
||||
/**
|
||||
* 数字格式化千分位显示
|
||||
* @param decimals 保留小数位数, 传-1则表示: 原数字有小数则保留两位小数,原数字没小数则为整数
|
||||
* @returns 返回处理后的数据字符串
|
||||
*/
|
||||
export const formatNumber = (value?: number, decimals = 0) => {
|
||||
if (!value) return 0
|
||||
if (decimals === -1) {
|
||||
decimals = value % 1 === 0 ? 0 : 2
|
||||
}
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
/***
|
||||
* 下载文件
|
||||
* @param url 文件地址
|
||||
* @param filename 文件名
|
||||
*/
|
||||
export const downloadFile = (url: string, filename: string) => {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename || 'download' // 设置下载的文件名
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
58
utils/validate.ts
Normal file
58
utils/validate.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 只能输入汉字或字母或数字
|
||||
* @param {} arg
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function textInputValidate(value?: string) {
|
||||
if (!value) return ''
|
||||
return value.replace(/[^\u4E00-\u9FA5A-Za-z0-9]/gi, '')
|
||||
}
|
||||
/**
|
||||
* 只能输入小数或整数(不可以负数)
|
||||
* @param val 当前值number或string
|
||||
* @returns 返回处理后的
|
||||
*/
|
||||
export function verifyNumberIntegerAndFloat<T = string>(val?: number | string) {
|
||||
if (!val) {
|
||||
if (typeof val === 'string') return val as T
|
||||
else return undefined
|
||||
}
|
||||
// 匹配空格
|
||||
let v = val.toString().replace(/(^\s*)|(\s*$)/g, '')
|
||||
// 只能是数字和小数点,不能是其他输入
|
||||
v = v.replace(/[^\d.]/g, '')
|
||||
// 以0开始只能输入一个
|
||||
v = v.replace(/^0{2}$/g, '0')
|
||||
// 保证第一位只能是数字,不能是点
|
||||
v = v.replace(/^\./g, '')
|
||||
// 小数只能出现1位
|
||||
v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.')
|
||||
// 小数点后面保留2位
|
||||
v = v.replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3')
|
||||
// 返回结果
|
||||
return v as T
|
||||
}
|
||||
|
||||
/**
|
||||
* 只能输入正整数
|
||||
* @param val 当前值number或string
|
||||
* @returns 返回处理后的
|
||||
*/
|
||||
export function verifiyNumberInteger<T = string>(val?: number | string) {
|
||||
if (!val) {
|
||||
if (typeof val === 'string') return val as T
|
||||
else return undefined
|
||||
}
|
||||
// 匹配空格
|
||||
let v = val.toString().replace(/(^\s*)|(\s*$)/g, '')
|
||||
// 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
|
||||
v = v.replace(/[.]*/g, '')
|
||||
// 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
|
||||
v = v.replace(/(^0[\d]*)$/g, '0')
|
||||
// 首位是0,只能出现一次
|
||||
v = v.replace(/^0\d$/g, '0')
|
||||
// 只匹配数字
|
||||
v = v.replace(/[^\d]/g, '')
|
||||
// 返回结果
|
||||
return v as T
|
||||
}
|
||||
Reference in New Issue
Block a user